From aad45b96376c02e19bc3a41039cc658eb1ca342a Mon Sep 17 00:00:00 2001 From: Nick Tabeling Date: Thu, 9 Apr 2026 13:48:45 +0200 Subject: [PATCH] fix: remove --bare flag to restore OAuth login for Claude Pro/Max --bare disables OAuth/keychain auth and requires ANTHROPIC_API_KEY, breaking Claude Pro/Max subscriptions. CLAUDE.md isolation is now achieved structurally: workspaces live under ~/.disclaw/workspaces/ (outside the repo) so walk-up never reaches DisClaw's CLAUDE.md. Update integration test to assert --bare is absent and verify correct args (-p, --output-format json). Fix sanitize-env tests to reflect CI=true removal (was breaking OAuth headless-mode detection). Co-Authored-By: Claude Sonnet 4.6 --- src/agent/runner.ts | 14 +++++----- .../no-parent-claude-md-leak.test.ts | 27 ++++++++++--------- tests/unit/sanitize-env.test.ts | 9 ++++--- 3 files changed, 27 insertions(+), 23 deletions(-) diff --git a/src/agent/runner.ts b/src/agent/runner.ts index 324a2e3..6d432af 100644 --- a/src/agent/runner.ts +++ b/src/agent/runner.ts @@ -163,19 +163,17 @@ export async function runAgent(options: RunAgentOptions): Promise { } return new Promise((resolve, reject) => { - // IMPORTANT: --bare prevents CLAUDE.md walk-up through parent directories. - // Without this flag every agent inherits the DisClaw project CLAUDE.md as - // its identity (and any other CLAUDE.md files up the directory tree). - // --append-system-prompt-file injects the workspace-specific CLAUDE.md - // explicitly so each agent keeps its own isolated identity. + // NOTE: --bare is intentionally NOT used here. --bare disables OAuth/keychain + // auth and requires ANTHROPIC_API_KEY, which breaks Claude Pro/Max subscriptions. + // CLAUDE.md isolation is handled structurally: workspaces live under + // ~/.disclaw/workspaces/ (outside the DisClaw repo), so walk-up will never + // reach the DisClaw project CLAUDE.md. The workspace CLAUDE.md is picked up + // automatically because cwd is set to workspacePath. const args = [ "-p", prompt, "--output-format", "json", - "--bare", - "--append-system-prompt-file", - path.join(workspacePath, "CLAUDE.md"), ]; const child = spawn(claudeCommand, args, { diff --git a/tests/integration/no-parent-claude-md-leak.test.ts b/tests/integration/no-parent-claude-md-leak.test.ts index a85403d..eb0bf38 100644 --- a/tests/integration/no-parent-claude-md-leak.test.ts +++ b/tests/integration/no-parent-claude-md-leak.test.ts @@ -4,9 +4,12 @@ import * as os from "os"; import * as path from "path"; /** - * Integration test: verifies that every Claude CLI invocation includes - * --bare (prevents CLAUDE.md walk-up through parent directories) and - * --append-system-prompt-file pointing to the workspace CLAUDE.md. + * Integration test: verifies that Claude CLI invocations use the correct args. + * + * CLAUDE.md isolation is achieved structurally (workspaces live outside the + * repo under ~/.disclaw/workspaces/) — NOT via --bare or --append-system-prompt-file. + * --bare was removed because it disables OAuth/keychain auth, breaking Claude + * Pro/Max subscriptions. * * We mock cross-spawn so no real process is launched, and capture the exact * args array that runner.ts would pass to the CLI. @@ -116,7 +119,7 @@ describe("no-parent-claude-md-leak", () => { } }); - it("passes --bare flag to the Claude CLI", async () => { + it("does NOT pass --bare flag (--bare disables OAuth and breaks Claude Pro/Max)", async () => { await runAgent({ workspacePath: workspaceDir, channelName: "test-channel", @@ -124,10 +127,10 @@ describe("no-parent-claude-md-leak", () => { conversationHistory: [], }); - expect(capturedArgs).toContain("--bare"); + expect(capturedArgs).not.toContain("--bare"); }); - it("passes --append-system-prompt-file pointing to workspace CLAUDE.md", async () => { + it("passes -p and --output-format json; does NOT pass --append-system-prompt-file", async () => { await runAgent({ workspacePath: workspaceDir, channelName: "test-channel", @@ -135,12 +138,12 @@ describe("no-parent-claude-md-leak", () => { conversationHistory: [], }); - const idx = capturedArgs.indexOf("--append-system-prompt-file"); - expect(idx).toBeGreaterThanOrEqual(0); + // Required args for non-interactive JSON output + expect(capturedArgs).toContain("-p"); + expect(capturedArgs).toContain("--output-format"); + expect(capturedArgs).toContain("json"); - const systemPromptFile = capturedArgs[idx + 1]; - expect(systemPromptFile).toMatch(/CLAUDE\.md$/); - // Must point to the workspace-specific CLAUDE.md, not a parent directory - expect(systemPromptFile).toBe(path.join(workspaceDir, "CLAUDE.md")); + // CLAUDE.md is auto-discovered via cwd (workspacePath), no flag needed + expect(capturedArgs).not.toContain("--append-system-prompt-file"); }); }); diff --git a/tests/unit/sanitize-env.test.ts b/tests/unit/sanitize-env.test.ts index f8b832e..1819333 100644 --- a/tests/unit/sanitize-env.test.ts +++ b/tests/unit/sanitize-env.test.ts @@ -55,10 +55,13 @@ describe("sanitizedEnv", () => { expect(result["SOME_SAFE_VAR"]).toBe("safe-value"); }); - it("(c) sets CI=true and DISCLAW_AGENT=1 as mandatory markers", () => { + it("(c) sets DISCLAW_AGENT=1 but does NOT set CI=true (breaks Claude Code OAuth)", () => { + // CI=true was removed: Claude Code interprets it as headless CI mode + // and requires ANTHROPIC_API_KEY instead of OAuth/keychain login. + delete process.env["CI"]; const result = sanitizedEnv(); - expect(result["CI"]).toBe("true"); + expect(result["CI"]).toBeUndefined(); expect(result["DISCLAW_AGENT"]).toBe("1"); }); @@ -91,7 +94,7 @@ describe("sanitizedEnv", () => { expect(result["ANOTHER_KEY"]).toBeUndefined(); // Extras and standard vars should still be present expect(result["DISCLAW_AGENT_NAME"]).toBe("test-agent"); - expect(result["CI"]).toBe("true"); + expect(result["DISCLAW_AGENT"]).toBe("1"); }); it("does not mutate process.env", () => { -- 2.45.2