Skip to content

fix(server): respawn Claude after an expired login - #9468

Closed
robertnisipeanu wants to merge 2 commits into
pingdotgg:mainfrom
robertnisipeanu:worktree-claude-auth-respawn
Closed

fix(server): respawn Claude after an expired login#9468
robertnisipeanu wants to merge 2 commits into
pingdotgg:mainfrom
robertnisipeanu:worktree-claude-auth-respawn

Conversation

@robertnisipeanu

@robertnisipeanu robertnisipeanu commented Sep 3, 2026

Copy link
Copy Markdown

Fixes #9607

Problem

Claude Code loads its OAuth credentials once per process. T3 Code keeps one long-lived CLI per thread, so after the credentials expire that thread keeps answering "Not logged in · Please run /login" even after the user logs in again from a terminal. New threads work because they spawn a fresh CLI. The stuck thread only recovers once the idle reaper kills its process, up to 30 minutes later, or after a manual Stop.

Fix

When the CLI reports an assistant message with error: "authentication_failed", the Claude adapter:

  • fails the turn with the CLI's own message (a message without text gets a runtime error with a fixed fallback instead), and
  • stops the session at the end of that turn, so the next send recovers the thread from its persisted resume cursor and spawns a fresh CLI with the current credentials.

The stop runs on the stream fiber itself, so stopSessionInternal skips interrupting that fiber and lets the stream end through its own stopped check. The first auth-failure message in a turn wins, so a repeated failure does not duplicate the runtime error or replace the CLI's message with the fallback.

Testing

  • vp test run apps/server/src/provider/Layers/ClaudeAdapter.test.ts (81 passed, two new tests: auth failure with text, and repeated auth failure without text)
  • server typecheck, lint, and format on the touched files

Built with Claude Fable 5.1 in Claude Code.

🤖 Generated with Claude Code


Note

Medium Risk
Changes Claude session lifecycle and turn completion on auth errors; incorrect handling could drop sessions prematurely or leave stale CLI processes, but behavior is scoped to authentication_failed and covered by new tests.

Overview
Fixes threads stuck on “Not logged in” after the user re-authenticates by treating Claude CLI authentication_failed assistant messages as a hard session failure and tearing down the long-lived process so the next message spawns a fresh CLI with current credentials.

The Claude adapter records the first auth failure per turn in authenticationError, fails the turn with the CLI’s text (or a runtime.error plus fallback when there is no text), skips duplicating that message as a generic runtime error, and calls stopSessionInternal after turn.completed. stopSessionInternal no longer interrupts the stream fiber when stop is invoked from that same fiber, avoiding a self-deadlock while the session exits.

Tests cover auth failure with CLI text (failed turn, session.exited, no session, one close) and repeated empty auth messages (single runtime error, first message wins).

Reviewed by Cursor Bugbot for commit 5222edf. Bugbot is set up for automated code reviews on this repo. Configure here.

Note

Stop and respawn Claude session after expired login authentication failure

  • Adds an authentication-error field to ClaudeSessionContext that captures the first authentication-failure message from a Claude CLI assistant message; textless failures emit a fallback runtime error.
  • The result handler in makeClaudeAdapter now checks for a captured authentication error: if present, it completes the turn as failed with that message and stops the session, suppressing a duplicate runtime error. Normal failed results keep existing behavior.
  • Fixes stopSessionInternal so that shutdown invoked from the stream fiber does not wait to interrupt that same fiber; distinct live stream fibers are still interrupted.
  • Risk: any code relying on a second runtime error after an authentication-failed result message will no longer receive it, since makeClaudeAdapter.handleResultMessage suppresses the duplicate when the assistant message already supplied the error text.

Macroscope summarized 619ddee.

Claude Code loads its OAuth credentials once per process. After they
expire, the long-lived CLI behind a thread keeps answering "Not logged
in" even once the user has run /login elsewhere, until the idle reaper
kills it.

When the CLI reports an assistant message with error
"authentication_failed", the adapter fails the turn with the CLI's
message and stops the session, so the next turn spawns a fresh CLI with
the current credentials.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:M 30-99 changed lines (additions + deletions). labels Sep 3, 2026
@macroscopeapp

macroscopeapp Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Not approved

Macroscope's review found this PR not approvable — The production adapter changes authentication-failure handling and terminates long-lived Claude sessions so later requests can respawn with refreshed credentials. Although the change is narrowly scoped and tested, its credential and session-lifecycle implications require focused review.

You can add or adjust custom eligibility rules. Learn more.

…respawn

# Conflicts:
#	apps/server/src/provider/Layers/ClaudeAdapter.ts
@juliusmarminge

Copy link
Copy Markdown
Member

Closing as part of the open-PR backlog sweep (wave 2).

Reason: Duplicate of newer #9628 for the same #9607 Claude auth-respawn fix
Related: #9628

Reopen if this is still wanted and you’re willing to rebase onto current main.

aorwall added a commit to aorwall/t3code that referenced this pull request Sep 8, 2026
Merges `pingdotgg/t3code` `8b2838e0e..a37c664` — 43 commits.

`343` files landed against `343` changed in the upstream range; fork
delta `723` files. Exact match, so nothing upstream changed was dropped.

Details in
[`docs/fork/upstream-merge-log.md`](../blob/merge/upstream-2026-09-08/docs/fork/upstream-merge-log.md).

## Two fork deltas this merge had to re-apply

**Upstream split the server-update banner into two routes.** pingdotgg#10596
added `useAutoBalanceUpdateBanner` beside the single-machine condition
the fork already gates. The conflict was on the first line only, so
resolving it correctly still left the auto-balance route ungated — an
auto-balanced project would have been offered `npx t3` against a backend
that does not implement `server.updateServer`.
`FEATURES.serverUpdateBanner` now carries two gates in `ChatView.tsx`.

**A new settings page needs a gate even though it degrades politely.**
pingdotgg#8103 added `/settings/snap-shot` for desktop window capture. Every
control drives `window.desktopBridge`, and upstream renders an
"unavailable" notice rather than hiding the page, so a hosted build
listed a sidebar section and six searchable rows for a feature it can
never run. Gated with `FEATURES.snapShots`.

Two smaller fixes: `packages/moatless-api` still ran `tsgo --noEmit`
after upstream replaced `@typescript/native-preview` with TypeScript
7.0.2, and `duplicate-adds.mjs` now skips `pnpm-lock.yaml` (it read
`iconv-lite: 0.6.3` as taken twice; `d3-dsv` and `encoding` each declare
it).

## Usable as-is

- Stop-thread keybinding command (pingdotgg#4308).
- Project import tolerates servers that predate the git-identity scan
(pingdotgg#10547).
- Proactive panels open when entering a thread (pingdotgg#10610); pull-request
markdown links open in the panel (pingdotgg#10623); markdown images navigate as
galleries (pingdotgg#10625); pull-request videos play inline (pingdotgg#10617).
- Settings project scopes are searchable and scrollable (pingdotgg#10570); ref
picker stays steady when opening (pingdotgg#9472); sidebar timer uses
`tabular-nums` (pingdotgg#10592); popup triggers stay steady when pressed
(pingdotgg#9468); settled PR colors restore on hover (pingdotgg#10023).
- Composer Fast mode persists across new chats (pingdotgg#2981); inserted
citations are removed on cancel (pingdotgg#10518).
- TypeScript 7.0.2 (pingdotgg#10663) and the knip desktop-export rules (pingdotgg#10269).

## Unsupported in Moatless / needs implementation

- **Cross-platform window capture** (pingdotgg#8103) —
`apps/desktop/src/snapShot/**`,
`apps/web/src/components/settings/SnapShotSettings.tsx`,
`apps/web/src/lib/desktopSnapShot.ts`. Needs an Electron
`window.desktopBridge`; a browser tab has none. Gated behind
`FEATURES.snapShots` in this PR.
- **Auto-balance server update** (pingdotgg#10596) —
`apps/web/src/components/chat/useAutoBalanceUpdateBanner.tsx`. Needs
`server.updateServer`, which Moatless does not dispatch. Gated behind
`FEATURES.serverUpdateBanner` in this PR.
- **Preview recording transfer** (pingdotgg#10572) —
`apps/server/src/mcp/toolkits/preview/handlers.ts`,
`apps/web/src/browser/browserRecordingUpload.ts`. Moves a finished
preview recording into the agent environment over the desktop bridge.
Adds four error types to `packages/contracts/src/previewAutomation.ts`
and no new RPC method, so no union changed. Sits behind the
`previewAutomation.connect` / `focusHost` / `respond` gap already in the
register.
- **Local media linked from remote threads** (pingdotgg#10619) and **browser
editing shortcuts** (pingdotgg#10621) — Electron shell only.
- **iOS Keychain access group** (pingdotgg#3665) and the mobile provider account
badge (pingdotgg#9899) — the fork ships no mobile build against Moatless.

## Backend behavior to consider reproducing in Moatless

- **Name the usage limit and its reset instead of relaying "out of
credits"** (pingdotgg#10473, `apps/server/src/provider/**` Codex adapter).
Moatless owns its provider runtime, so the clearer limit message has to
be produced there.
- **Report usage limits on retried turns** (pingdotgg#10549, Claude adapter). A
retry currently loses the limit signal; same ownership.
- **Disable executable capabilities in Claude metadata generation**
(pingdotgg#4169, `apps/server/src/textGeneration/ClaudeTextGeneration.ts`). Title
and metadata generation should not be able to run tools. Worth mirroring
wherever Moatless generates thread titles.

## Verification

`verify.mjs`: duplicate-adds, tripwires, resolution-check,
unsupported-methods (0 ADD, 0 DROP, 2 KEEP), fmt, lint and typecheck all
pass.

Tests pass except `@t3tools/desktop`, which cannot compile
`scripts/browser-secret-native.test.mjs` because the sandbox has no
`libsecret-1` — 1283 tests pass, 0 fail, and the file is byte-identical
to upstream. New entry in `docs/fork/gaps.md`. `t3` failed
`GrokAdapter.test.ts` once under parallel load and passes 42/42 alone.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---
Moatless task:
https://moatless.soaplabstest.com/tasks/6d8ea486-2fcb-4c25-bd34-dcd15cc4a7ac
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:M 30-99 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Claude thread stays logged out after re-login until its CLI process is reaped

2 participants