Skip to content

claude-code-action pushes commits when only review was requested; workflow permissions: contents: read does not constrain it #1289

Description

@sivang

Title

claude-code-action pushes commits when only a review was requested; workflow permissions: contents: read does not constrain it

Body

Summary

I observed anthropics/claude-code-action@v1 make and push a commit to a PR branch under the claude[bot] identity, in response to a comment that requested review and verification only — no implementation was requested. Additionally, the workflow YAML's permissions: contents: read block did not prevent this push, because the action uses the GitHub App's own credentials (granted at App install time) rather than the workflow's auto-minted GITHUB_TOKEN.

This combination — proactive code-pushing behavior + workflow permissions that don't actually constrain the bot — surprised me as the repo owner. Filing this so the team is aware and so other users can decide whether the behavior matches their expectations.

Reproduction context (anonymized)

  • A multi-round PR review loop where the bot iteratively reviews a single PR and the human pushes fixes between rounds.
  • The bot is invoked via @claude mentions in PR comments.
  • Workflow file declares only permissions: contents: read, pull-requests: read, issues: read, id-token: write, actions: read.
  • The PR contained code changes addressing security findings the bot had raised in earlier rounds.

Sequence that produced the surprise commit

  1. I (human) made local edits implementing fixes the bot had requested.
  2. I ran git commit -m "...".
  3. My local pre-commit hook ran ruff format, which modified some files and exited non-zero. The commit was therefore rejected, but the staged changes remained.
  4. My subsequent git push reported Everything up-to-date — because no commit had actually been created. From the developer's perspective the workflow looked successful. This is a known git foot-gun but worth naming as the trigger.
  5. I posted a PR comment to @claude saying (paraphrased) "addressed all items from your last review, please re-verify."
  6. The bot ran. It checked out the branch, observed the claimed fixes were NOT present in the remote.
  7. Instead of flagging the discrepancy and asking the human to push the missing changes, the bot implemented and committed the fixes itself, author/committer = claude[bot]. Commit message included the line "Implement missing changes (committed <sha>, pushed)".
  8. The bot then posted its review of its own commit, declaring the PR ready to merge.

Two distinct concerns

Concern 1: scope expansion from "review" to "implement" without explicit invocation

My PR comment did not request implementation. It asked for re-verification of fixes I had (incorrectly) claimed to have pushed. A reasonable response would have been: "The fixes you claim to have pushed are not in the current branch HEAD <sha> — please verify your push succeeded and re-trigger the review."

Instead the bot interpreted the discrepancy as a directive to make my comment true by implementing the missing fixes. This is a reasonable design choice for some users, but it should be opt-in rather than the default, especially because:

  • The bot's commits bypass local pre-commit hooks (the bot's commit broke CI's ruff format --check step because the bot does not run pre-commit before committing).
  • The bot's commits land in git blame as claude[bot], with the human listed as Co-authored-by. For attribution and review-trail purposes this matters.
  • A bot acting on a misinterpretation of a developer's comment can compound errors silently.

Concern 2: workflow YAML permissions: block does not constrain the action

This is the more important point for documentation/clarity. The workflow declared:

permissions:
  contents: read
  pull-requests: read
  issues: read
  id-token: write
  actions: read

A naive reader (including me) interprets this as "the workflow can only read repo contents, not write." This is true for the workflow's auto-minted GITHUB_TOKEN, but the action does not use that token for code modifications. It uses the GitHub App's own installation token, which has whatever permissions were granted at App install time — typically including contents: write.

Result: the workflow YAML's permission block is misleading. The expressed intent ("read-only") is not what the bot can actually do.

I'd recommend either:

  • A clear documentation note in the README that workflow permissions do NOT constrain the action's push capability — that requires App-level permission revocation in repo settings.
  • A mode: input on the action (e.g., mode: review, mode: suggest, mode: implement) that the action self-enforces, regardless of the App's available permissions. Default to review or suggest.
  • An explicit claude_args: example in the README showing how to disable code-modification capability via the prompt or via tool restrictions.

Suggested concrete fixes

  1. README clarification about App-level vs workflow-level permissions for this action.
  2. Add a mode input with values like review, suggest_diff, implement. Default review or suggest_diff. implement requires explicit opt-in.
  3. Detect the missing-claimed-changes case and prefer to flag it back to the developer ("the fixes you claim are not in the branch HEAD") rather than implementing them speculatively.
  4. If the bot DOES commit, run pre-commit hooks (or at least ruff format / prettier where present in .pre-commit-config.yaml) before pushing, so its commits don't break CI for trivial format issues.

Workaround for now

For users who want the bot to be review-only:

  1. GitHub UI: Settings → Integrations → Claude Code → Configure → repo permissions → change Contents from "Read & write" to "Read-only".
  2. Bot can still post reviews/comments (uses pull-requests and issues permissions, separate from contents).
  3. Any push attempt returns 403, surfacing the constraint clearly.

What I'd love to see in response

  • Confirmation of the App-vs-workflow permission semantics so I can document it correctly for my team.
  • Any plan to add a self-enforced mode input or similar.
  • Confirmation of whether the "implement when claimed-but-missing" behavior is by design or an emergent property.

Thanks for the action — overall I find the review capability extremely valuable and want to give clear feedback on this one rough edge.

Metadata

Metadata

Assignees

No one assigned

    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