From 5091bbd3f1b104510b76545800d9705f502411ea Mon Sep 17 00:00:00 2001 From: Jack Rothrock Date: Wed, 9 Sep 2026 12:32:11 -0600 Subject: [PATCH 1/2] test(capture): account for the injected conv_meta frame MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit TestCaptureSendRecordsAndPassesThrough asserted delivered==6, but the capturer legitimately injects a one-shot conv_meta frame (carrying the conv id/uuid to the UI) when it first creates the conversation row, so 7 frames reach the browser. The test predates that frame. Assert the 6 sent frames + exactly one conv_meta. Pre-existing failure — CI only runs Playwright e2e, never go test, so it went unnoticed on main. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/dashboard/conv_capture_test.go | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/internal/dashboard/conv_capture_test.go b/internal/dashboard/conv_capture_test.go index f8337674..269da9a5 100644 --- a/internal/dashboard/conv_capture_test.go +++ b/internal/dashboard/conv_capture_test.go @@ -47,9 +47,20 @@ func TestCaptureSendRecordsAndPassesThrough(t *testing.T) { } finalize("done") - // Every frame reached the browser, in order. - if len(delivered) != len(frames) { - t.Fatalf("delivered %d frames, want %d", len(delivered), len(frames)) + // Every frame reached the browser, PLUS the one-shot conv_meta frame the + // capturer injects when it first creates the conversation row (it carries the + // conv id/uuid to the UI). So delivered = the 6 sent frames + 1 conv_meta. + var metaCount int + for _, m := range delivered { + if m.Type == "conv_meta" { + metaCount++ + } + } + if metaCount != 1 { + t.Fatalf("expected exactly 1 injected conv_meta frame, got %d", metaCount) + } + if len(delivered) != len(frames)+1 { + t.Fatalf("delivered %d frames, want %d (6 sent + 1 conv_meta)", len(delivered), len(frames)+1) } // The conversation + messages persisted. Prompt(user) + text + tool_use + From fd757c16698f19cb8072f8bf436bd88b6aad5e2f Mon Sep 17 00:00:00 2001 From: Jack Rothrock Date: Wed, 9 Sep 2026 12:32:11 -0600 Subject: [PATCH 2/2] fix(conductor): stop AskUserQuestion silently vanishing; force corral-question MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The global-chat conductor is a headless `claude -p` process. It had two ways to ask the user a question: the native AskUserQuestion tool and the `corral-question` fenced convention. The old guidance only *suggested* the convention ("you MAY"), so the model often reached for the native tool instead — which is auto-DISMISSED in headless mode (no interactive channel to answer over the stream). The question then silently vanished: the user saw nothing and the model got an empty "dismissed" result. It was intermittent because the model sometimes picked the answerable convention (which worked) and sometimes the native tool (which didn't). Fix, both halves needed (verified empirically against claude 2.1.186): - Deny the native AskUserQuestion for the conductor via --disallowedTools, removing the silent-vanish path. - Rewrite chatQuestionGuidance from permissive to imperative: "you do NOT have an interactive question tool; the ONLY way to ask is a corral-question block" — so the model routes to the answerable path instead of dead-ending on the denied tool. buildClaudeArgs gains a `disallowed` param; only the conductor (global chat) passes AskUserQuestion. Project/worker chats are unaffected. Co-Authored-By: Claude Opus 4.8 (1M context) --- internal/dashboard/chat.go | 29 +++++++++++++++++++----- internal/dashboard/chat_args_test.go | 23 +++++++++++++++---- internal/dashboard/chat_skill_test.go | 4 ++-- internal/dashboard/worker_resume_test.go | 2 +- 4 files changed, 45 insertions(+), 13 deletions(-) diff --git a/internal/dashboard/chat.go b/internal/dashboard/chat.go index 1fea6247..41d10534 100644 --- a/internal/dashboard/chat.go +++ b/internal/dashboard/chat.go @@ -68,10 +68,12 @@ func chatReposContext() string { // // Injected on the first turn only (like the context hint); later turns inherit it // via --resume. Kept short and optional so it never forces a question. -const chatQuestionGuidance = "When a choice is genuinely ambiguous and a quick decision from the user would " + - "unblock you, you MAY ask with a fenced `corral-question` block instead of prose — the UI renders the " + - "options as one-click buttons. Use it sparingly (skip it when you can reasonably proceed). Format:\n" + - "```corral-question\nquestion: \n-