Skip to content

Persist claude session id only after the headless turn finishes - #116

Merged
bborbe merged 5 commits into
masterfrom
fix/resume-waits-for-turn
Aug 28, 2026
Merged

Persist claude session id only after the headless turn finishes#116
bborbe merged 5 commits into
masterfrom
fix/resume-waits-for-turn

Conversation

@bborbe

@bborbe bborbe commented Aug 28, 2026

Copy link
Copy Markdown
Owner

Problem

v0.116.3/0.116.4 made the non-interactive work-on branch return within a ~10s liveness window while the detached headless turn kept running, and persisted claude_session_id before spawning the child.

That fixed the Vault UI Start timeout but shipped a worse bug in its place. The UI keys its Resume button off the mere presence of claude_session_id, so the button flipped to Resume within ~10s — while the transcript was still being written. Clicking it and running the offered claude --resume <id> gave "session not found", then a few seconds later replayed only the first ~10s of work, then more on each reopen. Two writers could also land on the same jsonl.

The root confusion: an id on frontmatter meant "a session exists", not "a session is resumable".

Fix

Invert the ordering. The id is persisted only after the child has exited and its turn JSON validates. An id on disk now means Resume will work.

  • StartSession's non-interactive branch blocks on the child's Wait, bounded by sessionTurnTimeout (30m).
  • The bound is a wait bound, never a kill — the child stays detached in its own process group (exec.Command, Setpgid) and survives expiry; the parent just stops waiting.
  • Every non-clean outcome (child exit error, invalid/zero-turn JSON, bound expiry, ctx cancel) returns an error, so nothing is persisted and the UI correctly keeps showing Start.
  • The child's stdout goes to a caller-owned temp file, not a pipe — no pipe-buffer deadlock, no EPIPE, and the file is complete once Wait returns. Unlinked eagerly, so no path leaks it.
  • --output-format json validation (validateSessionTurn) is now shared by both branches and load-bearing: claude reports a session_id even for a failed or zero-turn session, so exit code alone is not enough.
  • clearSessionAndMetrics / clearGoalSession deleted — with post-exit ordering nothing is pre-written, so there is nothing to compensate.

Also fixed, found by a test while doing the above: persistSessionAndMetrics / persistGoalSessionID returned the session id even when the re-read or write failed. Nothing had landed on disk, so that reported an id the UI could not resume — the same lie one layer up. Both now return an empty id on failure.

Not changed

The interactive TTY branch keeps its blocking behaviour and 5m cap. Turn 2 syscall.Execs claude --resume against turn 1's on-disk result, so blocking is exactly what makes that handoff correct. scenarios/005 regression-locks it.

Verification

  • make precommit passes; pkg/ops is 833 specs green.
  • scenarios/002 timing note inverted: a fast return is now a FAIL, not a pass.
  • Detachment integration test rewritten for the new contract — the parent's wait is cancelled mid-turn and the child still runs to completion.
  • Live claude --resume verification against a completed turn is pending post-release; its absence is what let the previous bug ship.

bborbe added 5 commits August 28, 2026 10:15
Spec 040 shipped the non-interactive branch persisting claude_session_id
before spawn + returning after a 10s liveness window; the Vault UI flips to
Resume while the turn still runs, so resume fails (session not found, partial
replay, two writers on one transcript).

Inverts the non-interactive design: block until the detached child exits
(bounded by sessionTurnTimeout = 30 min, never a kill), capture stdout to a
temp file and validate the JSON (shared helper with the interactive branch),
and only then persist the id. Interactive branch + scenarios/005 untouched.
@bborbe
bborbe merged commit e060c01 into master Aug 28, 2026
2 checks passed
@bborbe
bborbe deleted the fix/resume-waits-for-turn branch August 28, 2026 11:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant