Skip to content

Caller resolution can attribute a call to a dead record on a recycled surface_id #468

Description

@EtanHey

Follow-up from the review of #466 (lane F1), raised by the reviewer as recommended, not blocking. Investigated during the review round and deliberately not fixed — the obvious guard does not work. Filing so the reason is not lost.

The hazard

resolveCurrentCallerAgent (src/server.ts) resolves the calling agent in four tiers:

  1. surface_uuid match, live
  2. surface_id match, live
  3. surface_uuid match (terminal records)
  4. surface_id match (terminal records)

Tier 4 is the exposure. surface_id is a recyclable ref — this repo guards it explicitly elsewhere (the "surface recycled" refusal in deliverAgentInput, src/server.ts:10962). So a dead worker's record whose ref got reused by a new pane, with no live record bound to that ref, now claims to be the caller — and the #378 guard then forces the new pane's children to worker/right off a corpse.

Tiers 1 and 3 (UUID) are unaffected. Tier 4 exists because #408 flips live agents to done; removing it re-breaks U6 for records that carry no surface_uuid.

Why the obvious guard does not work

The natural fix is the same signal deliverAgentInput uses: if the live pane hosts a different, known CLI than the record's, the record is not the caller.

It cannot fire here. registry.listMerged rewrites record.cli from the live pane, so by the time caller resolution runs, a recycled record already claims the new occupant's CLI. Verified directly:

BEFORE  claude      // record as written
AFTER   codex       // after one list_agents call, pane is codex

Shipping that guard would have added protective-looking code that never triggers in the scenario it names.

What a real fix needs

A signal the registry merge does not overwrite. Candidates:

  • surface_observer_id ownership plus record recency (the reviewer's suggestion) — needs a defensible recency window, and it drops caller attribution for records with no observer id, which are exactly the legacy/auto-discovered ones.
  • Persisting the surface UUID at spawn for every managed seat, so tier 4 becomes unnecessary.
  • Recording the CLI observed at registration separately from the merged/live one, so the comparison has an un-rewritten baseline.

An AIDEV-TODO at the call site points here.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions