Repository navigation
attach: follow a tab onto the background job it parked its work on - #7
Merged
Merged
Conversation
A pane pinned at 08:51 was still drawing its tab's idle row at 14:44, while three agents ran under a session it had never heard of. The tab had parked its work on a background job, and the pane was pinned to the tab. The registry says so plainly. The interactive row keeps its sessionId, goes status:"idle" and grows parkedJobId:"e9e4ae8e"; a second row with kind:"bg" and jobId:"e9e4ae8e" runs the work under a different session id. parseFile read six fields and none of them were these, so the pane had no way to know the hop had happened. Everything downstream then behaved correctly about the wrong session: the tab really was idle, the 27 suppressed agents really were its own from hours earlier, and the hook payloads carrying the live work went to the job's socket, which nobody was listening on. registry.Follow walks parkedJobId to the row whose jobId matches it. The walk is by job id because that is the only field the two rows share. It stops on a row that parks nothing, on a job whose row is missing (the process died between registry writes), on a repeat, and after four hops, so a registry shape we do not understand cannot spin the poll loop. waitForPinned becomes followSession, which is the same loop doing one more job. It no longer returns after the first attach, because parking happens mid-session: resolving this once at startup would have fixed nothing about the pane in question, which was correct when it opened and wrong an hour later. It follows back when the work returns to the tab. A base row going missing is still not a reason to re-resolve anything. The world has to be re-keyed on the hop or the fix does nothing visible. The events now arriving carry the job's session id and Machine.Accepts drops every one of them against the tab's. syncAttached resets rather than merges, because the two sessions are different worlds and the agents of the one that went away are not this one's, and it toasts, because a tree that empties itself with no explanation reads as a bug. Three decisions worth naming. The state file and run history stay filed under the TAB's id. A pinned pane's --oneline reads current-<pin>.json and the idle screen's LAST RUN is that tab's work; following a parked job moves the world, not the name it is filed under. So the attacher now carries a base (the tab) separately from what it is attached to (wherever the work is). The registry filter follows the attach, not the pin. Filtering to the pin after a hop would drop the rows the pane is now watching for, including the job's permission waits, which is the one signal the registry exists to carry. A spare bg process is skipped by the cwd guess and still reachable by a park. It is a warmed shell holding no work, idle by construction, and picking it would attach the pane to the one session guaranteed to show nothing. But a tab that parks onto it has STATED where its work went, and a stated hop beats a guess about what the row looked like a moment ago. The header names both ids while following (10e3 -> e9e4) and ? spells it out, because "which session am I looking at" has two honest answers here and hiding either one is how this went unnoticed for a whole session. doctor reports the hop and checks the job's socket rather than the tab's. Unverified, and stated in DATA-SOURCES §2.1 with the captured row shapes: whether `spare` is cleared when a spare process is handed a job. If it is not, a spare that gets work while still flagged stays out of the cwd guess. A park onto it still resolves.
simoncoombes
force-pushed
the
follow-parked-session
branch
from
September 15, 2026 21:15
adf7b6a to
6394748
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A pane pinned at 08:51 was still drawing its tab's idle row at 14:44, while three agents ran under a session it had never heard of. The tab had parked its work on a background job, and the pane was pinned to the tab.
What the registry says
The interactive row keeps its
sessionId, goesstatus:"idle"and growsparkedJobId; a second row withkind:"bg"and a matchingjobIdruns the work under a different session id.{"pid":18643,"sessionId":"10e378ef-…","kind":"interactive","status":"idle","parkedJobId":"e9e4ae8e"} {"pid":26106,"sessionId":"e9e4ae8e-…","kind":"bg","status":"busy","jobId":"e9e4ae8e"}parseFileread six fields and none of them were these, so the pane had no way to know the hop had happened. Everything downstream then behaved correctly about the wrong session: the tab really was idle, the 27 suppressed agents really were its own from hours earlier, and the hook payloads carrying the live work went to the job's socket, which nobody was listening on.The fix
registry.FollowwalksparkedJobIdto the row whosejobIdmatches it, by job id because that is the only field the two rows share. It stops on a row that parks nothing, on a missing row, on a repeat, and after four hops.waitForPinnedbecomesfollowSession. It no longer returns after the first attach, because parking happens mid-session: resolving this once at startup would have fixed nothing about the pane in question, which was correct when it opened and wrong an hour later. It follows back when the work returns to the tab.The world is re-keyed on the hop, or the fix does nothing visible: the events now arriving carry the job's session id and
Machine.Acceptsdrops every one of them against the tab's. It resets rather than merges, and toasts, because a tree that empties itself with no explanation reads as a bug.Decisions worth a look in review
--onelinereadscurrent-<pin>.jsonand the idle screen's LAST RUN is that tab's work. The attacher carries a base (the tab) separately from what it is attached to.sparebg process is skipped by the cwd guess and still reachable by a park. It is a warmed shell holding no work, but a tab that parks onto it has stated where its work went.Surfaces
The header names both ids while following (
AGENTS 3 · 10e3 → e9e4) and?spells it out.doctorreports the hop and checks the job's socket rather than the tab's.Tests
Follow(walk, missing row, stale duplicate, cycle),parseFileon the captured row shapes,AttachTargetskipping spares,resolveAttachon a parked pin,followSessionhopping to the job and back, and the world re-key. Green under-race. The threecodexsrcTestRealCodexSession*failures predate this branch and fail onmaintoo.Unverified
Whether
spareis cleared when a spare process is handed a job. If it is not, a spare that gets work while still flagged stays out of the cwd guess; a park onto it still resolves. Documented with the captured row shapes indocs/DATA-SOURCES.md§2.1.