From 8dbcf92a0d125050988474f258df3e55c538efec Mon Sep 17 00:00:00 2001 From: reasv <7143787+reasv@users.noreply.github.com> Date: Mon, 20 Apr 2026 02:13:20 +0200 Subject: [PATCH] fix(server): prevent probeClaudeCapabilities from wasting API requests (#2192) Co-authored-by: Claude Opus 4.6 Co-authored-by: Julius Marminge --- .../src/provider/Layers/ClaudeProvider.ts | 26 +++++++++++++++---- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/apps/server/src/provider/Layers/ClaudeProvider.ts b/apps/server/src/provider/Layers/ClaudeProvider.ts index 5274f84285bf22ad5fdeec776bdf00e54e713511..7c8a4c27a6e2316687d27a16a1c59fe00bced542 100644 --- a/apps/server/src/provider/Layers/ClaudeProvider.ts +++ b/apps/server/src/provider/Layers/ClaudeProvider.ts @@ -14,6 +14,7 @@ import { decodeJsonResult } from "@t3tools/shared/schemaJson"; import { query as claudeQuery, type SlashCommand as ClaudeSlashCommand, + type SDKUserMessage, } from "@anthropic-ai/claude-agent-sdk"; import { @@ -480,13 +481,24 @@ function dedupeSlashCommands( return [...commandsByName.values()]; } +function waitForAbortSignal(signal: AbortSignal): Promise { + if (signal.aborted) { + return Promise.resolve(); + } + return new Promise((resolve) => { + signal.addEventListener("abort", () => resolve(), { once: true }); + }); +} + /** * Probe account information by spawning a lightweight Claude Agent SDK * session and reading the initialization result. * - * The prompt is never sent to the Anthropic API — we abort immediately - * after the local initialization phase completes. This gives us the - * user's subscription type without incurring any token cost. + * We pass a never-yielding AsyncIterable as the prompt so that no user + * message is ever written to the subprocess stdin. This means the Claude + * Code subprocess completes its local initialization IPC (returning + * account info and slash commands) but never starts an API request to + * Anthropic. We read the init data and then abort the subprocess. * * This is used as a fallback when `claude auth status` does not include * subscription type information. @@ -495,12 +507,16 @@ const probeClaudeCapabilities = (binaryPath: string) => { const abort = new AbortController(); return Effect.tryPromise(async () => { const q = claudeQuery({ - prompt: ".", + // Never yield — we only need initialization data, not a conversation. + // This prevents any prompt from reaching the Anthropic API. + // oxlint-disable-next-line require-yield + prompt: (async function* (): AsyncGenerator { + await waitForAbortSignal(abort.signal); + })(), options: { persistSession: false, pathToClaudeCodeExecutable: binaryPath, abortController: abort, - maxTurns: 0, settingSources: ["user", "project", "local"], allowedTools: [], stderr: () => {}, -- 2.54.0