Skip to content

fix(core): chat UI fails silently when Claude OAuth refresh token is expired #1076

@tbrandenburg

Description

@tbrandenburg

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

  1. Log in to Claude via claude /login (OAuth flow).
  2. Make at least one request so the refresh token is used.
  3. Invalidate the session on the Claude side (e.g. log out, revoke access, or let it expire naturally).
  4. 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

  1. 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.
  2. In AUTH_PATTERNS (:202), add: 'refresh token', 'access token', 'could not be refreshed', 'log out and sign in'.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions