{"type":"meta","slug":"daemon-hygiene","base_branch":"main","gate0_mode":"strict","preset":"cursor-only","land_mode":"merge-to-main","exec_mode":"ship"}
{"type":"goal","text":"Close the daemon-hygiene backlog: O(journal-size) tick re-parse, hung-detected spam on stale sidecars of terminal runs, hardcoded web test port, and the red ca-session wrapper test."}
{"type":"task","id":"t1","wave":1,"deps":[],"seat":"coder","tier":"medium","desc":"src/daemon.js re-parses ENTIRE run journals on every daemon tick: openRunJournalForScan and its callers invoke journal.readAll(), which reads and row-converts every event of every pointed-at run journal each tick (~15s), pegging a CPU core once journals grow (a 33k-event journal caused a death spiral before the 0.1.44 scheduler fix). Add an in-process per-journal entry cache keyed by journal path that stores the parsed entries plus the last seen event seq (or equivalent monotonic cursor), and on subsequent ticks parses ONLY rows with seq greater than the cursor, appending them to the cached entries; a journal whose cursor regresses or whose file identity changes (deleted/recreated) must invalidate its cache entry and fall back to a full re-read. Preserve the exact entries semantics all existing consumers see (shouldDetectHungRun, promotion, resolver scans) — the cache must be a pure read-path optimization with no behavior change. Do not change the SQLite schema or journal write path. Add a regression test in src/daemon.test.js that proves the second scan of an unchanged journal does not re-parse existing rows (e.g. via an injected journal/readAll counting seam) and that new rows appended between ticks are picked up. Acceptance: node --test src/daemon.test.js passes.","files_modify":["src/daemon.js","src/daemon.test.js"],"requires_decision":null}
{"type":"task","id":"t2","wave":2,"deps":["t1"],"seat":"coder","tier":"medium","desc":"The daemon watchdog keeps scanning terminal runs whose runstate/<slug>.live.json liveness sidecar was left behind (runner crashed or predates cleanup), and in past incidents emitted repeated run.hung-detected notifications (ntfy 429 spam) — the only remedy today is manually deleting the sidecar. In src/daemon.js watchdog tick: when a pointed-at run's journal is terminal (hasTerminalRunRecord true) but a liveness sidecar file still exists for that slug, reconcile it — remove the sidecar (and its .lock) after verifying via the existing lock/proc liveness checks that no live runner process owns it, and journal a single run.sidecar-reconciled event (slug, runId, sidecar hb_ts) so the action is auditable. Never remove a sidecar whose owning pid is alive. Never emit run.hung-detected for a journal-terminal run. Add regression tests in src/daemon.test.js covering: terminal journal + stale sidecar is reconciled exactly once (file gone, one event, no hung-detected notification); terminal journal + sidecar with a live owning pid is left untouched; non-terminal runs keep the existing hung-detection behavior. Acceptance: node --test src/daemon.test.js passes.","files_modify":["src/daemon.js","src/daemon.test.js"],"requires_decision":null}
{"type":"task","id":"t3","wave":1,"deps":[],"seat":"coder","tier":"medium","desc":"test/ensure-web-port.sh hardcodes TEST_PORT=39217; when any foreign process (e.g. another project's dev server) listens on that port the test fails as a false positive, and it is currently excluded from land gates because of this. Make the test allocate a free TCP port at runtime (e.g. bind port 0 via python3/node and reuse the assigned port for every subsequent reference in the script, including the python bind probe and nc checks) so the test never collides with unrelated listeners. Keep the test's assertions and structure otherwise unchanged. Acceptance: bash test/ensure-web-port.sh passes, and passes even while another process holds 39217.","files_modify":["test/ensure-web-port.sh"],"requires_decision":null}
{"type":"task","id":"t4","wave":1,"deps":[],"seat":"coder","tier":"medium","desc":"wrappers/test/ca-session.sh has one pre-existing failing case: 'dispatch prints final session_id status line' — the dispatch path is expected to end by printing a final status line carrying the session/chat id, but the run output ends with a usage event line ({\"kind\":\"usage\",...}) instead. Diagnose whether the defect is in wrappers/ca.sh (it no longer prints the final session status line after the agent stream ends) or in the test's mock/assertions having drifted from the intended contract, and fix the defective side so the CONTRACT documented in the wrapper header stays true: the wrapper must surface the chat/session id of the dispatch so the orchestrator can resume the session later. Do not weaken the assertion to accept missing session ids. Acceptance: bash wrappers/test/ca-session.sh prints PASS with FAIL=0.","files_modify":["wrappers/ca.sh","wrappers/test/ca-session.sh"],"requires_decision":null}
