Skip to content

Daemon-replace path can kill the calling command: zero-action runs after 'Replacing daemon … unreachable' + SESSION_NOT_FOUND #1596

Closed as not planned

Description

@thymikee

Observed in the AppControlBench Element re-analysis (0.17.6 transcripts): three gpt_high runs (element-17, element-20, element-29) died with zero or near-zero agent actions immediately after this pair:

Replacing daemon (pid 72475, v0.17.6) in /Users/…/.agent-device/…: unreachable
Error (SESSION_NOT_FOUND): iOS snapshot requires an active app session on the target device.

The opencode transcript then ends with a zero-token step_finish reason:"other" — the driving process stopped mid-task with no further tool calls, no final message. element-17 died at 6 tool calls / 16.3s, element-20 at 4 calls / 10.0s (before ever calling open), element-29 got further but hit the same zero-token cutoff mid-task.

Two candidate mechanisms to investigate:

  1. The daemon replace/takeover path (unreachable daemon → replace) leaves the CLI hanging or exiting in a way that kills/wedges the calling shell's process group.
  2. SESSION_NOT_FOUND immediately after a replace is expected (fresh daemon has no sessions), but the combination may be emitted in a way that crashes the caller rather than returning the error.

Wanted: root cause + regression test. The fix should guarantee that a daemon replacement during a command returns a normal structured error (with a hint to open first) and never terminates or wedges the invoking process.

Evidence transcripts: AppControlBench data gpt_high__agent-device/element-{17,20,29}/transcript.jsonl.

Activity

  1. thymikee commented on Aug 5, 2026

    @thymikee
    MemberAuthor

    Root-caused against the archived 0.17.6 transcripts (bench repo git HEAD). The headline mechanism — daemon replace killing/wedging the calling process — is refuted by the evidence; the investigation did surface a real defect on the suspected code path, fixed in #1621. Details:

    What the transcripts show

    1. The driver exited cleanly, it was not killed. All three runs (element-17/20/29) have returncode: 0 in meta.json. The final transcript event is a step-finish with all-zero token usage (input: 0, output: 0, reasoning: 0) and reason: "other" — the model request was never billed, i.e. it failed or was aborted client-side, and opencode ended the run quietly with exit 0.

    2. The daemon-replace correlation is mostly incidental. Across the whole published dataset, 68 runs contain a Replacing daemon notice; 65 of them continued normally — including straight through the exact Replacing daemon … unreachable + SESSION_NOT_FOUND pair (element-17 itself ran three more successful commands after that pair before dying). There is also a fourth death with the same zero-token signature and no daemon replace at all: element-13 died 1.2s after a perfectly successful press (Tapped @e16 (59, 328)), and element-29 after a routine help keyboard.

    3. All four deaths are confined to the gpt_high cell. Zero deaths in gpt_low / haiku_high / haiku_low. The common factor is the model config, not the tool: an unretried provider/driver-level failure in the gpt_high (reasoning) cell, striking at arbitrary moments — element runs just replace daemons so often (19 of the gpt_high element runs) that 3 of 4 deaths landed near one.

    4. Mechanism 2 from the issue is expected behavior: SESSION_NOT_FOUND right after a replace is a fresh daemon with no sessions; the error returns normally and models recover from it routinely (65/68 runs).

    What the audit did find (→ #1621)

    The suspected code path hides a real hazard, just not the one that killed these runs: cleanupLeasedRunnerProcesses kills lease.runnerPid from the on-disk lease file raw — kill(-pid) + kill(pid) + pkill -P — with no identity verification, while the lease owner pid does get a start-time check. A SIGKILLed daemon leaves its lease behind; after a pid-space wrap (observed on this machine within hours under bench-like process churn) the stale-lease cleanup would tree-kill an innocent process group. That can produce exactly the killed-caller shape this issue hypothesized — it just demonstrably didn't in these three runs. #1621 adds runnerStartTime to the lease and refuses to signal any pid it cannot prove is still the leased runner.

    Suggest keeping this issue open only if we want to chase the opencode/gpt_high zero-token abort upstream; on the agent-device side, #1621 covers the actionable part.

  2. thymikee commented on Aug 6, 2026

    @thymikee
    MemberAuthor

    Closing because the archived evidence refuted this issue's reported agent-device mechanism: the driver exited cleanly, 65/68 runs continued through the same daemon-replace sequence, and an equivalent zero-token abort occurred without daemon replacement. The actionable adjacent stale-lease PID-reuse hazard found during the audit was fixed and merged in #1621. Any remaining zero-token abort investigation belongs upstream in the driver/provider rather than this repository.

  3. thymikee commented on Aug 6, 2026

    @thymikee
    MemberAuthor

    Correction with the literal model-cell name: the remaining zero-token gpt_high abort investigation belongs upstream in the driver/provider rather than this repository. The agent-device stale-lease PID-reuse hazard discovered during the audit was fixed in merged PR #1621.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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