Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 10 additions & 3 deletions packages/loopover-engine/src/miner/cli-subprocess-driver.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,22 @@ const CODING_AGENT_ENV_ALLOWLIST = [
* - `--output-format json` produces a single parseable JSON result on stdout, matching `claudeErrorStatus`'s
* own `JSON.parse(stdout.trim())` assumption (which was already written for this shape, just never
* actually triggered because this flag was never passed).
* - `--permission-mode acceptEdits` is the same edit-permission scope the Agent-SDK driver already uses
* (#4267) -- file edits run unattended inside the scoped worktree, nothing broader.
* - `--permission-mode acceptEdits` alone (the CLI's own edit-permission scope, matching the label the
* Agent-SDK driver's `permissionMode` also uses, #4267) only auto-approves file EDIT tool calls -- it
* does NOT grant `Read` or `Bash`, so a real task (which needs both to explore the repo and run tests)
* got every one of those calls denied in a headless `--print` invocation with no TTY to resolve an
* interactive prompt, silently producing zero real work every time (#6840). `--allowedTools Read Bash`
* grants exactly the two additional tool classes the task actually needs, confirmed via a live
* end-to-end reproduction (zero denials, a real fix + test committed) -- deliberately narrower than
* `--permission-mode bypassPermissions`, which also disables edit-scope confirmation and any other
* safety rail the CLI has, not just the Read/Bash gap this task actually hits.
* There is no turn-budget flag on the real CLI (verified via `claude --help`) and no acceptance-criteria
* flag either -- the coding agent discovers `task.acceptanceCriteriaPath` itself via its own Read tool
* inside the scoped working directory, exactly like the Agent-SDK driver already does (agent-sdk-driver.ts
* never passes it as a distinct option either; only `task.instructions` is forwarded as the prompt there
* too). The wall-clock `timeoutMs` (already implemented) is this provider's only real turn/cost ceiling. */
export function defaultClaudeCliArgs(task: CodingAgentDriverTask): string[] {
return ["--print", "--output-format", "json", "--permission-mode", "acceptEdits", task.instructions];
return ["--print", "--output-format", "json", "--permission-mode", "acceptEdits", "--allowedTools", "Read", "Bash", "--", task.instructions];
}

/** Real, verified `codex exec` non-interactive argv (confirmed against `codex exec --help`, #5135
Expand Down
14 changes: 14 additions & 0 deletions test/unit/cli-subprocess-driver.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -41,10 +41,24 @@ describe("createCliSubprocessCodingAgentDriver (#4266)", () => {
"json",
"--permission-mode",
"acceptEdits",
"--allowedTools",
"Read",
"Bash",
"--",
"Fix the pagination bug.",
]);
});

it("#6840: grants Read and Bash in addition to acceptEdits -- acceptEdits alone denies every Read/Bash tool call, silently blocking all real work", async () => {
const { spawn, calls } = fakeSpawn({ stdout: "done", code: 0 });
const driver = createCliSubprocessCodingAgentDriver({ command: "claude", spawn });
await driver.run(TASK);
const args = calls[0]?.args ?? [];
const allowedToolsIndex = args.indexOf("--allowedTools");
expect(allowedToolsIndex).toBeGreaterThan(-1);
expect(args.slice(allowedToolsIndex + 1, allowedToolsIndex + 3)).toEqual(["Read", "Bash"]);
});

it("uses codex's own real default argv (the exec subcommand, not claude's flags)", async () => {
const { spawn, calls } = fakeSpawn({ stdout: "done", code: 0 });
const driver = createCliSubprocessCodingAgentDriver({ command: "codex", spawn });
Expand Down