Skip to content

feat(sessions): a ceiling on waiting for an answer that never comes (v0.293.0) - #536

Merged
vikasprogrammer merged 1 commit into
mainfrom
feat/blocked-ceiling
Aug 3, 2026
Merged

feat(sessions): a ceiling on waiting for an answer that never comes (v0.293.0)#536
vikasprogrammer merged 1 commit into
mainfrom
feat/blocked-ceiling

Conversation

@vikasprogrammer

Copy link
Copy Markdown
Owner

The last thread in this morning's arc. #521 fixed a finished run held open by an unanswered card; this fixes a running one.

The idle janitor deliberately skips a session blocked on a question or approval — that wait is real, and cutting it would throw away work someone is about to unblock. But nothing expires an Inbox card, so the exemption had no floor and the wait could be permanent.

Found on live expresstech while updating it:

session status blocked since still holding a pane
ses_b1ac7cb96d53a72f running 2026-07-31 (66 h) yes
ses_09b95256dcb2ad1e done 2026-07-28
ses_56ac68828a99a366 done 2026-08-03

Each blocked session pins a claude process (~300 MB) and a concurrency-cap slot for as long as nobody looks at the Inbox.

The ceiling

New setting — Settings → Runtime → "Close a session waiting on an unanswered question after (hours)", default 72 h. That default isn't arbitrary: it's STALE_PROMPT_MAX_MS, the age at which escalateStalePrompts already stops nagging and treats a prompt as dead. The two now agree about when a prompt is abandoned.

Past the ceiling the session is closed and its card cancelled — which is also what makes the card dismissable rather than hanging in the Inbox forever.

Three deliberate boundaries:

  • The clock runs from when the oldest card was raised, not from session idleness. The claim being made is "nobody answered this in three days", and a blocked session is quiet by definition, so idleness measures the wrong thing.
  • Attached sessions are never cut. If someone is at the terminal they can answer; the ceiling is for the abandoned case.
  • Interactive lane only. Unattended runs already have unattendedMaxHours (24 h), which overrides a pending block.

Audited as session.reaped with reason: 'blocked-timeout' and how long it waited, so it's distinguishable from an ordinary idle reap when reading the trail. 0 restores the old wait-for-ever.

Tests

6 new assertions in the reaper suite (29/29), covering the cases that actually distinguish this from a blunt timeout:

✓ blocked 90h on a 72h ceiling → closed
✓ …and its question cancelled, so the card is dismissable
✓ blocked only 2h → still waiting
✓ …its question still open
✓ blocked 90h but a human is attached → left alone
✓ a 90h-old pending APPROVAL blocks the same way
✓ 0 → a 150h-old block is never cut

Also fixed a trap in that suite: section 4 pinned aliveNames() to its own two panes, so any session a later section created was marked crashed by the crash sweep before the idle sweep saw it — the new section failed for reasons that had nothing to do with the code under test. Stub restored per section.

159/159 conformance, 18/18 tier-A, 18/18 capability registry. typecheck and both builds clean.

Note on expresstech

Three questions there have been unanswered since 07-28 / 07-31. This stops them holding processes, but somebody should still answer them — the ceiling is a janitor, not a substitute for the Inbox.

🤖 Generated with Claude Code

…v0.293.0)

The idle janitor skips a session blocked on a question or approval — that wait
is real. But nothing expires an Inbox card, so the exemption had no floor: the
wait could be permanent. Live expresstech was holding a `support` session 66h
after its question was asked, with two more unanswered since 07-28; each pins a
claude process (~300MB) and a concurrency-cap slot for as long as nobody looks.

New setting (Settings → Runtime, default 72h — the age at which
escalateStalePrompts already stops nagging and treats a prompt as dead): past
it, the session is closed and its card cancelled, which is also what makes the
card dismissable instead of hanging. 0 restores wait-for-ever.

- Clock runs from when the OLDEST pending card was RAISED, not from session
  idleness: the claim is "nobody answered this in three days", and a blocked
  session is quiet by definition.
- A session with someone ATTACHED is never cut — a human is right there.
- Interactive lane only; unattended runs already have unattendedMaxHours (24h),
  which overrides a pending block.
- Audited as session.reaped reason:'blocked-timeout' with the wait duration, so
  it's distinguishable from an ordinary idle reap.

6 new reaper assertions (29/29): old block reaped + card cancelled, fresh block
kept, attached kept, approvals counted like questions, and 0 = never.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@vikasprogrammer
vikasprogrammer merged commit 4063a96 into main Aug 3, 2026
1 check passed
@vikasprogrammer
vikasprogrammer deleted the feat/blocked-ceiling branch August 3, 2026 08:06
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