diff --git a/clients/web/src/components/groups/ProtocolEntry/ProtocolEntry.test.tsx b/clients/web/src/components/groups/ProtocolEntry/ProtocolEntry.test.tsx
index 0408f0b5..45d658c4 100644
--- a/clients/web/src/components/groups/ProtocolEntry/ProtocolEntry.test.tsx
+++ b/clients/web/src/components/groups/ProtocolEntry/ProtocolEntry.test.tsx
@@ -756,6 +756,41 @@ describe("ProtocolEntry — client-rejected response", () => {
expect(screen.getByText(REASON)).toBeInTheDocument();
});
+ // messageLogState falls back to the standalone response frame when no request
+ // entry was there to fold into (a trimmed log, or a reconnect boundary). That
+ // entry is not `direction: "request"`, so the request-only status lifecycle
+ // would have rendered it without any badge at all.
+ const rejectedStandaloneResponse: MessageEntry = {
+ id: "rej-standalone",
+ timestamp: new Date("2026-07-28T10:00:00Z"),
+ direction: "response",
+ origin: "server",
+ message: {
+ jsonrpc: "2.0",
+ id: 11,
+ result: { resultType: "complete", tools: [] },
+ },
+ clientError: REASON,
+ };
+
+ it("renders Error on a rejected standalone response entry", () => {
+ renderWithMantine(
+ ,
+ );
+ expect(screen.getByText("Error")).toBeInTheDocument();
+ });
+
+ it("still renders no status badge on an unrejected standalone response", () => {
+ const clean: MessageEntry = {
+ ...rejectedStandaloneResponse,
+ clientError: undefined,
+ };
+ renderWithMantine();
+ expect(screen.queryByText("Error")).not.toBeInTheDocument();
+ expect(screen.queryByText("OK")).not.toBeInTheDocument();
+ expect(screen.queryByText("Pending")).not.toBeInTheDocument();
+ });
+
it("shows no rejection alert on an ordinary success", () => {
renderWithMantine(
,
diff --git a/clients/web/src/components/groups/ProtocolEntry/ProtocolEntry.tsx b/clients/web/src/components/groups/ProtocolEntry/ProtocolEntry.tsx
index d5819d3d..73edda6c 100644
--- a/clients/web/src/components/groups/ProtocolEntry/ProtocolEntry.tsx
+++ b/clients/web/src/components/groups/ProtocolEntry/ProtocolEntry.tsx
@@ -220,12 +220,16 @@ function extractResourceUri(entry: MessageEntry): string | undefined {
function extractStatus(
entry: MessageEntry,
): "success" | "error" | "pending" | "none" {
+ // A response the CLIENT refused is an error whichever entry carries it, so
+ // this is checked BEFORE the request-only lifecycle below (#1953).
+ // messageLogState annotates the request entry when the response was folded
+ // into one, but falls back to the standalone response frame when there was
+ // no matching request (a trimmed log, or a reconnect boundary) — and that
+ // entry would otherwise fall straight through to "none" and render no badge.
+ if (entry.clientError) return "error";
if (entry.direction !== "request") return "none";
if (!entry.response) return "pending";
if ("error" in entry.response) return "error";
- // The server answered with a valid result the CLIENT then refused (#1953).
- // The call failed, so the entry must not read as a success.
- if (entry.clientError) return "error";
return "success";
}