Skip to content

Arch violation: Daemon state.json maintains full session registry that shadows adapter ground truth #110

@c-h-

Description

@c-h-

What state is duplicated

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.

File: src/daemon/state.tsPersistedState.sessions: Record<string, SessionRecord>
Written by: src/daemon/session-tracker.tstrack(), onSessionExit(), reconcileAndEnrich(), cleanupDeadLaunches()

Where is the ground truth?

  • Claude Code: ~/.claude/projects/*/ (JSONL session files), ~/.claude/history.jsonl
  • OpenCode: ~/.local/share/opencode/storage/session/ (JSON files per session)
  • Codex: ~/.codex/sessions/ (JSONL session files)
  • Process state: PID liveness via kill(pid, 0), ps aux, lsof

How does it desync?

  1. Wrapper SIGTERM: When OpenClaw's exec timeout kills the launch wrapper, onSessionExit() fires and marks the session stopped in 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.
  2. 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.
  3. 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.
  4. 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.

Related: #109

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions