Skip to content

Arch violation: Pending-* ID resolution is daemon-side state tracking that can create ghost sessions #114

@c-h-

Description

@c-h-

What state is duplicated

The daemon maintains pending-<pid> placeholder session IDs when a launch starts before the adapter's real session ID is known. A 10-second background interval (startPendingResolution) polls adapter.discover() to match PIDs and replace pending IDs with real UUIDs.

Files:

  • session-tracker.ts: resolvePendingSessions(), resolvePendingId(), startPendingResolution()
  • state.ts: pending-* entries stored as SessionRecord in state.json

Where is the ground truth?

The adapter's native session creation. The real session ID is created by the adapter (Claude Code, OpenCode, Codex) when it initializes — agentctl shouldn't need to track the mapping.

How does it desync?

  1. Resolution never completes: If the adapter can't match the PID to a session (race, crash, or PID dies before discovery), the pending-* entry persists indefinitely in state.json. Evidence: ~/.agentctl/opencode-sessions/ currently has 20+ pending-*.json files for dead sessions.
  2. PID recycling during resolution: Between launch and resolution (up to seconds), the PID could theoretically be recycled (unlikely but possible on busy systems).
  3. Lock coupling: Auto-locks reference pending-* IDs. If resolution fails, the lock is stuck until manual cleanup.
  4. Duplicate sessions: If resolution matches wrong PID (adapter has multiple processes for same cwd), both the pending entry and the real session appear in results.

User-visible symptom

  • agentctl list -a shows stale pending-* entries for sessions that died long ago
  • lock list shows locks with pending-* session IDs that can't be auto-cleaned
  • Inconsistent state between list (daemon-enriched) and status (adapter-direct)

Proposed fix

  1. Add TTL to pending entries: If a pending-* isn't resolved within 2 minutes, check PID liveness. If dead, remove it and release associated locks.
  2. Move ID resolution to adapter: The adapter's launch() already polls for the real session ID (e.g., pollForSessionId in claude-code.ts). Increase the poll timeout and have launch() return the real ID synchronously — eliminating the need for background resolution.
  3. If pending must exist: Treat them as write-behind metadata, not as session status records. They should never influence what agentctl list reports.

Related: #110, #112

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions