Skip to content

[Bug]: Read-only git branch listing is blocked as mutation / 只读分支查询被误判为修改 #8406

Description

@SivanCola

Version line

v2 — Go rewrite (1.x), main-v2 (active development)

Exact version

A source build from main-v2 at or after merge commit 75537490abf2c8bfe18c431531676f7d530d9f73 (PR #8275).

What happened?

In a turn whose frozen TaskPolicy forbids workspace mutation (for example Plan mode, or a user request such as “read-only analysis; do not modify files”), the CLI blocks read-only branch inspection:

git branch -a

The command only lists local and remote-tracking branches, but it is classified as a workspace mutation and rejected before execution:

blocked: task policy forbids mutation

If the model sent later mutation/verification calls in the same provider batch, the dependency barrier may also report:

blocked: skipped because an earlier modification in this tool batch failed or was blocked

Expected behavior: branch-listing forms should be classified as workspace-non-mutating and remain available in read-only/Plan turns. Branch creation, deletion, rename, copy, forced ref movement, and upstream mutation must remain blocked or require the normal permission flow.

Root cause

The Go v2 classifier deliberately omits git branch from shellsafe.ReadOnlyPrefixes["git"] because the subcommand can mutate refs. evidence.ToolCallMutates reuses that conservative result through bashMayMutate; therefore it cannot distinguish git branch -a from git branch -D feature. PR #8275 then enforces TaskPolicy.ForbidMutation against that coarse classification, turning the false positive into a hard block.

This is a classification-boundary issue, not a request to broadly allow git branch. Permission auto-approval may remain conservative; workspace mutation accounting needs an argument-aware, host-proven listing classification.

Suggested fix

Add an argument-aware gitBranchIsListing classifier, analogous to the existing gitTagIsListing handling, and use it in the shared shell/workspace mutation path.

Allow only statically proven listing forms, including:

  • git branch
  • git branch -a / git branch --all
  • git branch -r / git branch --remotes
  • git branch --list [pattern...]
  • git branch --show-current

Keep fail-closed behavior for dynamic or malformed arguments and for mutating forms, including:

  • branch creation: git branch feature [start-point]
  • deletion: -d, -D, --delete
  • move/copy: -m, -M, -c, -C
  • forced ref movement: -f, --force
  • upstream/description mutations: --set-upstream-to, --unset-upstream, --edit-description

Compound commands must still be evaluated segment-by-segment; one unknown or mutating segment keeps the entire call mutating.

Regression coverage

Please cover the classification at all affected boundaries:

  • internal/shellsafe: listing vs mutating argument matrix
  • internal/permission: safe listing forms do not weaken destructive-argument handling
  • internal/evidence: ToolCallMutates("bash", ...)
  • internal/agent: TaskPolicy no-mutation turn allows listing and still blocks writers
  • batch behavior: an allowed git branch -a must not open the mutation dependency barrier

Related

Steps to reproduce

  1. Start the v2 CLI in a Git repository.
  2. Enter Plan mode, or ask for read-only analysis without workspace modifications.
  3. Ask the model to inspect all local and remote branches.
  4. Let it call git branch -a.
  5. Observe blocked: task policy forbids mutation.

The same false positive is visible when the listing call is embedded in an otherwise read-only inspection chain.

OS / platform

macOS (the exact OS version was not shown in the reproduction).

Relevant logs or output

Bash(git branch -a)
blocked: task policy forbids mutation

bash dependency · not executed
blocked: skipped because an earlier modification in this tool batch failed or was blocked

Metadata

Metadata

Assignees

No one assigned

    Labels

    agentCore agent loop (internal/agent, internal/control)bugSomething isn't workingtuiTerminal UI / CLI (internal/cli, internal/control)v2Go rewrite (1.x) — main-v2 branch, active development

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions