From e31a6c7e3493e00371a616eacb94d570e0174ca5 Mon Sep 17 00:00:00 2001 From: gavrielc Date: Wed, 29 Apr 2026 18:26:04 +0300 Subject: [PATCH] revert(credentials): drop auth-required login-message handling MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Removing the "Not logged in · Please run /login" detection and substitution from this PR — narrowing scope to just the OneCLI gateway transient-retry change. The login-message handling will be addressed separately. Reverts: - AgentProvider.isAuthRequired / authRequiredMessage - ClaudeProvider auth-required regex, classifier, and remediation text - poll-loop writeAuthRequiredMessage helper + call sites - claude.test.ts (auth-only test file) OneCLI/wakeContainer changes (the remaining content of the PR) are unaffected. Co-Authored-By: Claude Opus 4.7 (1M context) --- container/agent-runner/src/poll-loop.ts | 50 ++++--------------- .../agent-runner/src/providers/claude.test.ts | 47 ----------------- .../agent-runner/src/providers/claude.ts | 20 -------- container/agent-runner/src/providers/types.ts | 21 -------- 4 files changed, 11 insertions(+), 127 deletions(-) delete mode 100644 container/agent-runner/src/providers/claude.test.ts diff --git a/container/agent-runner/src/poll-loop.ts b/container/agent-runner/src/poll-loop.ts index fb54378..bd48db2 100644 --- a/container/agent-runner/src/poll-loop.ts +++ b/container/agent-runner/src/poll-loop.ts @@ -21,26 +21,6 @@ function generateId(): string { return `msg-${Date.now()}-${Math.random().toString(36).slice(2, 8)}`; } -// Generic fallback for providers that classify auth failures via -// `isAuthRequired` but don't supply their own remediation text. Concrete -// providers (Claude, Codex, …) override this with a provider-specific -// message via `authRequiredMessage()`. -const GENERIC_AUTH_REQUIRED_MESSAGE = - "I can't reach my credentials right now. The operator running NanoClaw needs to re-authenticate on the host machine."; - -function writeAuthRequiredMessage(provider: AgentProvider, routing: RoutingContext): void { - const text = provider.authRequiredMessage?.() ?? GENERIC_AUTH_REQUIRED_MESSAGE; - log('Auth-required detected — substituting host-aware message for the user'); - writeMessageOut({ - id: generateId(), - kind: 'chat', - platform_id: routing.platformId, - channel_type: routing.channelType, - thread_id: routing.threadId, - content: JSON.stringify({ text }), - }); -} - export interface PollLoopConfig { provider: AgentProvider; /** @@ -191,7 +171,7 @@ export async function runPollLoop(config: PollLoopConfig): Promise { const skippedSet = new Set(skipped); const processingIds = ids.filter((id) => !commandIds.includes(id) && !skippedSet.has(id)); try { - const result = await processQuery(query, routing, processingIds, config.provider, config.providerName); + const result = await processQuery(query, routing, processingIds, config.providerName); if (result.continuation && result.continuation !== continuation) { continuation = result.continuation; setContinuation(config.providerName, continuation); @@ -209,18 +189,15 @@ export async function runPollLoop(config: PollLoopConfig): Promise { clearContinuation(config.providerName); } - if (config.provider.isAuthRequired?.(errMsg)) { - writeAuthRequiredMessage(config.provider, routing); - } else { - writeMessageOut({ - id: generateId(), - kind: 'chat', - platform_id: routing.platformId, - channel_type: routing.channelType, - thread_id: routing.threadId, - content: JSON.stringify({ text: `Error: ${errMsg}` }), - }); - } + // Write error response so the user knows something went wrong + writeMessageOut({ + id: generateId(), + kind: 'chat', + platform_id: routing.platformId, + channel_type: routing.channelType, + thread_id: routing.threadId, + content: JSON.stringify({ text: `Error: ${errMsg}` }), + }); } // Ensure completed even if processQuery ended without a result event @@ -272,7 +249,6 @@ async function processQuery( query: AgentQuery, routing: RoutingContext, initialBatchIds: string[], - provider: AgentProvider, providerName: string, ): Promise { let queryContinuation: string | undefined; @@ -334,11 +310,7 @@ async function processQuery( // at all — either way the turn is finished. markCompleted(initialBatchIds); if (event.text) { - if (provider.isAuthRequired?.(event.text)) { - writeAuthRequiredMessage(provider, routing); - } else { - dispatchResultText(event.text, routing); - } + dispatchResultText(event.text, routing); } } } diff --git a/container/agent-runner/src/providers/claude.test.ts b/container/agent-runner/src/providers/claude.test.ts deleted file mode 100644 index 91836e4..0000000 --- a/container/agent-runner/src/providers/claude.test.ts +++ /dev/null @@ -1,47 +0,0 @@ -import { describe, it, expect } from 'bun:test'; - -import { ClaudeProvider } from './claude.js'; - -describe('ClaudeProvider.isAuthRequired', () => { - const provider = new ClaudeProvider(); - - it('matches the "Not logged in" banner', () => { - expect(provider.isAuthRequired('Not logged in · Please run /login')).toBe(true); - }); - - it('matches the "Invalid API key" banner', () => { - expect(provider.isAuthRequired('Invalid API key · Please run /login')).toBe(true); - }); - - it('matches with trailing content after the banner', () => { - expect(provider.isAuthRequired('Not logged in · Please run /login\n\nstack trace …')).toBe(true); - }); - - it('does not match when the agent quotes the phrase mid-sentence', () => { - const quoted = "The error 'Invalid API key · Please run /login' means your auth has expired."; - expect(provider.isAuthRequired(quoted)).toBe(false); - }); - - it('does not match when the agent leads its reply with the phrase in prose', () => { - const prose = '"Not logged in · Please run /login" is a Claude Code error.'; - expect(provider.isAuthRequired(prose)).toBe(false); - }); - - it('does not match a different separator (defensive against typos in agent output)', () => { - expect(provider.isAuthRequired('Not logged in - Please run /login')).toBe(false); - }); - - it('does not match unrelated text', () => { - expect(provider.isAuthRequired('Tool execution failed: timeout')).toBe(false); - }); -}); - -describe('ClaudeProvider.authRequiredMessage', () => { - const provider = new ClaudeProvider(); - - it('returns the Anthropic-specific remediation', () => { - const msg = provider.authRequiredMessage(); - expect(msg).toContain('Anthropic credentials'); - expect(msg).toContain('claude'); - }); -}); diff --git a/container/agent-runner/src/providers/claude.ts b/container/agent-runner/src/providers/claude.ts index 89dd5cd..fbb077c 100644 --- a/container/agent-runner/src/providers/claude.ts +++ b/container/agent-runner/src/providers/claude.ts @@ -236,18 +236,6 @@ const CLAUDE_CODE_AUTO_COMPACT_WINDOW = '165000'; */ const STALE_SESSION_RE = /no conversation found|ENOENT.*\.jsonl|session.*not found/i; -/** - * Auth-required detection. Matches Claude Code's banner when no usable - * credential is available — "Not logged in · Please run /login" or - * "Invalid API key · Please run /login". The user can't run /login from - * chat, so the poll-loop substitutes a host-aware message. - * - * Anchored to start-of-string with the specific `·` separator (U+00B7) - * the CLI uses, so an agent that quotes the phrase verbatim mid-sentence - * in a normal reply doesn't trip the classifier. - */ -const AUTH_REQUIRED_RE = /^(Not logged in|Invalid API key)\s*·\s*Please run \/login/; - export class ClaudeProvider implements AgentProvider { readonly supportsNativeSlashCommands = true; @@ -271,14 +259,6 @@ export class ClaudeProvider implements AgentProvider { return STALE_SESSION_RE.test(msg); } - isAuthRequired(text: string): boolean { - return AUTH_REQUIRED_RE.test(text); - } - - authRequiredMessage(): string { - return "I can't reach my Anthropic credentials right now. The operator running NanoClaw needs to re-run setup, or run `claude` in the project directory on the machine I'm running on."; - } - query(input: QueryInput): AgentQuery { const stream = new MessageStream(); stream.push(input.prompt); diff --git a/container/agent-runner/src/providers/types.ts b/container/agent-runner/src/providers/types.ts index 3124c07..55ab919 100644 --- a/container/agent-runner/src/providers/types.ts +++ b/container/agent-runner/src/providers/types.ts @@ -14,27 +14,6 @@ export interface AgentProvider { * (missing transcript, unknown session, etc.) and should be cleared. */ isSessionInvalid(err: unknown): boolean; - - /** - * True if the given text/error indicates the underlying SDK or CLI has no - * usable credentials (e.g. Claude Code's "Not logged in · Please run - * /login"). The poll-loop swaps the raw output for a host-aware message - * since the user can't authenticate from chat. - * - * Paired with `authRequiredMessage()` — providers that implement one - * should implement both. The matcher is provider-specific because each - * SDK/CLI has its own auth-failure banner format. - */ - isAuthRequired?(text: string): boolean; - - /** - * User-facing remediation message returned when `isAuthRequired` matches. - * Provider-specific because the actionable instruction differs across - * providers (e.g. Claude vs Codex vs OpenCode each direct the operator - * to a different auth flow). Falls back to a generic message in the - * poll-loop if a provider implements `isAuthRequired` but not this. - */ - authRequiredMessage?(): string; } /**