From 45db14b68d964ac5ad5a64ec7d3615c7c6d89010 Mon Sep 17 00:00:00 2001 From: core-devops Date: Sat, 13 Jun 2026 03:07:48 -0700 Subject: [PATCH] fix(canvas-chat): clear "unreachable" banner while the agent is thinking (core#2697) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Reported: the red "Failed to send — agent may be unreachable" banner showed next to a live "●●● 102s" timer with tool calls streaming — the agent was plainly reachable + working a long poll-mode turn that hadn't replied yet. #2736 cleared the banner only when a reply LANDED, so a banner set earlier lingered through the whole multi-minute turn. Add an effect that clears both error sources whenever `thinking` is true (sending || data.currentTask) — active processing proves reachability, so an "unreachable" banner is self-contradictory. The banner now only shows when the agent is genuinely idle + a send failed. Test: rendering with data.currentTask set (thinking) clears the send-error. Co-Authored-By: Claude Fable 5 --- canvas/src/components/tabs/ChatTab.tsx | 16 ++++++++++++++++ .../__tests__/ChatTab.errorClearOnReply.test.tsx | 10 ++++++++++ 2 files changed, 26 insertions(+) diff --git a/canvas/src/components/tabs/ChatTab.tsx b/canvas/src/components/tabs/ChatTab.tsx index 9f2ae508..af3f421c 100644 --- a/canvas/src/components/tabs/ChatTab.tsx +++ b/canvas/src/components/tabs/ChatTab.tsx @@ -243,6 +243,22 @@ function MyChatPanel({ workspaceId, data }: Props) { } }, [data.status, clearSendError]); + // Clear any stale "Failed to send — agent may be unreachable" banner the + // moment the agent is demonstrably WORKING (core#2697). `thinking` is true + // when the user's send is in flight OR the workspace heartbeat reports an + // in-flight task — either way the agent is reachable, so an "unreachable" + // banner is self-contradictory (reported: banner shown beside a live + // "●●● 102s" timer + streaming tool calls on a long poll-mode turn). + // #2736 only cleared on a reply LANDING; this also clears the instant the + // agent starts/continues working, so the banner can't linger through a + // multi-minute turn that hasn't replied yet. + useEffect(() => { + if (thinking) { + setError(null); + clearSendError(); + } + }, [thinking, clearSendError]); + // Scroll behavior across messages updates: // - Prepend (loadOlder landed) → restore the user's saved // distance-from-bottom so their reading position is unchanged. diff --git a/canvas/src/components/tabs/__tests__/ChatTab.errorClearOnReply.test.tsx b/canvas/src/components/tabs/__tests__/ChatTab.errorClearOnReply.test.tsx index 5802a20c..bce1739b 100644 --- a/canvas/src/components/tabs/__tests__/ChatTab.errorClearOnReply.test.tsx +++ b/canvas/src/components/tabs/__tests__/ChatTab.errorClearOnReply.test.tsx @@ -75,4 +75,14 @@ describe("ChatTab — stale error banner clears on a successful reply (core#2697 act(() => { captured.onSendComplete?.(); }); expect(clearErrorSpy).toHaveBeenCalled(); }); + + it("clears the 'unreachable' banner while the agent is THINKING (currentTask set), before any reply", () => { + // The reported bug: banner shown beside a live "●●● 102s" timer on a long + // poll-mode turn that hadn't replied yet. data.currentTask set => thinking + // => the agent is reachable => the unreachable banner must clear on its own. + const busy = { status: "online" as const, runtime: "claude-code", currentTask: "downloading assets" } as unknown as Parameters[0]["data"]; + render(); + // Mount with currentTask set => the thinking-clears-error effect fires. + expect(clearErrorSpy).toHaveBeenCalled(); + }); }); -- 2.52.0