Compare commits

..

No commits in common. "4661892820bb8c192d5cae8cb74570af86d416bb" and "f27af2401aad48de3af6cfc00ffb96ee2f7ba945" have entirely different histories.

3 changed files with 23 additions and 27 deletions

View file

@ -163,17 +163,19 @@ export async function runAgent(options: RunAgentOptions): Promise<string> {
} }
return new Promise<string>((resolve, reject) => { return new Promise<string>((resolve, reject) => {
// NOTE: --bare is intentionally NOT used here. --bare disables OAuth/keychain // IMPORTANT: --bare prevents CLAUDE.md walk-up through parent directories.
// auth and requires ANTHROPIC_API_KEY, which breaks Claude Pro/Max subscriptions. // Without this flag every agent inherits the DisClaw project CLAUDE.md as
// CLAUDE.md isolation is handled structurally: workspaces live under // its identity (and any other CLAUDE.md files up the directory tree).
// ~/.disclaw/workspaces/ (outside the DisClaw repo), so walk-up will never // --append-system-prompt-file injects the workspace-specific CLAUDE.md
// reach the DisClaw project CLAUDE.md. The workspace CLAUDE.md is picked up // explicitly so each agent keeps its own isolated identity.
// 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,12 +4,9 @@ import * as os from "os";
import * as path from "path"; import * as path from "path";
/** /**
* Integration test: verifies that Claude CLI invocations use the correct args. * Integration test: verifies that every Claude CLI invocation includes
* * --bare (prevents CLAUDE.md walk-up through parent directories) and
* CLAUDE.md isolation is achieved structurally (workspaces live outside the * --append-system-prompt-file pointing to the workspace CLAUDE.md.
* 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.
@ -119,7 +116,7 @@ describe("no-parent-claude-md-leak", () => {
} }
}); });
it("does NOT pass --bare flag (--bare disables OAuth and breaks Claude Pro/Max)", async () => { it("passes --bare flag to the Claude CLI", async () => {
await runAgent({ await runAgent({
workspacePath: workspaceDir, workspacePath: workspaceDir,
channelName: "test-channel", channelName: "test-channel",
@ -127,10 +124,10 @@ describe("no-parent-claude-md-leak", () => {
conversationHistory: [], conversationHistory: [],
}); });
expect(capturedArgs).not.toContain("--bare"); expect(capturedArgs).toContain("--bare");
}); });
it("passes -p and --output-format json; does NOT pass --append-system-prompt-file", async () => { it("passes --append-system-prompt-file pointing to workspace CLAUDE.md", async () => {
await runAgent({ await runAgent({
workspacePath: workspaceDir, workspacePath: workspaceDir,
channelName: "test-channel", channelName: "test-channel",
@ -138,12 +135,12 @@ describe("no-parent-claude-md-leak", () => {
conversationHistory: [], conversationHistory: [],
}); });
// Required args for non-interactive JSON output const idx = capturedArgs.indexOf("--append-system-prompt-file");
expect(capturedArgs).toContain("-p"); expect(idx).toBeGreaterThanOrEqual(0);
expect(capturedArgs).toContain("--output-format");
expect(capturedArgs).toContain("json");
// CLAUDE.md is auto-discovered via cwd (workspacePath), no flag needed const systemPromptFile = capturedArgs[idx + 1];
expect(capturedArgs).not.toContain("--append-system-prompt-file"); 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"));
}); });
}); });

View file

@ -55,13 +55,10 @@ describe("sanitizedEnv", () => {
expect(result["SOME_SAFE_VAR"]).toBe("safe-value"); expect(result["SOME_SAFE_VAR"]).toBe("safe-value");
}); });
it("(c) sets DISCLAW_AGENT=1 but does NOT set CI=true (breaks Claude Code OAuth)", () => { it("(c) sets CI=true and DISCLAW_AGENT=1 as mandatory markers", () => {
// 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"]).toBeUndefined(); expect(result["CI"]).toBe("true");
expect(result["DISCLAW_AGENT"]).toBe("1"); expect(result["DISCLAW_AGENT"]).toBe("1");
}); });
@ -94,7 +91,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["DISCLAW_AGENT"]).toBe("1"); expect(result["CI"]).toBe("true");
}); });
it("does not mutate process.env", () => { it("does not mutate process.env", () => {