anthropics / anthropics/claude-code-action

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

Open
#1,289 0 comments 0 reactions 0 assignees View on GitHub
Dominant language
TypeScript
Stars
8.9k
Forks
2.1k
Avg merge
3d 9h
Merged PRs (30d)
10

Description

# 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 ``, 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 `` — 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:

```yaml
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.

Contributor guide

Open the contributing guide

Research direction

Start by reading the README permission guidance and the workflow YAML examples, then trace how the action handles review comments, App credentials, and pushes. Check .pre-commit-config.yaml for the formatting context. Done means the permission behavior and review-only expectations are documented or explicitly enforced, with the proposed scope agreed before implementation.

Written by the indexing model from the issue text.

Assessment

Tech stack
github-actions, typescript
Domain
ci-cd, documentation, security
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Mostly clear
Newbie friendliness
38/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.