Public: anyone can read this; the crew can change it.

A finished run must free its slot: close the session's stdin after the final result instead of idling to the 45-minute cap #545

Merged opened this in answer to

Check · Review · Land · Live done — Landed and live in production.

Merged

A finished run now frees its crew slot within seconds of its final `result`, instead of holding it until the 45-minute cap. Per the operator's note, this starts over from today's main and only covers the runner settle, the idle guard and a test. The branch's tree was reset to main with a merge commit (force pushes are refused), so the earlier attempt's board state, migration and `run.finishing` ev…

shipped production is serving it deploy d63cf43

opened this

A finished run now frees its crew slot within seconds of its final result, instead of holding it until the 45-minute cap. Per the operator's note, this starts over from today's main and only covers the runner settle, the idle guard and a test. The branch's tree was reset to main with a merge commit (force pushes are refused), so the earlier attempt's board state, migration and run.finishing event are gone.

Main already closed stdin after a result with nothing pending. Two cases still held a slot, and src/crew/claude.ts now handles both:

  • Settle: after the final result, the session's input is closed and the process gets SETTLE_MS (5 s) to exit. If it is still running, its process group is stopped and the run settles on the result it already gave, as ok / completed, with the reason logged. The stdout/stderr reads now take an abort signal, so a child the agent left holding the pipe no longer keeps the run open after the agent exits.
  • Idle guard: a steer that Claude Code folds into the turn it interrupted leaves the runner waiting for a second result that never comes, with stdin left open. Now, after a result with a steer pending, every output line resets a timer. If the session is silent for IDLE_AFTER_RESULT_MS (5 min) it is closed and settled, and the log says why.
  • A steer after close: send returns false, so the existing rule makes it a comment for the next run. runner.ts and dispatch.ts needed no changes.

test/session-settle.test.ts (added to check.run in forge.json) uses stand-in Claude binaries for five cases:

  • one that ignores its closed stdin and one that exits but leaves a child holding its output — both settle in under 10 s;
  • one that exits on its own — not held for the grace period;
  • one that takes a steer and goes silent — the idle guard closes it, and a later steer returns false;
  • one that answers a steer slowly but keeps writing output — not closed early.

Against main's claude.ts, the three slot-holding cases time out and the two controls pass. The full check (tsc plus all 45 test files, 673 tests) passes locally.

Not done here: the board's "finishing" vs "working" state, which is left for a follow-up issue.

To try it: bun test test/session-settle.test.ts test/steer.test.ts.

🤖 Generated with Claude Code

Closes #474

Sign in to join this conversation.

↑↓ navigate ↵ open ⌘↵ its app esc close

Keyboard shortcuts

Docs