Skip to content

fix(pr-review): checkout PR head SHA on pull_request_target (#197)#198

Closed
cbeaulieu-gt wants to merge 3 commits intomainfrom
fix-197-pr-review-checkout-head
Closed

fix(pr-review): checkout PR head SHA on pull_request_target (#197)#198
cbeaulieu-gt wants to merge 3 commits intomainfrom
fix-197-pr-review-checkout-head

Conversation

@cbeaulieu-gt
Copy link
Copy Markdown
Member

Summary

  • pr-review/action.yml's actions/checkout@v5 step was missing a ref: parameter, causing it to check out the base ref on pull_request_target events — PR-only files were absent from the working tree
  • Added ref: ${{ github.event.pull_request.head.sha }} with an inline security audit comment explaining why this is safe for this specific composite action
  • Added a new key convention to CLAUDE.md documenting the pull_request_target + head-SHA checkout pattern for future reference

Root cause

On pull_request_target triggers, actions/checkout@v5 defaults to checking out the base ref (e.g. main), not the PR branch. This means files added or modified in the PR do not exist in the working tree.

claude-code-action@v1's track_progress: true feature runs git hash-object against the list of PR-changed files to establish a baseline before the Claude session starts. When a file exists in the PR diff but not on disk (because we checked out main), git hash-object fails. The action then attempts a git fetch origin --depth=20 <head-branch> recovery — which also fails inside the digest-pinned review container because the container's git config does not have access to origin in the same way a full runner environment does.

Adding ref: ${{ github.event.pull_request.head.sha }} ensures the head ref is present in the working tree, making both git hash-object and the fetch recovery unnecessary.

Security analysis

Checking out the head SHA on pull_request_target is the canonical GitHub Actions footgun: fork code lands on disk while the job runs with repository secrets. This is safe here because:

Pre-authz step Executes working-tree content?
actions/checkout@v5 No — just fetches; no script invocation
Resolve diff base No — pure gh api calls + shell arithmetic on env vars
Set diff scope No — pure shell string formatting
Compute effective max_turns No — gh pr view API call + arithmetic
Check author association No — reads github.event.* env vars + posts a gh pr comment
Check PR size Gated: if: steps.authz.outputs.skip != 'true'

The claude-code-action@v1 step — the only step that actually interprets working-tree content — is gated behind if: steps.authz.outputs.skip != 'true' && steps.size-check.outputs.skip != 'true', which only passes for OWNER/MEMBER/COLLABORATOR PRs. Fork PRs from untrusted authors hit the gh pr comment in the authz step and all downstream steps are skipped.

Test plan

  • actionlint run against all .github/workflows/*.yml — passes clean (composite action.yml files are outside actionlint's scope by design)
  • git diff --stat confirms exactly 2 files changed (pr-review/action.yml and CLAUDE.md)
  • Dogfood verification: this PR's own claude-pr-review check should exercise the fixed path — the pull_request_target trigger on this PR will check out the head SHA and track_progress should no longer fail with git hash-object / git fetch errors
  • Verify the claude-pr-review/quality-gate commit status posts correctly on the head SHA after the review completes

Closes #197

🤖 Generated by Claude Code on behalf of @cbeaulieu-gt

The composite action's actions/checkout@v5 step defaulted to the base
ref on pull_request_target, so PR-only files were absent from the
working tree. claude-code-action@v1's track_progress setup runs
git hash-object against changed files and falls back to git fetch
when files are missing — both fail inside the digest-pinned review
container. Adding ref: head.sha makes the head ref present and bypasses
the failing fetch recovery path entirely.

Security: the pull_request_target + checkout-head pattern is normally
dangerous (fork code on disk + privileged secrets), but this composite
action's pre-authz steps all use gh API calls or pure shell on event
payload env vars — none execute content from the working tree. The
claude-code-action step itself is gated behind the existing author
association check.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

Claude Code is working…

I'll analyze this and get back to you.

View job run

The digest-pinned claude-runtime-review container runs as UID 1001
while actions/checkout writes files as the host runner UID. Inside
the container, git's CVE-2022-24765 protection rejects the workspace
as 'dubious ownership' and refuses to operate. This blocks
claude-code-action@v1's track_progress setup, which runs git
internally.

Adding `safe.directory $GITHUB_WORKSPACE` before any git invocation
makes git trust the workspace regardless of UID drift. Surfaced
after the first commit's `ref:` fix moved the failure past the
missing-head-ref point.

The same UID mismatch is latent in every other Phase 5 container-
pinned reusable workflow; durable fix (bake `safe.directory *` into
the runtime base image entrypoint) tracked as a follow-up issue.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

Claude Code is working…

I'll analyze this and get back to you.

View job run

…#197)

The previous commit's --global write went to $HOME/.gitconfig, but
claude-code-action's bun-invoked TS entrypoint resolves $HOME
differently than this shell step, so the exemption was invisible to
the actual git invocation that fails. PR #198 run 25401775107 logs
showed the --global command executing successfully but the next
claude-code-action git call still tripping dubious-ownership.

Switch to --system (writes /etc/gitconfig, no $HOME dependency) and
widen the path to '*'. Container runs as root by default per
runtime/base/Dockerfile (no USER directive), so --system writes
succeed.

The durable fix is to bake this into the runtime base image's
Dockerfile — tracked as #199.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@github-actions
Copy link
Copy Markdown

github-actions Bot commented May 5, 2026

Claude Code is working…

I'll analyze this and get back to you.

View job run

@cbeaulieu-gt
Copy link
Copy Markdown
Member Author

Closing as superseded by #199.

The three commits on this branch correctly diagnosed the bug in layers — ref: missing on pull_request_target, then container UID mismatch, then --global vs --system HOME-resolution drift — but PR #198 cannot be empirically validated in this repo. The dogfood (claude-pr-review.yml) calls glitchwerks/github-actions/pr-review@v2 (the released tag), not the PR branch, per the explicit "Dogfooding limitation" in CLAUDE.md. Every commit on this branch passed actionlint but the runtime fix shape was untested by CI.

#199 captures the durable fix: bake `git config --system --add safe.directory ''` into `runtime/base/Dockerfile` so every overlay inherits it. STAGE 4 overlay smoke validates it end-to-end without the dogfooding limitation. The diagnostic narrative from this branch's three commits is preserved in #199's body as the spec-by-exhaustion of why `--system`/`` is the correct shape.

Branch will be deleted; the diagnosis lives in the issue thread.

🤖 Generated by Claude Code on behalf of @cbeaulieu-gt

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

bug: claude-code-action git fetch fails inside pull_request_target containerized review

1 participant