From d5e6af587476fa6e4e2ba4be1417bd30cf06c018 Mon Sep 17 00:00:00 2001 From: Bryan Cox Date: Thu, 25 Jun 2026 08:48:54 -0400 Subject: [PATCH] fix(ci): prevent Claude from breaking push credentials in fork PRs The address-review-comments workflow generates a GitHub App token for pushing to hypershift-community/hypershift fork PRs. actions/checkout configures the credential helper via persist-credentials. However, Claude was overwriting the remote URL with $GH_TOKEN on push timeouts, destroying the working credential config and causing all subsequent pushes to fail with auth errors. Add a verification step after checkout to fail fast if credentials are broken, expose PUSH_TOKEN/PR_REPO/PR_BRANCH env vars so Claude has the correct token available, and add a Claude rule file that instructs it to never modify the git remote URL in CI. Co-Authored-By: Claude Opus 4.6 --- .claude/rules/ci-git-push.md | 25 ++++++++++++++++++++ .github/workflows/reusable-claude-on-pr.yaml | 10 ++++++++ 2 files changed, 35 insertions(+) create mode 100644 .claude/rules/ci-git-push.md diff --git a/.claude/rules/ci-git-push.md b/.claude/rules/ci-git-push.md new file mode 100644 index 000000000000..d6474fa4df12 --- /dev/null +++ b/.claude/rules/ci-git-push.md @@ -0,0 +1,25 @@ +--- +description: Git push rules for CI environments (GitHub Actions workflows) +globs: +alwaysApply: true +--- + +# Git Push in CI + +When running inside GitHub Actions (the `CI` or `GITHUB_ACTIONS` environment variable is set): + +1. **NEVER modify the git remote URL.** The `actions/checkout` step configures credentials via `persist-credentials: true`. Running `git remote set-url` destroys this configuration. + +2. **NEVER embed tokens in URLs.** Do not use `https://x-access-token:$TOKEN@github.com/...` as a remote URL. The credential helper is already configured. + +3. **Just push directly.** Use `git push origin ` — authentication is handled automatically by the credential helper that `actions/checkout` configured. + +4. **If a push times out, retry with a longer timeout** (e.g., `timeout 300`). Do not assume a timeout is an authentication failure. + +5. **If a push genuinely fails with an auth error**, reconfigure the credential helper using `$PUSH_TOKEN` (not `$GH_TOKEN`): + ``` + git config --local credential.helper '!f() { echo "username=x-access-token"; echo "password=$PUSH_TOKEN"; }; f' + ``` + `$PUSH_TOKEN` is the correct token for the PR's fork. `$GH_TOKEN` only has access to the upstream repo. + +6. **Use `$PR_BRANCH` for the branch name** when pushing — it is set by the workflow. diff --git a/.github/workflows/reusable-claude-on-pr.yaml b/.github/workflows/reusable-claude-on-pr.yaml index c5f3627c2038..525835ae5b19 100644 --- a/.github/workflows/reusable-claude-on-pr.yaml +++ b/.github/workflows/reusable-claude-on-pr.yaml @@ -80,6 +80,13 @@ jobs: persist-credentials: true fetch-depth: 0 + - name: Verify push credentials + run: | + echo "Remote URL: $(git remote get-url origin)" + echo "Credential helper configured by checkout — testing ls-remote..." + git ls-remote --exit-code origin HEAD > /dev/null 2>&1 + echo "Push credentials verified." + - name: Authenticate to GCP via WIF id: gcp-auth uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0 @@ -114,6 +121,9 @@ jobs: ANTHROPIC_VERTEX_PROJECT_ID: hosted-control-planes GH_TOKEN: ${{ steps.app-token.outputs.token || github.token }} PR_NUMBER: ${{ github.event.issue.number }} + PR_REPO: ${{ steps.pr.outputs.repo }} + PR_BRANCH: ${{ steps.pr.outputs.branch }} + PUSH_TOKEN: ${{ steps.app-token.outputs.token || github.token }} CLAUDE_PROMPT: ${{ inputs.claude-prompt }} MAX_TURNS: ${{ inputs.max-turns }} ALLOWED_TOOLS: ${{ inputs.allowed-tools }}