fix(github): het opruimen van previews mag productie niet raken #3762
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Claude Code Review | |
| on: | |
| pull_request: | |
| types: [opened, synchronize, ready_for_review, reopened] | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| jobs: | |
| # A cross-repo PR gets no secrets, so CLAUDE_CODE_OAUTH_TOKEN is empty there and | |
| # the action can only fail. Skip instead, and let review-gate report that. | |
| # Compare full_name rather than reading `head.repo.fork`: inside a fork of this | |
| # repo its own PRs are not cross-repo and do have secrets. | |
| claude-review: | |
| if: >- | |
| ${{ !github.event.pull_request.draft && | |
| github.event.pull_request.head.repo.full_name == github.repository && | |
| github.event.pull_request.user.login != 'dependabot[bot]' }} | |
| runs-on: ubuntu-latest | |
| permissions: | |
| contents: read | |
| pull-requests: write | |
| issues: read | |
| id-token: write | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 1 | |
| # Opruimen gebeurt ná de review, niet ervoor. Wat hier gebeurt is alleen | |
| # vastleggen wat er staat: de ids om straks op te ruimen, en de teksten om | |
| # aan de review mee te geven. Klapt de run hierna, dan is er niets | |
| # weggegooid en staat de vorige bevinding er nog. Dat is precies het geval | |
| # waarin de oude opzet — eerst wissen, dan reviewen — hem kwijtraakte, | |
| # terwijl de poort op een kritieke bevinding rood wordt: opnieuw pushen tot | |
| # de review het niet meer opmerkt zou dan groen opleveren. | |
| - name: Snapshot the previous review | |
| id: snapshot | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| REPO: ${{ github.repository }} | |
| PR: ${{ github.event.pull_request.number }} | |
| run: script/claude-review-comments.sh snapshot | |
| - name: Run Claude Code Review | |
| id: claude-review | |
| uses: anthropics/claude-code-action@be7b93b1907a4abad570368f3c74b6fe3807510b # v1 | |
| with: | |
| claude_code_oauth_token: ${{ secrets.CLAUDE_CODE_OAUTH_TOKEN }} | |
| use_sticky_comment: true | |
| claude_args: "--allowedTools Bash,Read,Glob,Grep" | |
| prompt: | | |
| Review PR #${{ github.event.pull_request.number }} in ${{ github.repository }}. | |
| Read `REVIEW.md` for the project context, review dimensions, severity scale, and | |
| skip rules. Follow those guidelines strictly. | |
| ## CRITICAL SCOPE RULE | |
| **ONLY review lines that were actually added or modified in this PR's diff.** | |
| You MUST NOT comment on: | |
| - Pre-existing code that was not changed in this PR | |
| - Context lines shown in the diff that were not added/modified (lines without + prefix) | |
| - Issues in unchanged files or unchanged sections of changed files | |
| - Pre-existing patterns, naming, style, or technical debt in surrounding code | |
| You may read full files for context to understand the changes, but every single | |
| finding you report MUST point to a line that was added or modified in the diff. | |
| If a line was not changed by this PR, it is OUT OF SCOPE, no matter how wrong it looks. | |
| Before submitting each finding, verify: "Was this line actually changed in the PR diff?" | |
| If the answer is no, drop the finding. | |
| Skip dimensions that have no findings. Be precise and actionable — no "consider" | |
| or "you might want to" comments. | |
| ## Output format | |
| Structure your review as a single comment with sections for each dimension that has | |
| findings. Use this severity scale: | |
| - 🔴 **Critical** — wrong legal outcome, data loss, runtime crash, security vulnerability | |
| - 🟠 **Significant** — likely bug, broken reference, missing edge case, lost jobs | |
| - 🟡 **Minor** — code quality, style, non-blocking improvement | |
| Also leave inline comments on specific lines where possible. | |
| If there are no issues, say so briefly. Do not pad the review with praise or filler. | |
| ## The Critical marker is read by a machine | |
| The merge gate `Claude review completed` searches this comment, your inline | |
| comments and the body of the review you submit for the exact string | |
| `🔴 **Critical**`, and turns red when it finds one, which blocks the merge. | |
| Write the marker exactly as it appears above — same | |
| emoji, same bold, same word — and only on a finding that is genuinely critical. | |
| Do not reword it, do not drop the emoji, and do not write it anywhere you are | |
| not reporting a critical finding: not in a heading, not in a legend of the | |
| scale, not in an example. A merge is held or let through on that string. | |
| End every comment you write for this review — the summary comment and each | |
| inline comment — with this line, exactly: | |
| <!-- claude-review --> | |
| It renders as nothing and it is how the workflow tells your review apart from | |
| an ordinary `@claude` answer in the same thread. A comment without it is left | |
| alone: it is not cleaned up after the next review, and it is not handed to that | |
| review as context. | |
| ${{ steps.snapshot.outputs.context }} | |
| # Het bewijs waar review-gate op afgaat. `execution_file` wijst naar het | |
| # uitvoerbestand van Claude en wordt pas gezet nadat de CLI gedraaid heeft; | |
| # stapt de actie er eerder uit — de workflow-validation-skip, wanneer dit | |
| # bestand afwijkt van de versie op de default branch — dan blijft die | |
| # output leeg en wordt deze stap overgeslagen. De poort leest de uitkomst | |
| # van deze stap uit de jobs van déze run, dus het bewijs is per constructie | |
| # run-gebonden, en het bestaat ook wanneer de review nul bevindingen had. | |
| # Hernoem deze stap niet zonder `PROOF_STEP` in review-gate mee te nemen. | |
| - name: Record that the review ran | |
| if: steps.claude-review.outputs.execution_file != '' | |
| env: | |
| EXECUTION_FILE: ${{ steps.claude-review.outputs.execution_file }} | |
| HEAD_SHA: ${{ github.event.pull_request.head.sha }} | |
| run: | | |
| echo "De review-actie is uitgevoerd; uitvoerbestand: ${EXECUTION_FILE}" | |
| { | |
| echo "### Claude review uitgevoerd" | |
| echo | |
| echo "De review-actie heeft gedraaid voor commit \`${HEAD_SHA}\`." | |
| echo "Nul bevindingen is hier een geldige uitkomst; deze stap zegt alleen dat er gereviewd is." | |
| } >>"$GITHUB_STEP_SUMMARY" | |
| # Nu de nieuwe review er staat, mag de vorige weg, en alleen de vorige: | |
| # wat in de momentopname stond en sindsdien niet is aangeraakt. Deze stap | |
| # draait onder dezelfde voorwaarde als de bewijsstap, dus een run die | |
| # klapte voordat er iets geschreven was gooit niets weg. | |
| - name: Clean up the superseded review | |
| if: steps.claude-review.outputs.execution_file != '' | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| REPO: ${{ github.repository }} | |
| PR: ${{ github.event.pull_request.number }} | |
| run: script/claude-review-comments.sh clean-up | |
| # The merge gate. Unlike claude-review this job has no `if:` — it must run and | |
| # report on every PR, cross-repo and dependabot included, because a required | |
| # check that never reports blocks such a PR forever. | |
| review-gate: | |
| name: Claude review completed | |
| runs-on: ubuntu-latest | |
| # Ruim boven de ~10 minuten die de review kost, zodat een trage run niet | |
| # onterecht rood wordt; het script stopt zelf eerder met een duidelijke melding. | |
| timeout-minutes: 40 | |
| permissions: | |
| actions: read | |
| contents: read | |
| # `issues: read` is voor de samenvattende comment: die is een issue-comment | |
| # en komt van `issues/{n}/comments`. Zonder die scope leest de poort hem | |
| # niet en blokkeert elke PR op "niet op te halen". | |
| issues: read | |
| pull-requests: read | |
| steps: | |
| - name: Checkout repository | |
| uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 | |
| with: | |
| fetch-depth: 1 | |
| # De poort mag niet draaien op de versie van zichzelf die in de PR staat: | |
| # een PR die het script naar `exit 0` verandert zou anders per definitie | |
| # groen zijn. Draai daarom de versie van de base-branch, en blokkeer als | |
| # die niet te krijgen is — terugvallen op de PR-versie is precies de | |
| # bypass die deze stap moet dichten. | |
| - name: Take the gate script from the base branch | |
| env: | |
| BASE_SHA: ${{ github.event.pull_request.base.sha }} | |
| GATE: script/await-claude-review.sh | |
| run: | | |
| set -uo pipefail | |
| fail() { | |
| echo "::error title=Claude review gate::$1" | |
| exit 1 | |
| } | |
| git fetch --depth=1 origin "$BASE_SHA" || | |
| fail "base-commit ${BASE_SHA} is niet op te halen, dus de versie van de poort is niet vast te stellen. Draai deze job opnieuw." | |
| git cat-file -e "${BASE_SHA}:${GATE}" 2>/dev/null || | |
| fail "${GATE} bestaat niet op de base-branch (${BASE_SHA}). De poort kan dan niet voor zichzelf instaan; deze PR moet met de hand gereviewd worden." | |
| git checkout "$BASE_SHA" -- "$GATE" || | |
| fail "${GATE} is niet uit ${BASE_SHA} te checkouten, dus de poort zou de versie uit deze PR draaien. Draai deze job opnieuw." | |
| echo "Poort draait de versie van ${BASE_SHA}." | |
| - name: Wait for the Claude review of this commit | |
| # Alleen de coördinaten van deze run. Wat de poort als feit behandelt — | |
| # fork, draft, auteur, de versie van dit workflowbestand — haalt zij zelf | |
| # op, want dit env-blok staat in de PR die gereviewd wordt. | |
| env: | |
| GH_TOKEN: ${{ github.token }} | |
| REPO: ${{ github.repository }} | |
| RUN_ID: ${{ github.run_id }} | |
| PR_NUMBER: ${{ github.event.pull_request.number }} | |
| MAX_WAIT_SECONDS: '2100' | |
| POLL_SECONDS: '20' | |
| run: script/await-claude-review.sh |