Summary
- What broke: When the Claude OAuth refresh token is expired or already used, the chat UI shows no response at all — no error message, no indication of failure. The server logs the error, but the user is left staring at a blank response.
- When it started: Present since stream/batch mode was introduced (original implementation gap).
- Severity:
major
Steps to Reproduce
- Log in to Claude via
claude /login (OAuth flow).
- Make at least one request so the refresh token is used.
- Invalidate the session on the Claude side (e.g. log out, revoke access, or let it expire naturally).
- Send any message in the Archon Web UI.
Expected vs Actual
- Expected: An error message is shown in the chat UI — e.g. "AI error (auth). Check your Claude credentials or use /reset."
- Actual: No response is shown. The UI hangs silently. Server logs contain:
Your access token could not be refreshed because your refresh token was already used. Please log out and sign in again.
User Flow
User (Web UI) Orchestrator Claude SDK
───────────── ──────────── ──────────
sends message ────────► handleStreamMode
for-await loop
subprocess fails
auth error → stderr
yields result msg
{ type: 'result',
is_error: true,
session_id: undefined }
[X] condition false:
msg.sessionId is undefined
→ result chunk DROPPED
allMessages.length === 0
→ early return (no send)
(no response) ◄───────── sendMessage never called
Environment
- Platform: Web
- Database: SQLite
- Running in worktree?: No
- OS: Linux
Logs
subprocess_error: Your access token could not be refreshed because your refresh token was already used.
Please log out and sign in again.
(No assistant message is sent to the platform — that is the bug.)
Impact
- Affected workflows/commands: All workflows and direct messages using the Claude SDK
- Reproduction rate: Always (reproducible with an expired/invalidated OAuth token)
- Workaround available?: Restart Claude auth via
claude /login in the terminal, but the user has no indication they need to do this.
- Data loss risk?: No
Scope
- Package(s) involved:
@archon/core
- Modules:
orchestrator/orchestrator-agent.ts — primary bug (handleStreamMode:876, handleBatchMode:988)
clients/claude.ts — secondary bug (AUTH_PATTERNS:202-209 missing refresh-token patterns, causes needless retries)
Root Cause (for implementors)
Primary (orchestrator-agent.ts:876 and :988):
Both handleStreamMode and handleBatchMode guard the result handler with msg.sessionId:
} else if (msg.type === 'result' && msg.sessionId) {
When auth fails before a session is established, the SDK yields a result with is_error: true and no session_id (field is undefined). The condition evaluates to false, the entire result chunk is dropped, allMessages stays empty, and both modes hit the early-return guard — without ever calling platform.sendMessage.
Secondary (claude.ts:202-209):
AUTH_PATTERNS does not include 'refresh token', 'access token', 'could not be refreshed', or 'log out and sign in'. If the SDK throws for this reason rather than yielding an error result, it is misclassified as crash and retried 3 times unnecessarily.
Fix
- In
handleStreamMode (:876) and handleBatchMode (:988), remove && msg.sessionId from the result branch guard. Assign newSessionId only when msg.sessionId is defined. Add an explicit isError check that calls platform.sendMessage with a user-visible error and returns early.
- In
AUTH_PATTERNS (:202), add: 'refresh token', 'access token', 'could not be refreshed', 'log out and sign in'.
Summary
majorSteps to Reproduce
claude /login(OAuth flow).Expected vs Actual
User Flow
Environment
Logs
(No assistant message is sent to the platform — that is the bug.)
Impact
claude /loginin the terminal, but the user has no indication they need to do this.Scope
@archon/coreorchestrator/orchestrator-agent.ts— primary bug (handleStreamMode:876,handleBatchMode:988)clients/claude.ts— secondary bug (AUTH_PATTERNS:202-209missing refresh-token patterns, causes needless retries)Root Cause (for implementors)
Primary (
orchestrator-agent.ts:876and:988):Both
handleStreamModeandhandleBatchModeguard theresulthandler withmsg.sessionId:When auth fails before a session is established, the SDK yields a
resultwithis_error: trueand nosession_id(field isundefined). The condition evaluates tofalse, the entire result chunk is dropped,allMessagesstays empty, and both modes hit the early-return guard — without ever callingplatform.sendMessage.Secondary (
claude.ts:202-209):AUTH_PATTERNSdoes not include'refresh token','access token','could not be refreshed', or'log out and sign in'. If the SDK throws for this reason rather than yielding an error result, it is misclassified ascrashand retried 3 times unnecessarily.Fix
handleStreamMode(:876) andhandleBatchMode(:988), remove&& msg.sessionIdfrom theresultbranch guard. AssignnewSessionIdonly whenmsg.sessionIdis defined. Add an explicitisErrorcheck that callsplatform.sendMessagewith a user-visible error and returns early.AUTH_PATTERNS(:202), add:'refresh token','access token','could not be refreshed','log out and sign in'.