Compare commits

..

2 commits

Author SHA1 Message Date
dev
4661892820 Merge pull request 'fix: remove --bare flag to restore OAuth login for Claude Pro/Max' (#32) from fix/remove-bare-flag into main
Some checks are pending
CI / build-and-test (ubuntu-latest) (push) Waiting to run
CI / build-and-test (windows-latest) (push) Waiting to run
CI / lint (push) Waiting to run
Reviewed-on: #32
2026-04-09 11:56:24 +00:00
Nick Tabeling
aad45b9637 fix: remove --bare flag to restore OAuth login for Claude Pro/Max
Some checks failed
CI / build-and-test (ubuntu-latest) (pull_request) Has been cancelled
CI / build-and-test (windows-latest) (pull_request) Has been cancelled
CI / lint (pull_request) Has been cancelled
--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 <noreply@anthropic.com>
2026-04-09 13:48:45 +02:00
3 changed files with 27 additions and 23 deletions

View file

@ -163,19 +163,17 @@ export async function runAgent(options: RunAgentOptions): Promise<string> {
} }
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
// IMPORTANT: --bare prevents CLAUDE.md walk-up through parent directories. // NOTE: --bare is intentionally NOT used here. --bare disables OAuth/keychain
// Without this flag every agent inherits the DisClaw project CLAUDE.md as // auth and requires ANTHROPIC_API_KEY, which breaks Claude Pro/Max subscriptions.
// its identity (and any other CLAUDE.md files up the directory tree). // CLAUDE.md isolation is handled structurally: workspaces live under
// --append-system-prompt-file injects the workspace-specific CLAUDE.md // ~/.disclaw/workspaces/ (outside the DisClaw repo), so walk-up will never
// explicitly so each agent keeps its own isolated identity. // reach the DisClaw project CLAUDE.md. The workspace CLAUDE.md is picked up
// automatically because cwd is set to workspacePath.
const args = [ const args = [
"-p", "-p",
prompt, prompt,
"--output-format", "--output-format",
"json", "json",
"--bare",
"--append-system-prompt-file",
path.join(workspacePath, "CLAUDE.md"),
]; ];
const child = spawn(claudeCommand, args, { const child = spawn(claudeCommand, args, {

View file

@ -4,9 +4,12 @@ import * as os from "os";
import * as path from "path"; import * as path from "path";
/** /**
* Integration test: verifies that every Claude CLI invocation includes * Integration test: verifies that Claude CLI invocations use the correct args.
* --bare (prevents CLAUDE.md walk-up through parent directories) and *
* --append-system-prompt-file pointing to the workspace CLAUDE.md. * 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 * We mock cross-spawn so no real process is launched, and capture the exact
* args array that runner.ts would pass to the CLI. * 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({ await runAgent({
workspacePath: workspaceDir, workspacePath: workspaceDir,
channelName: "test-channel", channelName: "test-channel",
@ -124,10 +127,10 @@ describe("no-parent-claude-md-leak", () => {
conversationHistory: [], 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({ await runAgent({
workspacePath: workspaceDir, workspacePath: workspaceDir,
channelName: "test-channel", channelName: "test-channel",
@ -135,12 +138,12 @@ describe("no-parent-claude-md-leak", () => {
conversationHistory: [], conversationHistory: [],
}); });
const idx = capturedArgs.indexOf("--append-system-prompt-file"); // Required args for non-interactive JSON output
expect(idx).toBeGreaterThanOrEqual(0); expect(capturedArgs).toContain("-p");
expect(capturedArgs).toContain("--output-format");
expect(capturedArgs).toContain("json");
const systemPromptFile = capturedArgs[idx + 1]; // CLAUDE.md is auto-discovered via cwd (workspacePath), no flag needed
expect(systemPromptFile).toMatch(/CLAUDE\.md$/); expect(capturedArgs).not.toContain("--append-system-prompt-file");
// Must point to the workspace-specific CLAUDE.md, not a parent directory
expect(systemPromptFile).toBe(path.join(workspaceDir, "CLAUDE.md"));
}); });
}); });

View file

@ -55,10 +55,13 @@ describe("sanitizedEnv", () => {
expect(result["SOME_SAFE_VAR"]).toBe("safe-value"); 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(); const result = sanitizedEnv();
expect(result["CI"]).toBe("true"); expect(result["CI"]).toBeUndefined();
expect(result["DISCLAW_AGENT"]).toBe("1"); expect(result["DISCLAW_AGENT"]).toBe("1");
}); });
@ -91,7 +94,7 @@ describe("sanitizedEnv", () => {
expect(result["ANOTHER_KEY"]).toBeUndefined(); expect(result["ANOTHER_KEY"]).toBeUndefined();
// Extras and standard vars should still be present // Extras and standard vars should still be present
expect(result["DISCLAW_AGENT_NAME"]).toBe("test-agent"); 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", () => { it("does not mutate process.env", () => {