From 8d1813c900cd133a7958b92ea09f19dc2895f2a7 Mon Sep 17 00:00:00 2001 From: joaovictor91123 Date: Sun, 12 Jul 2026 16:48:11 +0400 Subject: [PATCH] fix(miner): gate doctor's claude/codex CLI-presence checks by MINER_CODING_AGENT_PROVIDER (#5165) checkClaudeCliPresent/checkCodexCliPresent in laptop-init.js ran unconditionally, contradicting their own comment that a CLI is "only needed once a driver provider is configured" -- a rationale that was never actually implemented, so doctor could not distinguish "no provider configured, CLI absence is fine" from "provider configured but the CLI is missing, every attempt will fail." Both checks now read MINER_CODING_AGENT_PROVIDER and only flip ok to false when the relevant provider (claude-cli / codex-cli) is configured and its CLI binary is absent. An unconfigured provider, or a different provider configured, remains ok: true with the existing advisory message. The already-present authenticated/unauthenticated detail is unchanged either way. --- packages/gittensory-miner/lib/laptop-init.js | 39 +++++++-- test/unit/miner-cli-doctor-checks.test.ts | 85 ++++++++++++++++++++ 2 files changed, 116 insertions(+), 8 deletions(-) diff --git a/packages/gittensory-miner/lib/laptop-init.js b/packages/gittensory-miner/lib/laptop-init.js index 6cd4c7c228..3aa7299499 100644 --- a/packages/gittensory-miner/lib/laptop-init.js +++ b/packages/gittensory-miner/lib/laptop-init.js @@ -103,15 +103,30 @@ function resolveCodexAuthPath(env = process.env) { return join(base, "auth.json"); } -/** Informational only — a coding-agent CLI is only needed once a driver provider is configured (#4289), so a - * missing or unauthenticated CLI is advisory (`ok: true`), mirroring checkDockerPresent's optional tone. The - * auth probe is read-only and never spawns the CLI: it surfaces, proactively, the SAME condition claude - * checks at call time — `CLAUDE_CODE_OAUTH_TOKEN` present (see createClaudeCodeAi, src/selfhost/ai.ts). */ +/** A coding-agent CLI is only needed once a driver provider is configured (#4289) — gated by + * `MINER_CODING_AGENT_PROVIDER` (#5165). When that provider is NOT the CLI being checked, absence is + * advisory (`ok: true`), mirroring checkDockerPresent's optional tone. When it IS configured and the CLI is + * missing, `ok: false` — every attempt will fail without it. The auth probe (once found) stays advisory + * either way, since an unauthenticated-but-installed CLI is a separate, already-visible warning. */ +function codingAgentProviderConfiguredFor(env, providerName) { + return env.MINER_CODING_AGENT_PROVIDER === providerName; +} + +/** Informational unless `MINER_CODING_AGENT_PROVIDER=claude-cli` (#5165), in which case a missing CLI fails + * doctor. The auth probe is read-only and never spawns the CLI: it surfaces, proactively, the SAME condition + * claude checks at call time — `CLAUDE_CODE_OAUTH_TOKEN` present (see createClaudeCodeAi, src/selfhost/ai.ts). */ export function checkClaudeCliPresent(options = {}) { const env = options.env ?? process.env; const claudePath = (options.resolveClaudePath ?? (() => findExecutableOnPath("claude", env)))(); if (!claudePath) { - return { name: "claude-cli-present", ok: true, detail: "not installed (optional until a coding-agent driver is configured)" }; + const configured = codingAgentProviderConfiguredFor(env, "claude-cli"); + return { + name: "claude-cli-present", + ok: !configured, + detail: configured + ? "not installed — MINER_CODING_AGENT_PROVIDER is set to claude-cli, every attempt will fail without it" + : "not installed (optional until a coding-agent driver is configured)", + }; } const authed = typeof env.CLAUDE_CODE_OAUTH_TOKEN === "string" && env.CLAUDE_CODE_OAUTH_TOKEN.length > 0; return { @@ -121,13 +136,21 @@ export function checkClaudeCliPresent(options = {}) { }; } -/** Informational only — mirrors {@link checkClaudeCliPresent} for the codex CLI. The auth probe checks the - * same read-only condition assertCodexAuthConfigured uses at call time: codex's `auth.json` is readable. */ +/** Informational unless `MINER_CODING_AGENT_PROVIDER=codex-cli` (#5165), in which case a missing CLI fails + * doctor — mirrors {@link checkClaudeCliPresent}. The auth probe checks the same read-only condition + * assertCodexAuthConfigured uses at call time: codex's `auth.json` is readable. */ export function checkCodexCliPresent(options = {}) { const env = options.env ?? process.env; const codexPath = (options.resolveCodexPath ?? (() => findExecutableOnPath("codex", env)))(); if (!codexPath) { - return { name: "codex-cli-present", ok: true, detail: "not installed (optional until a coding-agent driver is configured)" }; + const configured = codingAgentProviderConfiguredFor(env, "codex-cli"); + return { + name: "codex-cli-present", + ok: !configured, + detail: configured + ? "not installed — MINER_CODING_AGENT_PROVIDER is set to codex-cli, every attempt will fail without it" + : "not installed (optional until a coding-agent driver is configured)", + }; } const authPath = (options.resolveCodexAuthPath ?? (() => resolveCodexAuthPath(env)))(); let authed = false; diff --git a/test/unit/miner-cli-doctor-checks.test.ts b/test/unit/miner-cli-doctor-checks.test.ts index 74d7e2a487..848cac7624 100644 --- a/test/unit/miner-cli-doctor-checks.test.ts +++ b/test/unit/miner-cli-doctor-checks.test.ts @@ -58,4 +58,89 @@ describe("gittensory-miner doctor — coding-agent CLI checks (#4304)", () => { expect(names).toContain("claude-cli-present"); expect(names).toContain("codex-cli-present"); }); + + describe("provider-gated CLI-presence failures (#5165)", () => { + it("claude: regression -- CLI missing while unconfigured no longer breaks doctor (ok stays true)", () => { + const check = checkClaudeCliPresent({ env: {}, resolveClaudePath: () => null }); + expect(check.ok).toBe(true); + }); + + it("claude: CLI missing + a DIFFERENT provider configured stays advisory (ok true)", () => { + const check = checkClaudeCliPresent({ + env: { MINER_CODING_AGENT_PROVIDER: "codex-cli" }, + resolveClaudePath: () => null, + }); + expect(check.ok).toBe(true); + expect(check.detail).toMatch(/^not installed \(optional/); + }); + + it("claude: CLI missing + claude-cli configured fails doctor with an actionable message", () => { + const check = checkClaudeCliPresent({ + env: { MINER_CODING_AGENT_PROVIDER: "claude-cli" }, + resolveClaudePath: () => null, + }); + expect(check.ok).toBe(false); + expect(check.detail).toBe( + "not installed — MINER_CODING_AGENT_PROVIDER is set to claude-cli, every attempt will fail without it", + ); + }); + + it("claude: CLI present + claude-cli configured still reports the normal present/authenticated detail", () => { + const check = checkClaudeCliPresent({ + env: { MINER_CODING_AGENT_PROVIDER: "claude-cli", CLAUDE_CODE_OAUTH_TOKEN: "present" }, + resolveClaudePath: () => "/usr/bin/claude", + }); + expect(check.ok).toBe(true); + expect(check.detail).toBe("found at /usr/bin/claude (authenticated)"); + }); + + it("codex: regression -- CLI missing while unconfigured no longer breaks doctor (ok stays true)", () => { + const check = checkCodexCliPresent({ env: {}, resolveCodexPath: () => null }); + expect(check.ok).toBe(true); + }); + + it("codex: CLI missing + a DIFFERENT provider configured stays advisory (ok true)", () => { + const check = checkCodexCliPresent({ + env: { MINER_CODING_AGENT_PROVIDER: "claude-cli" }, + resolveCodexPath: () => null, + }); + expect(check.ok).toBe(true); + expect(check.detail).toMatch(/^not installed \(optional/); + }); + + it("codex: CLI missing + codex-cli configured fails doctor with an actionable message", () => { + const check = checkCodexCliPresent({ + env: { MINER_CODING_AGENT_PROVIDER: "codex-cli" }, + resolveCodexPath: () => null, + }); + expect(check.ok).toBe(false); + expect(check.detail).toBe( + "not installed — MINER_CODING_AGENT_PROVIDER is set to codex-cli, every attempt will fail without it", + ); + }); + + it("codex: CLI present + codex-cli configured still reports the normal present/authenticated detail", () => { + const authFile = join(tempRoot(), "auth.json"); + writeFileSync(authFile, "{}"); + const check = checkCodexCliPresent({ + env: { MINER_CODING_AGENT_PROVIDER: "codex-cli" }, + resolveCodexPath: () => "/usr/bin/codex", + resolveCodexAuthPath: () => authFile, + }); + expect(check.ok).toBe(true); + expect(check.detail).toBe("found at /usr/bin/codex (authenticated)"); + }); + + it("invariant: an unconfigured (or differently-configured) provider's CLI check is never reported as ok: false regardless of CLI presence", () => { + const missingUnconfigured = checkClaudeCliPresent({ env: {}, resolveClaudePath: () => null }); + const presentUnconfigured = checkClaudeCliPresent({ env: {}, resolveClaudePath: () => "/usr/bin/claude" }); + const missingOtherProvider = checkCodexCliPresent({ + env: { MINER_CODING_AGENT_PROVIDER: "claude-cli" }, + resolveCodexPath: () => null, + }); + expect(missingUnconfigured.ok).toBe(true); + expect(presentUnconfigured.ok).toBe(true); + expect(missingOtherProvider.ok).toBe(true); + }); + }); });