feat: structured AgentError + classified error UX with Retry#693
Merged
Conversation
Structured AgentError (extends Error; kind/retryable/status/cause) + a shared toAgentError() classifier (5-class: connection/auth/server/interrupted/aborted), normalized in both adapters; neutral Agent.error re-typed to Signal<AgentError>; new neutral retry(); ChatErrorComponent renders legible per-kind copy + a conditional Retry. Fixes the cryptic 'HTTP 500:' surfacing + langgraph abort inconsistency the audit flagged. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…auth/server/interrupted/aborted) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
… neutral retry() Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…l retry() - Thread `userAbortRequested` flag in the bridge; stop() sets it before aborting so the runStream() catch can distinguish user-stop (→ Idle, no error) from a mid-stream abort (→ interrupted AgentError) or fresh connect abort (→ toAgentError classification). - Thread `streamingStarted` flag in runStream() (set on first event); AbortError after streaming has begun → kind:'interrupted'/retryable:true; AbortError with no events yet falls through to toAgentError. - Normalize all non-abort catch errors through toAgentError from @threadplane/chat so agent.error() is always AgentError | undefined. - Add retry() to agent.fn.ts: no-op while loading, clears error$, then calls resubmitLast() — implements the neutral Agent contract method. - Tighten errorSig to Signal<AgentError | undefined> via a documented cast (BehaviorSubject stays unknown to satisfy StreamSubjects invariance). - MockLangGraphAgent.error re-typed to WritableSignal<AgentError|undefined>. - Update bridge + agent.fn specs: stop()→Idle assertion; four new error-UX tests (server error kind, user-stop→idle, retry() clears+resubmits, retry() no-op while loading); fix pre-existing empty-gen lint error. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ormalize all error$ sites via toAgentError Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…un last input, no duplicate message) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…onditional Retry Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…try recovery
Split the error-handling e2e into two focused tests: one that asserts the
alert shows human-legible copy (matching /can't reach|connection|server|
interrupted|try again/i and NOT /HTTP \d{3}/) with a visible Retry button,
and a second that clicks Retry after unrouting and confirms a final
assistant bubble appears.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ction-before-text; dedup isAbortError/messages
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
…-error # Conflicts: # libs/langgraph/src/lib/agent.fn.spec.ts
blove
added a commit
that referenced
this pull request
Jun 18, 2026
#693's friendly AGENT_ERROR_MESSAGES copy no longer contains "fail"/"error"; update the assertion to mirror the chat example so ag-ui — e2e stays green on this branch too. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
blove
added a commit
that referenced
this pull request
Jun 18, 2026
…2e (#695) #693 replaced the raw stream-failure text with friendly AGENT_ERROR_MESSAGES copy ("Can't reach the server. Check your connection and try again."), which no longer contains "fail"/"error" — turning main red on examples/ag-ui — e2e. The chat example's spec was updated in #693; the ag-ui twin was missed. Mirror that assertion so the ag-ui error-handling spec asserts the actual copy. Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Replaces the cryptic "HTTP 500:" the audit flagged with a structured, classified error and a retry affordance, across
@threadplane/chat+ both adapters.A backend/stream failure was previously caught raw and shown verbatim (
String(error)), with no classification, no retry UI, and an inconsistency where a user stop showed as an error in LangGraph.Changes
AgentError extends Error(@threadplane/chat):kind(connection/auth/server/interrupted/aborted),retryable,status?,cause. Stays anErrorsubclass, so existing.message/instanceofreads keep working. +AGENT_ERROR_MESSAGESdefault copy.toAgentError(raw)— idempotent classifier. Structured.status/.cause.statusis authoritative; network markers →connection; only HTTP-shaped message tokens (HTTP 500,status: 503) yield a status (a baregpt-500no longer misfires); abort short-circuits; fallback isserver+ retryable.Agent.error: Signal<AgentError | undefined>(wasunknown); new neutralretry()(re-run last input; no-op while loading; clears error).error$write viatoAgentError; user-stop → idle (no error); a non-user mid-stream death →interrupted, a non-user pre-stream failure →connection;retry()→ clear +resubmitLast.retry()re-runs the last input via an extractedrunCurrentMessages()helper without duplicating the user message, re-emitting the client-tools catalog.ChatErrorComponent— renders the legibleAgentError.messageand a Retry button only whenretryable, wired toagent.retry().Validation
examples/chat, fixture replay): an aborted stream now shows "Can't reach the server. Check your connection and try again." (asserts noHTTP \d{3}leak), a Retry button appears, and clicking it recovers — 2/2.Design spec + TDD plan under
docs/superpowers/. No backwards-compatibility constraint (pre-1.0);AgentError extends Errorkeeps the ripple small.🤖 Generated with Claude Code