Skip to content

Copilot reviews lose effectiveness due to permission denials in non-interactive mode #555

@cderv

Description

@cderv

I've been using copilot as one of my review agents for a while and only recently noticed the reviews felt shallow compared to other agents. Looking at the job output more carefully, I found the copilot CLI is hitting permission denials on most of its tool calls when run by the daemon.

Here's a trimmed example from a recent review:

✓ git --no-pager show --name-status --format=fuller --no-patch b75995e
  24 lines...

✗ Get changed file paths and statuses (shell)
  git --no-pager diff-tree --no-commit-id --name-status -r b75995e
  Permission denied and could not request permission from user

✗ Validate JSONL and list new issues (shell)
  python -c "import subprocess,json,sys; ..."
  Permission denied and could not request permission from user

✓ Show diff for issues jsonl (shell)
  git --no-pager show b75995e -- .beads/issues.jsonl
  141 lines...

✗ Check issues jsonl validity and duplicates (shell)
  python -c "import json,subprocess,collections; ..."
  Permission denied and could not request permission from user

About half the tool calls fail. The first git command succeeds (likely auto-approved by copilot's defaults), but subsequent shell commands get denied because there's no user to approve them in the daemon context.

Looking at internal/agent/copilot.go, the current implementation passes no permission or non-interactive flags — it pipes the prompt via stdin and runs with default permissions:

cmd := exec.CommandContext(ctx, a.Command, args...)
cmd.Stdin = strings.NewReader(prompt)

What the copilot CLI supports

The copilot CLI has several capabilities relevant to non-interactive use (from copilot --help, copilot help permissions, and the GitHub docs):

  • -p/--prompt for non-interactive mode ("exits after completion")
  • -s/--silent to suppress stats, "useful for scripting with -p"
  • --allow-all-tools to auto-approve everything (the help says this is "required for non-interactive mode")
  • Granular --allow-tool patterns, e.g. --allow-tool='shell(git:*)' to allow git commands specifically
  • --deny-tool to block specific commands (deny takes precedence over allow)
  • --available-tools to limit which tools the model can even see

The granular permission system is interesting because roborev's daemon is read-only by design. Something like --allow-tool='shell(git:*)' with --deny-tool='shell(git push)' could be a safer fit than blanket --allow-all-tools.

No per-repo permission config on copilot's side

Unlike Claude Code's .claude/settings.json which supports per-project permission overrides, the copilot CLI has no repo-level configuration for tool permissions. The closest repo-level files are .github/copilot-instructions.md (prompts, not permissions), .github/hooks/*.json (lifecycle hooks), and AGENTS.md (agent instructions) — none of which can set --allow-tool/--deny-tool defaults.

Permission configuration in copilot is strictly CLI flags at invocation time, the COPILOT_ALLOW_ALL env var, or interactive session prompts. So roborev would need to be the one passing these flags — users can't configure them on the copilot side per-repo. Something in .roborev.toml to expose agent-specific CLI flags could let users tune this per project.

Open questions

  • Does stdin piping work as a prompt source, or is -p needed for proper non-interactive behavior? The help says -p "exits after completion" which suggests it changes the execution mode, not just the input source.
  • What's the right default permission set for a read-only review workflow?

Environment

  • roborev v0.45.1
  • copilot 1.0.10
  • Windows 11, Git Bash

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions