feat(chat): configurable dialog auto-dismiss window — stop claude's 60s picker timeout from killing manual-run questions #125

Merged
dominik.polakovics merged 1 commit from afk/124 into main 2026-07-11 02:12:44 +02:00

Closes #124

What

Manual sessions no longer lose undriven AskUserQuestion/ExitPlanMode pickers to Claude Code's 60s self-resolve. A new dialog_timeout_minutes setting (typed int, floor 0, deliberately not seeded) controls the window: absent/0 = effectively never (2^31−1 ms ≈ 24.8 days), 1 = upstream 60s parity, N = N minutes. AFK runs are untouched — upstream's unattended auto-advance stays, by design.

Step-1 spike (gate) — both hypotheses confirmed live

Recorded on #124 (2026-07-10, 2.1.198): a --settings file env block with CLAUDE_AFK_TIMEOUT_MS=5000 self-resolved the picker at +5.033s (afkTimeoutMs:5000 in the transcript), and 2147483647 held byte-identical past +92s with zero timeout events. The primary settings-env mechanism ships; the pre-authorized spawn-env fallback was not needed.

How

  • Seam (provider.LiveSignals): Setup(runID, dir, opts SetupOpts)opts.DialogTimeout > 0 makes the claudecode adapter emit "env":{"CLAUDE_AFK_TIMEOUT_MS":"<ms>"} in the same per-run settings file as the §9 hooks, clamped to 2^31−1 ms (JS setTimeout instant-fire hazard). Zero opts → payload byte-identical to before. Codex has no LiveSignals — untouched. CLAUDE_AFK_COUNTDOWN_MS untouched.
  • Policy (internal/instance): dialogTimeout(ctx, kind) resolves the setting for RunKindManual only; both AFK kinds pass zero.
  • Surface: settings PATCH validation (floor 0, unknown-key rejection untouched) + a "Dialog auto-dismiss (minutes)" number field in the Spawn defaults card (per-field min so 0 = never is saveable; hint documents next-spawn semantics).
  • Docs: compat.md §11 pins the env pair, delivery path, cap rationale, the value-scaled timeout message ("No response after 5s" — never string-match "after 60s"), and the no-countdown-UI observation, with §5/§7/§9 cross-refs. Tier-2 live checklist gains the conditional spike #5; ADR-0036 carries a status amendment. (Issue said "§9", but §9/§10 were already occupied — noted on #124.)

Proof

  • go build ./..., go test ./..., gofmt, golangci-lint run — all green/0 issues.
  • web: 593/593 vitest, tsc --noEmit && vite build clean.
  • Hermetic: adapter emits env only when a timeout is passed (AFK file byte-identical), clamp at 2^31−1; launch threading per run kind (never/minutes/zero) through the real Start/Launch paths; settings PATCH floor-0 + not-seeded; Settings UI renders/saves 0 and blocks invalid input.
  • Live end-to-end with the real combined payload: dumped the adapter's actual hooks+env file, drove a throwaway 2.1.198 session — PreToolUse spooled the pending dialog, picker self-resolved ~5s with afkTimeoutMs:5000, PostToolUse cleared the spool. Dialog capture and the timeout override coexist in one file.

🤖 Generated with Claude Code

Closes #124 ## What Manual sessions no longer lose undriven AskUserQuestion/ExitPlanMode pickers to Claude Code's 60s self-resolve. A new `dialog_timeout_minutes` setting (typed int, floor 0, deliberately not seeded) controls the window: absent/0 = effectively never (2^31−1 ms ≈ 24.8 days), `1` = upstream 60s parity, N = N minutes. AFK runs are untouched — upstream's unattended auto-advance stays, by design. ## Step-1 spike (gate) — both hypotheses confirmed live Recorded on #124 (2026-07-10, 2.1.198): a `--settings` file `env` block with `CLAUDE_AFK_TIMEOUT_MS=5000` self-resolved the picker at **+5.033s** (`afkTimeoutMs:5000` in the transcript), and `2147483647` held byte-identical past +92s with zero timeout events. The primary settings-env mechanism ships; the pre-authorized spawn-env fallback was not needed. ## How - **Seam** (`provider.LiveSignals`): `Setup(runID, dir, opts SetupOpts)` — `opts.DialogTimeout > 0` makes the claudecode adapter emit `"env":{"CLAUDE_AFK_TIMEOUT_MS":"<ms>"}` in the same per-run settings file as the §9 hooks, clamped to 2^31−1 ms (JS setTimeout instant-fire hazard). Zero opts → payload byte-identical to before. Codex has no LiveSignals — untouched. `CLAUDE_AFK_COUNTDOWN_MS` untouched. - **Policy** (`internal/instance`): `dialogTimeout(ctx, kind)` resolves the setting for `RunKindManual` only; both AFK kinds pass zero. - **Surface**: settings PATCH validation (floor 0, unknown-key rejection untouched) + a "Dialog auto-dismiss (minutes)" number field in the Spawn defaults card (per-field `min` so 0 = never is saveable; hint documents next-spawn semantics). - **Docs**: compat.md **§11** pins the env pair, delivery path, cap rationale, the value-scaled timeout message ("No response after **5s**" — never string-match "after 60s"), and the no-countdown-UI observation, with §5/§7/§9 cross-refs. Tier-2 live checklist gains the conditional spike #5; ADR-0036 carries a status amendment. (Issue said "§9", but §9/§10 were already occupied — noted on #124.) ## Proof - `go build ./...`, `go test ./...`, `gofmt`, `golangci-lint run` — all green/0 issues. - web: 593/593 vitest, `tsc --noEmit && vite build` clean. - Hermetic: adapter emits env only when a timeout is passed (AFK file byte-identical), clamp at 2^31−1; launch threading per run kind (never/minutes/zero) through the real Start/Launch paths; settings PATCH floor-0 + not-seeded; Settings UI renders/saves 0 and blocks invalid input. - **Live end-to-end with the real combined payload**: dumped the adapter's actual hooks+env file, drove a throwaway 2.1.198 session — PreToolUse spooled the pending dialog, picker self-resolved ~5s with `afkTimeoutMs:5000`, PostToolUse cleared the spool. Dialog capture and the timeout override coexist in one file. 🤖 Generated with [Claude Code](https://claude.com/claude-code)
feat(chat): configurable dialog auto-dismiss window for manual sessions (#124)
All checks were successful
ci / native (pull_request) Successful in 5m15s
f59d272790
Claude Code self-resolves an undriven AskUserQuestion/ExitPlanMode picker
after 60s, which killed manual-run questions once the needs-input push made
the operator's answer a phone round-trip. The window is the undocumented
integer env var CLAUDE_AFK_TIMEOUT_MS (default 60000); the Step-1 live spike
(2026-07-10, 2.1.198, results on #124) confirmed the per-run --settings
file's env block reaches the picker timer and that 2147483647 (2^31-1 ms)
holds with no immediate-fire hazard — so the primary settings-env mechanism
ships and the pre-authorized spawn-env fallback was not needed.

- provider seam: LiveSignals.Setup gains a SetupOpts param carrying
  DialogTimeout; the claudecode adapter emits the env block (clamped to
  2^31-1 ms) next to the §9 hooks; zero opts keep the payload byte-identical
  (codex: no LiveSignals, untouched; CLAUDE_AFK_COUNTDOWN_MS untouched)
- launch policy: instance.dialogTimeout resolves dialog_timeout_minutes for
  RunKindManual only — absent/0 = effectively never (2^31-1 ms), N>0 = N
  minutes; AFK runs pass zero and keep upstream's unattended auto-advance
- settings surface: dialog_timeout_minutes (typed int, floor 0, deliberately
  NOT seeded) with PATCH validation, plus a Spawn-defaults card number field
  (per-field min so 0 = never is saveable)
- compat: new §11 pins the env pair, the settings-env delivery, the 2^31-1
  rationale, the value-scaled timeout message, and the no-countdown-UI
  observation, with §5/§7/§9 cross-refs; Tier-2 live checklist gains the
  conditional dialog-auto-dismiss-defeat spike (ADR-0036 amended)

Verified end-to-end live with the adapter's real combined hooks+env payload:
PreToolUse spooled the pending dialog, the picker self-resolved at ~5s with
afkTimeoutMs:5000, PostToolUse cleared the spool.

Closes #124

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign in to join this conversation.
No reviewers
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Cloonar/coding-lab!125
No description provided.