feat(chat): configurable dialog auto-dismiss window — stop claude's 60s picker timeout from killing manual-run questions #124
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab#124
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Problem
Claude Code (2.1.198) self-resolves an undriven AskUserQuestion/ExitPlanMode picker after 60s with
answers:{}+afkTimeoutMs:60000("No response after 60s — the user may be away from keyboard. Proceed using your best judgment…"). Already pinned in compat.md §5/§7 (live-verified 2026-07-08). Since the needs-input push (#99) landed, the operator's answer path is a phone round-trip that routinely exceeds 60s, so questions in manual sessions are gone before the operator arrives — observed live 2026-07-10: 5 of 15 grill questions self-resolved at exactly +60s (sessions aa8e1ff3, c3c51f00). An answer sent after the timeout 409s on the tool_id re-read.The timeout is overridable: the CLI reads integer env vars
CLAUDE_AFK_TIMEOUT_MS(default 60000, bundle constantlZe) andCLAUDE_AFK_COUNTDOWN_MS(default 20000). Undocumented upstream → fragile coupling, compat discipline applies.Decisions (grill 2026-07-10)
CLAUDE_AFK_TIMEOUT_MS=2147483647(2^31−1 ms ≈ 24.8 days — see hazard below). In a manual session auto-advance has no beneficiary; a picker should wait like a normal terminal.dialog_timeout_minutes, integer minutes, min 0, not seeded (absent = 0 = never auto-dismiss).1restores upstream's 60s. Number field in the Spawn defaults card: label "Dialog auto-dismiss (minutes)", hint: 0 = never; applies to manual sessions at next spawn (running sessions keep their spawn-time value). Follows the typed-int settings precedent (afk_budget_minutesetc., PATCH validation with min 0).provider.LiveSignals.Setupwith an opts param (e.g.Setup(runID, dir string, opts SetupOpts)) carrying the resolved dialog timeout for manual runs; the claudecode adapter emits"env": {"CLAUDE_AFK_TIMEOUT_MS": "<ms>"}in the per-run--settingsfile it already writes (dialogspool.go), next to the hook couplings.internal/instance/launch.go(armDialogHooks) resolves the setting and passes opts. AFK runs pass no timeout → no env entry → upstream default. Codex: opts ignored (no equivalent knob).CLAUDE_AFK_COUNTDOWN_MSstays untouched (TUI display only).Step 1 — live spike (do this FIRST)
Two upstream behaviors are unverified; prove both with the tmux dialog rig (compat live-recipes precedent, #64) before wiring anything:
--settingsfile whose env block setsCLAUDE_AFK_TIMEOUT_MS=5000, open an AskUserQuestion picker, verify it self-resolves at ~5s (transcriptafkTimeoutMs:5000).2147483647, verify the picker does NOT fire early (JS setTimeout above 2^31−1 fires immediately — if claude computes a deadline instead, huge values are safe; the cap keeps us under the hazard either way). Hold the picker well past 60s to prove the default is defeated.Pre-authorized fallback: if settings.env does NOT reach the timer, pivot to spawn env: a small provider env-contribution seam (e.g. optional
SpawnEnv(spec) []stringapplied in instance spawnEnv →tmux new-session -e), keeping the claude-named variable inside the adapter. No round-trip to the operator needed; record the spike result on this issue either way.Also required
dialog_timeout_minutes(min 0, unknown-key rejection untouched); Settings.tsx renders + saves the field.Acceptance
dialog_timeout_minutes=1: picker self-resolves at ~60s (upstream parity via our env).Step-1 live spike results (2026-07-10, claude-code 2.1.198) — both hypotheses CONFIRMED
Rig: tmux private socket, throwaway scratch dirs,
claude --model haiku --settings <file>, trust dialog accepted interactively, picker opened via an AskUserQuestion prompt, transcript = ground truth (~/.claude/projects/<slug>/<sessionId>.jsonl).H1 — settings.env reaches the picker timer: CONFIRMED. Settings file
{"env":{"CLAUDE_AFK_TIMEOUT_MS":"5000"}}; the undriven picker self-resolved +5.033s after the tool_use (tool_use2026-07-10T20:41:22.287Z→ timeout event20:41:27.320Z). Transcript timeout event verbatim (trimmed):toolUseResult = {"questions":[…],"answers":{},"annotations":{},"afkTimeoutMs":5000}, tool_result content: "No response after 5s — the user may be away from keyboard. Proceed using your best judgment based on the context so far; you can re-ask this question later if it's still relevant."H2 — 2147483647 holds: CONFIRMED. Settings file
{"env":{"CLAUDE_AFK_TIMEOUT_MS":"2147483647"}}; picker still rendered, byte-identical pane captures at +30s/+62s/+92s, transcript contains zeroafkTimeoutMslines. No immediate-fire 2^31 hazard on 2.1.198, and the 60s default is defeated well past a minute.Extra pins for compat:
afkTimeoutMsechoes the effective ms value (5000, not 60000). lab's readers (dialogintent.go,dialogoutcome.go) check> 0/answers:{}, never== 60000— verified safe, no change needed.Consequence: the primary mechanism ships —
CLAUDE_AFK_TIMEOUT_MSin the per-run--settingsfile's"env"block (same file as the §9 hooks), emitted by the claudecode adapter behindprovider.LiveSignals.Setupopts. The pre-authorized spawn-env fallback is not needed.One correction to the issue text: compat.md §9 is already occupied (dialog-capture hook contract) and §10 exists too — the new pin lands as §11, with cross-refs from the §5/§7 timeout bullets.
Landing audit — PR #125 → PASS.
ci / native (pull_request)success (5m15s, run #129). Relied on; not re-run.mainis an ancestor ofafk/124— no conflict, clean merge.feat(chat): …).afk/124, validCloses #124.SetupOpts.DialogTimeout; AFK passes 0 → settings file byte-identical to hooks-only payload.dialogTimeout(): manual =dialog_timeout_minutes(0/absent = effectively-never2^31−1 ms), AFK = 0. Never-sentinel matches the adapter clampmaxDialogTimeout; no overflow.CLAUDE_AFK_TIMEOUT_MSto2^31−1 ms(JS setTimeout instant-fire hazard), rationale pinned at the clamp + compat §11.Verdict: PASS. Awaiting free-text merge go-ahead.