What happened
Cold-starting a Runtime Host against a workspace with a large accumulated artifact store blocks Host readiness for ~10 minutes at 60-70% CPU. Connected TUI clients render nothing during this window (maka --resume <id> appears to hang).
Observed on a long-lived workspace with ~11.7k artifact files (~6 GB on disk, comparable artifact_records row count). A long-running Host amortizes these costs in memory; when the Host is replaced (for example a version upgrade with a compatibility-epoch cutover), the new Host pays the full cost synchronously during startup recovery.
CPU profile of the Host main thread during the stall (CDP Profiler, 500µs sampling):
preparePurgePathsUnlocked (packages/storage/dist/artifact-store.js:528)
resolveArtifactRemovalEntry → realpath / lstat per record (node:internal/fs/promises)
decodeArtifactRecordJsons (artifact-metadata-codec.js) — full in-memory decode of all ~11.7k records
- Followed by
readEventsForRecovery / readSqliteAgentRunEvents replaying 56k core_agent_run_events + 29k session_messages before serving
Root causes in packages/storage/src/artifact-store.ts:
- Startup recovery (
hasCanonicalRecoveryResidueUnlocked, recoverMetadataTempsUnlocked, publication recovery) readdirs every session directory and runs realpath + lstat on every artifact file — O(files) syscalls on every Host start.
preparePurgePathsUnlocked performs its referential-integrity check by resolving resolveArtifactRemovalEntry (realpath) for all records, not just the purge set — O(all records) per purge() call, so retiring M sessions costs M × N realpaths.
- The artifact store eagerly decodes all metadata records into memory at open (
metadataRepository.readAll()).
Severity scales with artifact count. Fresh/small installs are unaffected (sub-millisecond sweeps), but heavy long-lived workspaces degrade on every Host restart (version update, epoch cutover, crash recovery), and the TUI gives no progress indication while it waits.
How to reproduce
- Accumulate a workspace artifact store with ~10k files (long-lived heavy use: many sessions, side conversations, tool outputs).
- Stop the running Runtime Host (or trigger an epoch cutover via a version upgrade).
- Run
maka --resume <session-id>.
- Observe: new Host spins at 60-70% CPU for ~10 minutes; TUI stays blank until Host recovery completes.
Profiling one-liner used:
kill -USR1 <host-pid> # enable inspector on 127.0.0.1:9229
# then CDP Profiler.start/stop over ws://127.0.0.1:9229/<id>
Environment
- Commit:
4cc781f31 (main, 2026-08-27)
- Node: 26.3.0
- OS: Linux
- Surface: Runtime Host / TUI
Logs, screenshots, or additional context
Top self-time frames from the profile (5s window during the stall):
12.1% run
5.2% run
1.3% realpath
1.1% lstat
1.0% lstat node:internal/fs/promises:1670
0.9% realpath node:internal/fs/promises:1826
0.8% preparePurgePathsUnlocked packages/storage/dist/artifact-store.js:528
0.6% decodeArtifactRecordJsons packages/storage/dist/artifact-metadata-codec.js:40
(libuv threadpool fs syscalls account for additional CPU not visible to the main-thread inspector.)
Suggested directions:
- Make startup recovery lazy or index-driven (metadata in SQLite) instead of per-file
realpath/lstat sweeps.
- In
preparePurgePathsUnlocked, restrict referential-integrity checks to records sharing the purge set's relative paths (metadata query) rather than resolving every record.
- Surface Host bootstrap progress to waiting TUI clients so a cold start does not look like a hang.
What happened
Cold-starting a Runtime Host against a workspace with a large accumulated artifact store blocks Host readiness for ~10 minutes at 60-70% CPU. Connected TUI clients render nothing during this window (
maka --resume <id>appears to hang).Observed on a long-lived workspace with ~11.7k artifact files (~6 GB on disk, comparable
artifact_recordsrow count). A long-running Host amortizes these costs in memory; when the Host is replaced (for example a version upgrade with a compatibility-epoch cutover), the new Host pays the full cost synchronously during startup recovery.CPU profile of the Host main thread during the stall (CDP
Profiler, 500µs sampling):preparePurgePathsUnlocked(packages/storage/dist/artifact-store.js:528)resolveArtifactRemovalEntry→realpath/lstatper record (node:internal/fs/promises)decodeArtifactRecordJsons(artifact-metadata-codec.js) — full in-memory decode of all ~11.7k recordsreadEventsForRecovery/readSqliteAgentRunEventsreplaying 56kcore_agent_run_events+ 29ksession_messagesbefore servingRoot causes in
packages/storage/src/artifact-store.ts:hasCanonicalRecoveryResidueUnlocked,recoverMetadataTempsUnlocked, publication recovery)readdirs every session directory and runsrealpath+lstaton every artifact file — O(files) syscalls on every Host start.preparePurgePathsUnlockedperforms its referential-integrity check by resolvingresolveArtifactRemovalEntry(realpath) for all records, not just the purge set — O(all records) perpurge()call, so retiring M sessions costs M × N realpaths.metadataRepository.readAll()).Severity scales with artifact count. Fresh/small installs are unaffected (sub-millisecond sweeps), but heavy long-lived workspaces degrade on every Host restart (version update, epoch cutover, crash recovery), and the TUI gives no progress indication while it waits.
How to reproduce
maka --resume <session-id>.Profiling one-liner used:
Environment
4cc781f31(main, 2026-08-27)Logs, screenshots, or additional context
Top self-time frames from the profile (5s window during the stall):
(libuv threadpool fs syscalls account for additional CPU not visible to the main-thread inspector.)
Suggested directions:
realpath/lstatsweeps.preparePurgePathsUnlocked, restrict referential-integrity checks to records sharing the purge set's relative paths (metadata query) rather than resolving every record.