Chat: pending dialog vanishes from the browser (picker still up in the TUI) and in-progress dialog answers are wiped by refetches #89
Labels
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
Cloonar/coding-lab#89
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?
Operator report: "Sometimes a question dialog disappears but is still in Claude Code. Sometimes I type and the thing I type in the question as answer just disappears and I have to type again."
Diagnosed live (2026-07-10). Two independent root causes, one per symptom.
1. Server: the spool's mtime staleness guard suppresses genuinely pending dialogs
claudecode.PendingDialog(dialogspool.go) treated a dialog spool as stale (rotation, issue #34) whenever the transcript's mtime was newer than the spool's, on the compat §5 premise that the transcript is byte-frozen during a pending window. That premise is false: in the live grill transcriptd4be520a, during the pending window oftoolu_018wW6XQsVmBc4yuscy6fvfz(23:52:54→23:54:06), Claude Code appendedqueue-operation+attachmententries at 23:53:21 — a message queued while the picker was up is written live. From that write on, the server suppressed the still-pending dialog:pending_dialog= null, state fell back to needs_input, the card vanished from the chat (any refetch or browser refresh shows it gone) while the TUI picker stayed up. Bonus hazard: with state no longerquestion, the composer unlocks and a free-text Reply can land in the focused picker.Fix: staleness keyed to the spool payload's own session identity (
session_id, falling back to thetranscript_pathstem) compared against the current transcript's filename stem — exact for the /clear//rewind rotation case, immune to mid-window transcript writes. A payload with no session identity degrades to the old mtime backstop. compat §5/§9 updated with the corrected ground truth.2. Client: dialog input state wiped on every refetch
Every
GET /messagesresponse is a fresh JSON parse, so the same pending dialog arrives as a NEW object each SSE tick. Three compounding effects in RunChat:DialogPanel's reset effect (on(() => props.dialog.tool_id, reset, {defer:true})) — Solid'sondoes not equality-gate, so any upstream write re-ran the reset and wipedselected/otherTextmid-typing;MultiQuestionForm(resetKey) wiped all picked answers;<For>, recreating the rows and the Other<input>element (focus loss under the operator's fingers).Fix:
pendingDialogFieldkeeps its object identity while the sametool_idstays pending (dialog content is immutable per tool_id — the hook spools it once), and both reset effects key on an equality-gatedcreateMemoof the identity. Regression tests assert picks, typed text, and the input element itself survive a same-dialog refetch, and that a real identity change still resets.Repro method (server side): scratch claude 2.1.198 in tmux with lab's exact injected hook settings, spool/transcript mtimes sampled during pending windows — simple and back-to-back questions hold the frozen premise; the queued-message write is what breaks it (recovered from the live grill transcript's mid-window entry timestamps).
PR #90 audit — verdict: PASS
git diff origin/main..branchappears to delete ~1100 lines (ADR-0036 conformance suite,providertest/conformance.go, etc.). Verified this is a stale-base artifact, not a real removal — those files were added tomainby PR #88 after this branch was cut from670195f; the branch simply predates them. The branch's real change (merge-base..head) is exactly the 5 files the body describes:dialogspool.go(+test),RunChat.tsx(+test),compat.md.fix(chat): …✓Closes #89✓main), which is the real post-merge signal the branch CI never saw (main gained the conformance suite after the branch was cut). Results:go build/go vetclean;go testgreen forclaudecode,providertest,provider,compat— main's new conformance suite and this diff coexist (the suite uses a fake provider, never touches the changedPendingDialog).TestPendingDialog_survivesTranscriptWritesDuringPending,TestPendingDialog_staleAfterTranscriptRotation,TestPendingDialog_legacyPayloadFallsBackToMtime.session_id, transcript-stem fallback) instead of transcript mtime — same session served through mid-window writes, rotated session suppressed, identity-less payload degrades to the old mtime backstop. Client change gives the pending dialog stable object identity pertool_idand equality-gates both reset effects. Both map directly to the two root causes in #89.main, no conflicts.No blockers. Awaiting explicit free-text go-ahead to merge.