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:
surface_uuid match, live
surface_id match, live
surface_uuid match (terminal records)
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.
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:surface_uuidmatch, livesurface_idmatch, livesurface_uuidmatch (terminal records)surface_idmatch (terminal records)Tier 4 is the exposure.
surface_idis a recyclable ref — this repo guards it explicitly elsewhere (the"surface recycled"refusal indeliverAgentInput,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 toworker/rightoff 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 nosurface_uuid.Why the obvious guard does not work
The natural fix is the same signal
deliverAgentInputuses: if the live pane hosts a different, known CLI than the record's, the record is not the caller.It cannot fire here.
registry.listMergedrewritesrecord.clifrom the live pane, so by the time caller resolution runs, a recycled record already claims the new occupant's CLI. Verified directly: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_idownership 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.An
AIDEV-TODOat the call site points here.