From 6a33dedcbb90e7bf9db91207f0edbf19ca6d705e Mon Sep 17 00:00:00 2001 From: KlausUllrich <33103541+KlausUllrich@users.noreply.github.com> Date: Mon, 26 Oct 2026 11:24:11 +0100 Subject: [PATCH] fix: pass --settings before the auth status subcommand (#1392) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `runCliAuthProbe` builds `["auth", "status", "--json"]` and, when the session has a settings override, appends `--settings `. `--settings` is a global option of the Claude Code CLI; the `auth status` subcommand only accepts `--json` and `--text` (see `claude auth status --help`). So every probe with an override fails with `error: unknown option '--settings'` and an empty stdout. The catch branch logs `claude auth status failed: …` on every call and returns `undefined`. For sessions that pass `_meta.claudeCode.options.settings`, the session-creation and per-prompt refreshes therefore never update the auth status. Only the initialize probe, which has no override, still works. This moves `--settings ` in front of the subcommand. Without an override the argv is unchanged. **Reproduce** (no credentials needed; an empty config dir answers "not logged in"): ```sh d=$(mktemp -d) env -i HOME="$d" PATH="$PATH" CLAUDE_CONFIG_DIR="$d/.claude" \ claude auth status --json --settings '{}' # error: unknown option '--settings' env -i HOME="$d" PATH="$PATH" CLAUDE_CONFIG_DIR="$d/.claude" \ claude --settings '{}' auth status --json # {"loggedIn": false, "authMethod": "none", ...} ``` The option is applied in the global position, not just parsed: `claude --settings '{"apiKeyHelper":"/bin/echo rt-fake-key"}' auth status --json` reports `"loggedIn": true, "authMethod": "api_key_helper"`. Tested with 10 native Claude Code binaries (linux-x64), each in an empty `HOME`: | Claude Code | `auth status --json --settings '{}'` | `--settings '{}' auth status --json` | |---|---|---| | 2.1.286 | unknown option | JSON | | 2.1.294 | unknown option | JSON | | 2.1.295, 2.1.296, 2.1.299 | unknown option | JSON | | 2.1.300, 2.1.301, 2.1.302 | unknown option | JSON | | 2.1.303 (the SDK's `claudeCodeVersion`) | unknown option | JSON | | 2.1.304 | unknown option | JSON | The existing test in `auth-status.test.ts` pinned the old order; it now expects `--settings` first. Its mock and `statusCalls()` matched probes by `args[1] === "status"`, which a leading global option shifts, so both now locate the subcommand instead. `npm run build`, `format:check`, `lint` and `test:run` pass, apart from one test that fails identically on `main` without this change. Co-authored-by: Claude Opus 5.5 --- src/acp-agent.ts | 4 +++- src/tests/auth-status.test.ts | 13 +++++++++---- 2 files changed, 12 insertions(+), 5 deletions(-) diff --git a/src/acp-agent.ts b/src/acp-agent.ts index 893a546c..ed3157b9 100644 --- a/src/acp-agent.ts +++ b/src/acp-agent.ts @@ -3430,7 +3430,9 @@ export class ClaudeAcpAgent { try { const cliPath = await claudeCliPath(); const args = ["auth", "status", "--json"]; - if (context?.override) args.push("--settings", JSON.stringify(context.override)); + // `--settings` is a global CLI option: after the `auth status` subcommand the CLI rejects + // it with "unknown option '--settings'", so it has to come first. + if (context?.override) args.unshift("--settings", JSON.stringify(context.override)); ({ stdout } = await execFileAsync(cliPath, args, { timeout: AUTH_STATUS_PROBE_TIMEOUT_MS, cwd: context?.manager.getCwd() ?? process.cwd(), diff --git a/src/tests/auth-status.test.ts b/src/tests/auth-status.test.ts index 2265a217..b59398bc 100644 --- a/src/tests/auth-status.test.ts +++ b/src/tests/auth-status.test.ts @@ -22,6 +22,11 @@ vi.mock("@anthropic-ai/claude-agent-sdk", async () => ({ /** Shared spy for both `claude auth status --json` and `claude auth logout`. * Tests set `execFileResults` per invoked subcommand. */ const execFileSpy = vi.hoisted(() => vi.fn()); +/** `auth status` probes, wherever global options such as `--settings` put the subcommand. */ +function isStatusProbe(args: string[]): boolean { + const auth = args.indexOf("auth"); + return auth >= 0 && args[auth + 1] === "status"; +} vi.mock("node:child_process", async () => { const actual = await vi.importActual("node:child_process"); @@ -409,7 +414,7 @@ describe("auth status over ACP", () => { execFileSpy.mockImplementation((...invocation: unknown[]) => { const args = invocation[1] as string[]; const cb = invocation[invocation.length - 1] as (...a: unknown[]) => void; - if (args[1] === "status") { + if (isStatusProbe(args)) { const stdout = statusStdout; const fails = statusFails; const answer = () => { @@ -451,7 +456,7 @@ describe("auth status over ACP", () => { }); function statusCalls() { - return execFileSpy.mock.calls.filter((call) => (call[1] as string[])[1] === "status"); + return execFileSpy.mock.calls.filter((call) => isStatusProbe(call[1] as string[])); } function updates() { @@ -542,11 +547,11 @@ describe("auth status over ACP", () => { env: { ANTHROPIC_BASE_URL: "https://example.invalid/override" }, }); expect(statusCalls().at(-1)?.[1]).toEqual([ + "--settings", + JSON.stringify({ apiKeyHelper: "central proxy start" }), "auth", "status", "--json", - "--settings", - JSON.stringify({ apiKeyHelper: "central proxy start" }), ]); await promptOnce(sessionId); await flushStatus();