fix(ask-user): restore dangling questions on startup opens
Fixes #4764
Bug
A pending ask_user_question (waitForAnswer: true) killed mid-wait is re-asked when a running client resumes the session with RPC switch_session, but NOT when a restarted CLI opens the same session file with --session <file> — the path a user most often takes after a crash.
Root cause
The CLI's initial runtime passes no explicit session_start event, so it defaults to reason startup (packages/coding-agent/src/core/agent-session.ts). resumeDanglingQuestion (packages/coding-agent/src/core/extensions/builtin/ask-user/resume.ts) returned early unless the reason was resume or reload, so the restore never ran on the CLI restart path.
Change
resume.ts: also run on reasonstartup. This is safe: a fresh session has no branch entries, sofindDanglingQuestionsfinds nothing there, while a reopened file restores exactly like theswitch_sessionpath (same resumed-entry guard prevents a second re-ask).--continue/--resumepickers that open an existing file go through the same initial runtime, so they are covered too.resume.ts: diagnostic sizes-only startup (sizesOnly: true, which starts no turn) still restores nothing, matching the early returns in the other builtins.test/suite/ask-user-resume.test.ts: regression test re-presenting a dangling question once onstartup(mirrors the existingresumecase, including the no-double-ask second start), plus a sizes-only startup test asserting no restore. The new tests live in the existing owner file rather thansuite/regressions/because the dangling-session harness (danglingSession/install/emitStart) is local to that file; happy to move them if preferred.
Local tests (real runs)
- New regression test run against pre-fix code first: FAILED as expected (question called 0 times on startup).
- After the fix:
test/suite/ask-user-resume.test.ts— 13 passed (11 pre-existing + 2 new). - Related:
test/suite/ask-user-extension.test.ts+test/characterization/crash-recovery-today.test.ts— 22 passed. oxlinton both changed files: 0 warnings, 0 errors.bun fmt --checkon both files: clean.
Not run (left for CI / follow-up)
- Full
bun run checkand thesenpi-qareal-CLI channel were not run in this session; CI covers them. - Issue #4764 also mentions updating the X13 characterization test and the EQ E25 golden together with the fix. E25 drives the pending-question kill through the
switch_sessionpath only, so its assertions are unaffected by this change; a real-CLI kill test that reopens with--session(startup path) is still missing and is follow-up, not claimed here.
Summary by cubic
Restores pending ask_user_question prompts when a restarted CLI opens an existing session file, since those opens arrive with session_start reason startup and the restore logic previously handled only resume and reload. Running restore on startup is safe because a fresh session has no branch entries, and diagnostic sizesOnly startup restores nothing since no turn starts.
The follow-up also makes headless startup skip restore entirely: print mode (-p / --mode json) has no question UI, so restoring there would settle the question as orphaned-after-restart, start an unasked model turn, and write the ask-user:resumed marker, blocking a later interactive re-ask. A CLI --fork <file> also arrives as startup with copied branch entries, so it re-presents the source's dangling question (unlike runtime /fork, excluded); the event carries no signal to tell a forked startup from a plain restart, so this divergence is documented, not filtered.
Bug Fixes
- Includes
startupin the reason guard inresume.ts, skips whensizesOnlyis set, and skipsstartuprestores without a UI. - Adds regression tests for startup restore, sizes-only no-restore on startup and resume, and headless no-restore.
- Adds a changelog fragment documenting the fix and its rationale.