Skip to content

Runtime storage

The Host's runtime storage is the on-disk representation of "what should be running." Two directories are involved, both relative to <host-home> and both configurable in host.json:

ts
// projects/matrix-3/packages/host-service/src/types.ts:6-9
interface IHostRuntimeStorageConfig {
  readonly recordsDir: string;  // default "runtimes"
  readonly logsDir: string;     // default "logs/runtimes"
}

hostPaths (host-paths.ts:50-62) joins them under <home> to produce the final paths.

Records: <home>/runtimes/

One subdirectory per runtime, named after the sanitized runtimeId. Each contains runtime.json — the durable record. Schema: Runtime record schema.

<home>/runtimes/
  RUNTIME-HOST-LOCAL-ABC123-SYSTEM/runtime.json
  RUNTIME-HOST-LOCAL-ABC123-HOST-CONTROL/runtime.json
  RUNTIME-HOST-LOCAL-ABC123-GATEWAY/runtime.json
  RUNTIME-HOST-LOCAL-ABC123-WEB/runtime.json
  RUNTIME-HOST-LOCAL-ABC123-EDGE/runtime.json
  ...

Sanitization (HostStateStore.sanitizeRuntimeId, host-state-store.ts:165-171) requires ^[A-Za-z0-9_-]+$ — anything else throws. The default IDs the wrapper generates always match.

Atomic writes

Every record write goes through HostStateStore.writeRuntimeRecord (host-state-store.ts:90-95) which calls atomicWriteJson: write to a temp file, fsync, rename, fsync the directory. A crash mid-write cannot leave a half-written record — at worst it leaves the previous record intact plus a .tmp.<pid>.<ts> file that is harmless.

Parse-and-skip recovery

HostStateStore.listRuntimeRecordsAndCorrupt (host-state-store.ts:106-142) reads every record and never throws on a single bad record. Outcome shape:

ts
{
  records: IHostRuntimeRecord[];   // healthy
  corrupt: ICorruptRuntimeRecord[]; // unhealthy, with reason
}

Reasons (see ICorruptRuntimeRecord.reason, host-state-store.ts:13-17):

reasonMeaning
missingRuntime directory exists but has no runtime.json (skipped silently)
emptyFile is zero bytes (the original 22-hour crash-loop bug)
parseFile exists but is not parseable JSON
shapeFile parses but does not match IHostRuntimeRecord

hivecast seed reports the corrupt list so an operator can see exactly which records need attention. hivecast start re-seeds empty records from desired state on the next auto-start. See WORKSTREAMS/launch-readiness-atomic-writes-and-bootstrap/.

Logs: <home>/logs/runtimes/<runtimeId>/

For every supervised runtime, the Host opens two append-mode log streams (matrix-host-service.ts:375-378):

<home>/logs/runtimes/<runtimeId>/stdout.log
<home>/logs/runtimes/<runtimeId>/stderr.log

The runtime's stdout and stderr are piped directly into these files. They are not rotated by the Host. On a .deb install, journald owns the Host's own stdout/stderr (capped at 2G — see Logs), but per-runtime log files are out-of-band.

For container/worker installs that need rotation, mount these via the container's logging driver or run logrotate over them externally.

Per-runtime environment files: <home>/runtime-env/

Not part of runtimeStorage config but lives in this domain. The Host writes one JSON file per runtime (matrix-host-service.ts:200-232, cli.ts:783-828):

<home>/runtime-env/<runtimeId>.environment.json

Contents (sketch):

json
{
  "name": "host",
  "nats": {
    "mode": "external",
    "url": "nats://127.0.0.1:4270",
    "wsUrl": "ws://127.0.0.1:4271",
    "credentialsRef": "file:credentials.json"
  },
  "runtime": {
    "root": "COM.OPEN-MATRIX.LOCAL.AUTHORITY",
    "runtimeId": "RUNTIME-HOST-LOCAL-ABC123-CHAT",
    "runtimeMount": "system.runtimes.RUNTIME-HOST-LOCAL-ABC123-CHAT",
    "controlMount": "system.runtimes.RUNTIME-HOST-LOCAL-ABC123-CHAT.control"
  },
  "host": { "matrixDir": "/home/me/.matrix" },
  "http": { "enabled": true, "port": 5183 }
}

The runtime reads this file on spawn (passed via --env-file in the default run command, cli.ts:599-617) so it knows which NATS to dial, which subjects to mount on, and which port to bind for served apps. The file is rewritten on every Host start and on every config.reload.

Treat it as derived state: never edit by hand, always restart the Host to regenerate.

Customizing storage paths

json
// host.json
{
  "runtimeStorage": {
    "recordsDir": "state/runtimes",
    "logsDir": "var/log/runtimes"
  }
}

Both are joined under <home>. Absolute paths are not supported (the Host joins via path.join, not resolves; an absolute path would still be treated as relative on Linux but is fragile and unsupported).

See also