You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
state.json stores a full SessionRecord for every daemon-launched session, including: status, startedAt, stoppedAt, adapter, cwd, model, prompt, tokens, cost, pid, group, and meta. This is a complete shadow copy of session state.
Adapter timeout/failure: If adapter.discover() times out during session.list, reconcileAndEnrich() may mark daemon-tracked sessions as stopped because the adapter "didn't return them" — even though the adapter just failed.
Grace period gaps: The 30s LAUNCH_GRACE_PERIOD_MS creates a window where session status is ambiguous — the daemon is guessing based on timing rather than asking the adapter.
Stale accumulation: Stopped sessions remain in state.json indefinitely (no TTL/eviction), growing the file and adding noise.
User-visible symptom
agentctl list and agentctl status report sessions as "stopped" while the actual coding agent process is alive and producing work. This is the trigger bug (#109).
Proposed fix
Phase 1 (minimal): Remove status from SessionRecord. The daemon should only store launch metadata (prompt, group, spec, hooks) — information the adapters don't have. Status must always come from adapter.discover() or PID liveness checks at query time.
Phase 2 (full): Eliminate the sessions record from state.json entirely. Move launch-only metadata (prompt, group, spec) to a separate lightweight structure with automatic TTL eviction. Status determination flows exclusively through adapters.
What state is duplicated
state.jsonstores a fullSessionRecordfor every daemon-launched session, including:status,startedAt,stoppedAt,adapter,cwd,model,prompt,tokens,cost,pid,group, andmeta. This is a complete shadow copy of session state.File:
src/daemon/state.ts—PersistedState.sessions: Record<string, SessionRecord>Written by:
src/daemon/session-tracker.ts—track(),onSessionExit(),reconcileAndEnrich(),cleanupDeadLaunches()Where is the ground truth?
~/.claude/projects/*/(JSONL session files),~/.claude/history.jsonl~/.local/share/opencode/storage/session/(JSON files per session)~/.codex/sessions/(JSONL session files)kill(pid, 0),ps aux,lsofHow does it desync?
onSessionExit()fires and marks the sessionstoppedin state.json — but the detached child process is still alive and working. This is the root cause of Bug: Session status shows 'stopped' while Claude Code process is still running #109.adapter.discover()times out duringsession.list,reconcileAndEnrich()may mark daemon-tracked sessions as stopped because the adapter "didn't return them" — even though the adapter just failed.LAUNCH_GRACE_PERIOD_MScreates a window where session status is ambiguous — the daemon is guessing based on timing rather than asking the adapter.User-visible symptom
agentctl listandagentctl statusreport sessions as "stopped" while the actual coding agent process is alive and producing work. This is the trigger bug (#109).Proposed fix
Phase 1 (minimal): Remove
statusfromSessionRecord. The daemon should only store launch metadata (prompt, group, spec, hooks) — information the adapters don't have. Status must always come fromadapter.discover()or PID liveness checks at query time.Phase 2 (full): Eliminate the sessions record from state.json entirely. Move launch-only metadata (prompt, group, spec) to a separate lightweight structure with automatic TTL eviction. Status determination flows exclusively through adapters.
Related: #109