Repository navigation
fix: headless permission denials in coder (autopilot mode) and judge (tool isolation) - #1114
Closed
Cedric Vidal (cedricvidal) wants to merge 3 commits into
Closed
Cedric Vidal (cedricvidal) wants to merge 3 commits into
Cedric Vidal (cedricvidal) wants to merge 3 commits into
Conversation
…s execution
The Copilot CLI replaced the bypassPermissions session mode with
agent/plan/autopilot. The worker only set the mode when bypassPermissions
was advertised, so sessions stayed in the default agent mode where
execute/bash tool calls (e.g. npm run build) are denied non-interactively.
This caused the build gate to fail all iterations in integration. The
--yolo CLI flag does not change the ACP session mode.
Add a selectPermissionMode helper that switches the session into autopilot
mode, matched by its canonical ACP URL id (with an endsWith("#autopilot")
safety net), and replace the dead bypassPermissions block. Includes unit
tests and updated worker docs.
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
The first session/set_mode call right after session creation can fail transiently (cold start), and the rejection is a JSON-RPC object rather than an Error, so the previous log showed an opaque [object Object]. Add a single retry on failure and a formatModeError() helper that serializes non-Error rejections (message + code, or JSON) so failures are diagnosable. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
…ion denial The judge's CopilotClient was created without a mode, defaulting to mode: "copilot-cli", which injects the built-in CLI tools (bash, edit, view, ...) into the session. Those built-ins are not skipPermission, so when the headless judge model chose to run a build/test command itself instead of reading the coder's captured output, the runtime denied it with "could not request permission from user" and the judge mis-reported that as the coder's result - a bogus, non-deterministic permission-denied failure even when the coder's command actually succeeded. Restrict every judge session to its own custom tools via availableTools: ["custom:*"] and excludedTools: ["builtin:*", "mcp:*"] (excludedTools always wins), extracted into a testable buildSessionConfig helper. The judge keeps its 6 read-only skipPermission tools and is forced to evaluate from read_tool_outputs. Closes #1117 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Collaborator
Author
|
Original author: @github-actions[bot] Test Results (Node.js 22)test: Run #1957
🎉 All tests passed! |
Collaborator
Author
|
Original author: @github-actions[bot] Test Results (Node.js 22)test: Run #1959
🎉 All tests passed! |
Collaborator
Author
|
Original author: @github-actions[bot] Test Results (Node.js 22)test: Run #1962
🎉 All tests passed! |
This was referenced Jul 29, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Original author: Cedric Vidal (@cedricvidal)
Why
Headless "Permission denied and could not request permission from user" errors were breaking benchmark runs in two distinct places. This PR fixes both. See issue #1104 (coder) and issue #1117 (judge).
Fix 1 - coder gets stuck in the build gate (#1104)
In integration,
coder-acp-copilotruns failed thebuildgate: everyexecute/bash tool call (e.g.npm run build) was rejected non-interactively, so the gate exhausted all iterations, failed, and the downstreamtestgate was skipped.Root cause. The Copilot CLI replaced its old
bypassPermissionsACP session mode withagent/plan/autopilot. The worker only set the session mode whenbypassPermissionswas advertised, so the session stayed in the defaultagentmode, where execute/bash calls are denied non-interactively. The--yoloCLI flag does not change the ACP session mode, so it was not enough on its own.Verified against Copilot CLI 1.0.64 by probing it directly over ACP:
...#agent,...#plan,...#autopilot). Autopilot "enables allow-all and runs until task completion without user interaction."--yolo, the session'scurrentModeIdis still...#agent.session/set_modewith the autopilot URL id returns success.Approach. Add an exported
selectPermissionModehelper (mirroringselectModel) that switches the session into autopilot aftersession/new. It matches autopilot by its full canonical URL id, with anendsWith("#autopilot")safety net, warns and continues if the mode is absent, and is resilient tosetSessionModethrowing. A single cold-start retry plus aformatModeErrorhelper give clear diagnostics instead of[object Object]. The deadbypassPermissionsblock is removed.Scope note: this intentionally drops backward compatibility with older Copilot CLI versions that exposed
bypassPermissions; we target the current CLI'sautopilotmode only.Fix 2 - judge reports a bogus permission denial (#1117)
After Fix 1, the coder's commands ran fine, but the
testgate still intermittently failed with a "Permission denied" message coming from the judge's feedback. Ground-truth capturedtool-calls.jsonlproved the coder'snpm testactually succeeded in every iteration - the denial was the judge's own.Root cause. The judge's
CopilotClientis created with nomode, so the SDK defaults tomode: "copilot-cli", which injects the built-in CLI tools (bash,edit,view, ...) into the session. Those built-ins are notskipPermission. When the headless judge model chose to run a build/test command itself instead of reading the coder's captured output, the runtime denied it ("could not request permission from user") and the judge mis-reported that as the coder's result - a non-deterministic failure even when the coder succeeded.Approach. Restrict every judge session to its own custom tools via
availableTools: ["custom:*"]andexcludedTools: ["builtin:*", "mcp:*"](excludedTools always wins), extracted into a testablebuildSessionConfighelper. The judge keeps its 6 read-onlyskipPermissiontools and is forced to evaluate fromread_tool_outputs. Chose tool filtering over switching the client tomode: "empty"(the latter needs extra config and changes more defaults) as the surgical fix that directly closes the denial path.Verification
Reproduced end to end against a local stack by retrying the original failing run:
Tests and docs
apps/workers/coder-acp-copilot/src/acp-client.test.ts- 25 tests (autopilot by URL id,endsWithsafety net, absent/empty modes, resilient to throw, retry-then-succeed,formatModeErrorserialization).apps/judge/src/judge-strategies.test.ts- 16 tests, 5 new (restricts tocustom:*, excludesbuiltin:*/mcp:*, never exposesbash, replace-mode prompt, exported constants).coder-acp-copilot,judge).docs/architecture/worker-requirements.md+worker-compliance.md(autopilot mode governs headless execution, not--yolo);docs/design/gates.md(judge tool isolation).Closes #1104
Closes #1117