Skip to content

Interrupting a thread shows a raw [ede_diagnostic] error banner (fixed upstream in #5557; blocked on the sync) #54

Description

@radroid

Symptom

Interrupting (stopping) a Claude thread pops a red error banner:

[ede_diagnostic] result_type=user last_content_type=n/a stop_reason=null

It happens on every interrupt, so a normal, deliberate "stop" reads as a failure.

Root cause

[ede_diagnostic] … entries are CLI-internal telemetry. The Claude CLI hides them from its own UI. They arrive in SDKResultMessage.errors and should never be surfaced.

On our main, apps/server/src/provider/Layers/ClaudeAdapter.ts does two things wrong:

  1. isInterruptedResult() has no terminal_reason check. It only pattern-matches the joined error text for interrupt / aborted / request was aborted / interrupted by user. The diagnostic string above contains none of those substrings, so the interrupt is not recognised.
  2. Nothing filters the [ede_diagnostic] prefix, so once step 1 falls through to "failed", the raw telemetry line is promoted straight into the error banner.

The CLI does stamp aborts explicitly — terminal_reason: "aborted_tools" when you interrupt mid-tool-call, "aborted_streaming" mid-stream — we just weren't reading it.

This is already fixed upstream — we're just behind

Upstream pingdotgg/t3code landed it on 2026-08-06:

c471145e9 — fix(server): stopping a Claude thread no longer shows an ede_diagnostic error (pingdotgg#5557)
apps/server/src/provider/Layers/ClaudeAdapter.ts +24, ClaudeAdapter.test.ts +61

The fix adds both halves:

/**
 * First user-facing error from a non-success result. "[ede_diagnostic] ..."
 * entries are CLI-internal telemetry (the CLI hides them from its own UI too),
 * so they must never become the error banner.
 */
function resultUserFacingError(result: SDKResultMessage): string | undefined {
  if (result.subtype === "success" || !Array.isArray(result.errors)) return undefined;
  return result.errors.find((error) => !error.startsWith("[ede_diagnostic]"));
}

function isInterruptedResult(result: SDKResultMessage): boolean {
  if (result.terminal_reason === "aborted_tools" ||
      result.terminal_reason === "aborted_streaming") {
    return true;
  }
  // …existing string heuristics retained as fallback
}

Filtering is on the [ede_diagnostic] prefix, so it covers this report's stop_reason=null variant as well as the stop_reason=tool_use variant in upstream's regression test (ClaudeAdapter.test.ts: "treats aborted_tools results as interrupted and hides ede_diagnostic errors").

What to do

Do not write fork code for this. Writing our own filter would create a parallel path in ClaudeAdapter.ts — already one of the fork's highest-churn seams — that duplicates an upstream capability and would then conflict with c471145e9 when the sync finally lands.

The fix arrives for free with the upstream sync. Blocked on #49 (daily rebase failing since 2026-08-04; origin/main is behind by a large backlog and does not contain c471145e9).

Verification once the sync lands

  1. git log --oneline origin/main -1 --grep ede_diagnostic → should show c471145e9.
  2. Start any Claude thread, let it call a tool, hit stop mid-tool-call.
  3. Expect: turn reads interrupted, no red banner.
  4. Regression coverage already ships with the sync — no new fork test needed.

Evidence

  • Confirmed absent from fork origin/main: git grep -c ede_diagnostic origin/main -- apps/server/src/provider/Layers/ClaudeAdapter.ts → no match.
  • Confirmed present upstream: same grep against upstream/main → 5 hits across adapter + test.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions