fix: background tabs survive updates — immediate re-escrow + replay head repair - #310
Merged
Conversation
…e the next update A session revived at restore only re-escrowed when its runtime surface was created, and surface creation is deferred until the tab's view is in a window (TerminalSurface.attachSurface: view.window == nil -> defer). A revived panel in a tab never shown during that app run held its child's only pty fd in app memory; the next update closed it and SIGHUPed the agent. Production diagnostics show it exactly: the 2026-08-19 21:05 relaunch revived 8/8 sessions, the 2026-08-20 11:26 relaunch revived 1/8 — the other 7 fell back not_escrowed, and their session ids are precisely the panel ids minted by the previous night's revival. One update of protection, then death. Three changes: - TerminalSurface escrows the revive descriptor's fd at construction (dup + hand to the holder), not at realization. hasAttemptedSessionEscrow keeps the realization-path escrow one-shot. - SessionWALStore.stampDeferredReviveEscrow records the escrow facts (escrowed/socketPath/token/childPID) before the WAL writer's full registration exists, creating the session dir + meta.json if needed — reattach's guard requires all four fields. - startWriter hydrates durable meta facts from an existing meta.json instead of clobbering them, so the eventual full registration (tab shown later, or runtime-surface recreation) preserves what the stamp wrote. This also fixes a latent clobber: any surface recreation previously wiped escrow state from meta.json. Observability: escrow.reattach early_reescrow outcome=ok/failed in the release diagnostics log.
The WAL byte stream can begin mid-escape-sequence — log rotation and ring
overruns cut at byte boundaries. When the cut lands inside a CSI sequence the
surviving tail ("38;114m") has no ESC byte, so positioningSanitizedText and
the ANSI-safe truncation (which only guards its own length-cap cut, and only
runs above the cap) both pass it through, and it renders literally at the head
of every fallback-restored terminal — the torn rendering in today's
update-reset report. preparedText now strips a bare parameter-tail head
(requires a ; or ? in the fragment so prose like "1m 30s" and "42x42"
survives) before the rest of the pipeline.
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.
What this does
Fixes this morning's report: after a Programa update, every terminal except the active one came back reset with dead agents, and the reset tabs rendered torn scrollback with literal escape-sequence fragments. Background tabs now keep their live processes across updates, and fallback replays no longer print corrupt heads.
Summary
not_escrowedfallbacks are precisely the panel ids minted by the previous revival.TerminalSurfaceescrows the revive descriptor's fd at construction;SessionWALStore.stampDeferredReviveEscrowrecords the escrow facts before full WAL registration;startWriterhydrates durable meta facts from disk instead of clobbering them (also fixes a latent clobber on runtime-surface recreation).preparedTextstrips orphaned CSI parameter tails (38;114m) from the replay head — WAL rotation/ring cuts can remove the ESC prefix, making the fragment invisible to every existing sanitizer.escrow.reattach early_reescrow outcome=ok/failedin the release diagnostics log — the next update on a dogfooding machine proves the fix from the log alone.Review order: commit 1 (
TerminalSurface/SessionWALStore), then commit 2 (replay head repair, self-contained).Test Plan
SessionWALDeferredReviveEscrowTests,ScrollbackSeedOrphanedHeadTestsoutcome=revivedfor all tabs in~/Library/Logs/Programa/diagnostics.logNN;NNmfragments)