fix: use draft releases to support immutable release policy#429
fix: use draft releases to support immutable release policy#429
Conversation
Release Please now creates draft releases. Docker and CLI workflows attach assets to the mutable draft. A new finalize-release workflow publishes the draft once both workflows succeed, making it immutable. This fixes the 422 "Cannot upload assets to an immutable release" error that blocked GoReleaser and release note updates.
Dependency Review✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.Scanned FilesNone |
|
Caution Review failedThe pull request is closed. ℹ️ Recent review info⚙️ Run configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Run ID: 📒 Files selected for processing (1)
📝 WalkthroughSummary by CodeRabbit
WalkthroughAdds draft-release behavior to Release Please via Changes
Sequence Diagram(s)mermaid Estimated code review effort🎯 3 (Moderate) | ⏱️ ~20 minutes Possibly related PRs
🚥 Pre-merge checks | ✅ 3✅ Passed checks (3 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
✨ Simplify code
📝 Coding Plan
Comment |
Summary of ChangesHello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request refines the release process to enhance supply chain security by implementing an immutable release policy. Previously, Release Please would publish releases immediately, leading to issues when subsequent workflows attempted to modify them. The updated flow now configures Release Please to create releases as drafts. A new workflow is responsible for publishing these drafts only after both Docker and CLI build workflows have successfully attached their respective assets and notes. This ensures that once a release is published, its contents are immutable, bolstering security. Highlights
Changelog
Ignored Files
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for GitHub and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Code Review
This pull request updates the release process to use draft releases, allowing other workflows to attach assets before the release is published. This change supports the repository's immutable release policy. The changes include updating the Release Please configuration to create drafts and adding documentation for the new finalize-release workflow. My review focuses on the clarity of the updated documentation. I have one suggestion to improve the description of the new release finalization workflow in CLAUDE.md to make the logic clearer for developers.
CLAUDE.md
Outdated
| - **CLA**: `.github/workflows/cla.yml` — Contributor License Agreement signature check on PRs via `contributor-assistant/github-action`. Triggers on `pull_request_target` and `issue_comment`. Skips Dependabot. Signatures stored in `.github/cla-signatures.json` on the `cla-signatures` branch (unprotected, so the action can commit directly). | ||
| - **Release**: `.github/workflows/release.yml` — Release Please (Google) auto-creates a release PR on every push to main. Merging the release PR creates a git tag (`vX.Y.Z`) + GitHub Release with changelog. Tag push triggers the Docker workflow to build version-tagged images. Uses `RELEASE_PLEASE_TOKEN` secret (PAT/GitHub App token) so tag creation triggers downstream workflows (GITHUB_TOKEN cannot). Config in `.github/release-please-config.json` and `.github/.release-please-manifest.json`. After creating/updating a release PR, auto-updates the BSL Change Date in LICENSE to 3 years ahead. | ||
| - **Release**: `.github/workflows/release.yml` — Release Please (Google) auto-creates a release PR on every push to main. Merging the release PR creates a git tag (`vX.Y.Z`) + **draft** GitHub Release with changelog. Tag push triggers Docker and CLI workflows to attach assets to the draft. Uses `RELEASE_PLEASE_TOKEN` secret (PAT/GitHub App token) so tag creation triggers downstream workflows (GITHUB_TOKEN cannot). Config in `.github/release-please-config.json` (`"draft": true`) and `.github/.release-please-manifest.json`. After creating/updating a release PR, auto-updates the BSL Change Date in LICENSE to 3 years ahead. | ||
| - **Finalize Release**: `.github/workflows/finalize-release.yml` — publishes draft releases after both Docker and CLI workflows succeed. Triggers on `workflow_run` completion of Docker and CLI. Checks both workflow conclusions for the tag, publishes the draft only when both are successful. Immutable releases are enabled on the repo — once published, release assets and body cannot be modified. |
There was a problem hiding this comment.
The description of the 'Finalize Release' workflow logic could be clearer. To help developers quickly understand this critical new part of the release process, consider rephrasing it for better flow and clarity. The current wording is a bit repetitive and ambiguous.
| - **Finalize Release**: `.github/workflows/finalize-release.yml` — publishes draft releases after both Docker and CLI workflows succeed. Triggers on `workflow_run` completion of Docker and CLI. Checks both workflow conclusions for the tag, publishes the draft only when both are successful. Immutable releases are enabled on the repo — once published, release assets and body cannot be modified. | |
| - **Finalize Release**: `.github/workflows/finalize-release.yml` — Publishes draft releases created by Release Please. It triggers on `workflow_run` completion of the Docker and CLI workflows. It then verifies that both workflows have succeeded for the associated Git tag before publishing the draft. This two-step process is necessary to comply with the repository's immutable release policy, which prevents modifying already-published releases. |
Greptile SummaryThis PR introduces a draft-release workflow to work around GitHub's immutable release policy. Release Please is now configured to create draft releases ( Key changes:
The only remaining nit is a stale inline comment (and its mirror in Confidence Score: 4/5
Important Files Changed
Prompt To Fix All With AIThis is a comment left during a code review.
Path: .github/workflows/finalize-release.yml
Line: 21-23
Comment:
**Inline comment describes a looser guard than what the code implements**
The comment says the guard is `event != 'pull_request'`, but the actual `if` condition on line 25 uses the stricter `event == 'push'`. The two are not equivalent: `event == 'push'` also blocks `workflow_dispatch`, `schedule`, and any other non-push event type, while `event != 'pull_request'` would permit them.
This inaccuracy is repeated in `CLAUDE.md` ("Guards against PR-triggered runs (`event != 'pull_request'`)") and could mislead future maintainers into relaxing the condition to `!= 'pull_request'` under the false impression it is equivalent.
Consider updating the comment to match the code:
```suggestion
# Only process tag-triggered release builds (event == 'push').
# This prevents PR-triggered Docker/CLI runs (and any other non-push
# event) from reaching this privileged publish step.
```
How can I resolve this? If you propose a fix, please make it concise.Last reviewed commit: 1b691fd |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/finalize-release.yml:
- Around line 31-36: Replace direct template expansion of `${{
github.event.workflow.name }}` and `${{ github.event.workflow_run.conclusion }}`
with shell environment variables: set WORKFLOW_NAME and TRIGGER_CONCLUSION from
those template values at the top of the step (e.g., write them to $GITHUB_ENV or
export them), then use $WORKFLOW_NAME and $TRIGGER_CONCLUSION in the echo lines
and the if test that currently references `${{
github.event.workflow_run.conclusion }}`; keep existing TAG usage but reference
it via $TAG in shell, ensuring all template values are passed through env
variables before any shell evaluation.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: a79a5909-f583-46f7-b22e-b68ba465b2a3
📒 Files selected for processing (4)
.github/release-please-config.json.github/workflows/finalize-release.ymlCLAUDE.mddocs/security.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Greptile Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (2)
.github/workflows/**/*.yml
📄 CodeRabbit inference engine (CLAUDE.md)
All GitHub Actions workflows must be security-hardened: use SHA-pinned actions, implement SLSA L3 provenance attestations via
actions/attest-build-provenance, sign Docker images with cosign, and scan with Trivy/Grype before pushing
Files:
.github/workflows/finalize-release.yml
docs/**/*.md
📄 CodeRabbit inference engine (CLAUDE.md)
Always read the relevant
docs/design/page before implementing any feature — the design spec is the starting point for architecture, data models, and behavior
Files:
docs/security.md
🧠 Learnings (5)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T00:35:49.651Z
Learning: Dependabot: use `/review-dep-pr` to review Dependabot PRs before merging. Dependabot auto-updates uv + github-actions + npm + pre-commit + docker + gomod daily with grouped minor/patch updates and no auto-merge
📚 Learning: 2026-03-15T00:35:49.651Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T00:35:49.651Z
Learning: Applies to .github/workflows/**/*.yml : All GitHub Actions workflows must be security-hardened: use SHA-pinned actions, implement SLSA L3 provenance attestations via `actions/attest-build-provenance`, sign Docker images with cosign, and scan with Trivy/Grype before pushing
Applied to files:
.github/workflows/finalize-release.ymlCLAUDE.mddocs/security.md
📚 Learning: 2026-03-15T00:35:49.651Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T00:35:49.651Z
Learning: Applies to .github/pre-commit-config.yaml : Pre-commit hooks must include: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint. Pre-push hooks: mypy + pytest unit tests + golangci-lint + go vet + go test (CLI). Autoupdate disabled — Dependabot owns version bumps
Applied to files:
CLAUDE.mddocs/security.md
📚 Learning: 2026-03-15T00:35:49.651Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T00:35:49.651Z
Learning: Dependabot: use `/review-dep-pr` to review Dependabot PRs before merging. Dependabot auto-updates uv + github-actions + npm + pre-commit + docker + gomod daily with grouped minor/patch updates and no auto-merge
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-15T00:35:49.651Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T00:35:49.651Z
Learning: Signed commits required on main via branch protection — all commits must be GPG/SSH signed
Applied to files:
docs/security.md
🪛 GitHub Actions: Workflow Security
.github/workflows/finalize-release.yml
[error] 8-8: dangerous-triggers: use of fundamentally insecure workflow trigger. workflow_run is almost always used insecurely.
[error] 31-31: template-injection: code injection via template expansion. Potential attacker-controlled code expansion in run block.
🪛 LanguageTool
CLAUDE.md
[uncategorized] ~275-~275: The official name of this software platform is spelled with a capital “H”.
Context: ...kflows (GITHUB_TOKEN cannot). Config in .github/release-please-config.json (`"draft": ...
(GITHUB)
[uncategorized] ~275-~275: The official name of this software platform is spelled with a capital “H”.
Context: ...ease-config.json ("draft": true) and .github/.release-please-manifest.json`. After c...
(GITHUB)
🔇 Additional comments (6)
.github/release-please-config.json (1)
11-11: LGTM!The
"draft": trueconfiguration correctly enables draft releases in Release Please, allowing the Docker and CLI workflows to attach assets before the finalize-release workflow publishes the release. This aligns with the immutable release policy.docs/security.md (1)
156-156: LGTM!The documentation clearly explains the immutable release policy and the draft-then-finalize flow. This provides important context for users verifying release integrity.
CLAUDE.md (1)
275-276: LGTM!The CI documentation accurately describes the draft-based release flow:
- Release Please creates draft releases with
"draft": trueconfig- Tag push triggers Docker and CLI workflows to attach assets
- Finalize Release publishes the draft after both succeed
- Immutability is enforced post-publication
The static analysis hints about "GitHub" capitalization are false positives—these are directory paths (
.github/), not references to the platform name..github/workflows/finalize-release.yml (3)
13-22: LGTM on permissions scoping.Good security practice: top-level
permissions: {}with job-levelcontents: writefollows the principle of least privilege. The workflow only needs write access to publish the release.
41-70: Logic for checking both workflows and publishing is sound.The workflow correctly:
- Queries both CLI and Docker workflow conclusions for the tag
- Uses
// "pending"fallback for runs not yet completed- Only publishes when both are
success- Verifies the release is still a draft before publishing
- Exits gracefully in all other cases, allowing the other workflow's completion to trigger another attempt
One minor consideration: if
gh release viewfails because the release doesn't exist, the command will error. The currentexit 0on line 66 handles "not a draft" but not "not found" separately.
8-11:⚠️ Potential issue | 🔴 CriticalAdd critical security guards for
workflow_runtrigger per 2025 best practices.The
workflow_runtrigger requires two additional controls that are not present:
- Add
branches:filter to the trigger to restrict which branches can fan out to this privileged workflow (typically[main]or release branches only).- Add an event-type guard to reject
pull_requestevents, since PRs can modify the triggering workflows (Docker, CLI) to be pull_request-triggered and still reach this privileged workflow:if: github.event.workflow_run.event != 'pull_request' && startsWith(github.event.workflow_run.head_branch, 'v')The
head_branchfilter alone is insufficient—it does not prevent the attack vector where a PR modifies the triggering workflows. Per GitHub Security Lab guidance, assume the triggering workflow is attacker-influenced and gate by both origin event and branch scope.⛔ Skipped due to learnings
Learnt from: CR Repo: Aureliolo/synthorg PR: 0 File: CLAUDE.md:0-0 Timestamp: 2026-03-15T00:35:49.651Z Learning: Applies to .github/workflows/**/*.yml : All GitHub Actions workflows must be security-hardened: use SHA-pinned actions, implement SLSA L3 provenance attestations via `actions/attest-build-provenance`, sign Docker images with cosign, and scan with Trivy/Grype before pushing
- finalize-release.yml: move template expressions to env vars (zizmor fix) - finalize-release.yml: add event != 'pull_request' guard (CodeRabbit) - finalize-release.yml: handle TOCTOU race on concurrent publish (Greptile) - finalize-release.yml: graceful error if release not found (infra-reviewer) - finalize-release.yml: add --status completed to run queries (Greptile) - finalize-release.yml: add branches filter to workflow_run trigger - CLAUDE.md: update CLI bullet to say "draft" release (docs-consistency) - CLAUDE.md: clarify Finalize Release description (Gemini)
workflow_run is safe here: no checkout, no untrusted code execution, guarded by event != 'pull_request' and tag prefix check.
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In @.github/workflows/finalize-release.yml:
- Around line 8-24: Update the publish job's workflow_run guard to use a
positive allowlist and same-repo check: replace the negative check on
github.event.workflow_run.event with an explicit equality check
(github.event.workflow_run.event == 'push'), remove the ineffective branches:
[main] filter, and add a same-repo guard comparing
github.event.workflow_run.head_repository.full_name == github.repository so only
runs from the same repo (not forks) and only push-triggered workflow_runs
proceed for the publish job (refer to the jobs.publish.if condition and
github.event.workflow_run.* fields).
- Around line 26-27: The workflow's permissions block currently only grants
contents: write but the steps that run gh run list (the workflow run status
queries) require Actions API read scope; update the permissions stanza by adding
actions: read alongside contents: write so the gh run list commands can query
workflow runs without 403 errors (i.e., add "actions: read" under the existing
permissions key in finalize-release.yml).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 955d25b2-09b2-436e-9ab6-1389a9629a61
📒 Files selected for processing (2)
.github/workflows/finalize-release.ymlCLAUDE.md
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (2)
- GitHub Check: Greptile Review
- GitHub Check: Analyze (python)
🧰 Additional context used
📓 Path-based instructions (1)
.github/workflows/**/*.yml
📄 CodeRabbit inference engine (CLAUDE.md)
All GitHub Actions workflows must be security-hardened: use SHA-pinned actions, implement SLSA L3 provenance attestations via
actions/attest-build-provenance, sign Docker images with cosign, and scan with Trivy/Grype before pushing
Files:
.github/workflows/finalize-release.yml
🧠 Learnings (6)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T00:35:49.651Z
Learning: Dependabot: use `/review-dep-pr` to review Dependabot PRs before merging. Dependabot auto-updates uv + github-actions + npm + pre-commit + docker + gomod daily with grouped minor/patch updates and no auto-merge
📚 Learning: 2026-03-15T00:35:49.651Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T00:35:49.651Z
Learning: Applies to .github/workflows/**/*.yml : All GitHub Actions workflows must be security-hardened: use SHA-pinned actions, implement SLSA L3 provenance attestations via `actions/attest-build-provenance`, sign Docker images with cosign, and scan with Trivy/Grype before pushing
Applied to files:
CLAUDE.md.github/workflows/finalize-release.yml
📚 Learning: 2026-03-15T00:35:49.651Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T00:35:49.651Z
Learning: Applies to .github/pre-commit-config.yaml : Pre-commit hooks must include: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint. Pre-push hooks: mypy + pytest unit tests + golangci-lint + go vet + go test (CLI). Autoupdate disabled — Dependabot owns version bumps
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-15T00:35:49.651Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T00:35:49.651Z
Learning: Dependabot: use `/review-dep-pr` to review Dependabot PRs before merging. Dependabot auto-updates uv + github-actions + npm + pre-commit + docker + gomod daily with grouped minor/patch updates and no auto-merge
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-15T00:35:49.651Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T00:35:49.651Z
Learning: Applies to docker/Dockerfile* : Use Chainguard Python and distroless runtime images; run as non-root (UID 65532); implement CIS hardening. Backend: 3-stage build (builder → setup → distroless). Web: use `nginxinc/nginx-unprivileged`. Lint all Dockerfiles with hadolint
Applied to files:
CLAUDE.md
📚 Learning: 2026-03-15T00:35:49.651Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T00:35:49.651Z
Learning: Applies to cli/**/*.go : Use Go 1.26+; run `cd cli && go test ./...`, `go vet ./...`, and `golangci-lint run` before committing. Fuzz targets run seed corpus only without `-fuzz` flag
Applied to files:
CLAUDE.md
🪛 GitHub Actions: Workflow Security
.github/workflows/finalize-release.yml
[error] 8-12: Dangerous triggers detected: use of workflow_run trigger is insecure. See Dizmor audit note for details.
🪛 LanguageTool
CLAUDE.md
[uncategorized] ~275-~275: The official name of this software platform is spelled with a capital “H”.
Context: ...kflows (GITHUB_TOKEN cannot). Config in .github/release-please-config.json (`"draft": ...
(GITHUB)
[uncategorized] ~275-~275: The official name of this software platform is spelled with a capital “H”.
Context: ...ease-config.json ("draft": true) and .github/.release-please-manifest.json`. After c...
(GITHUB)
🔇 Additional comments (1)
CLAUDE.md (1)
262-263: Release-flow documentation update looks accurate and consistent.The draft-release + finalize-release behavior is clearly documented and matches the workflow intent.
Also applies to: 275-276
There was a problem hiding this comment.
♻️ Duplicate comments (2)
.github/workflows/finalize-release.yml (2)
29-30:⚠️ Potential issue | 🔴 CriticalAdd
actions: readpermission for workflow status queries.The
gh run listcommands (lines 54-62) query the GitHub Actions API to check workflow run statuses. The currentcontents: writepermission lacks Actions API read scope—these commands will fail with a 403 permission error, blocking release finalization.🔧 Proposed fix
permissions: + actions: read contents: write🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/finalize-release.yml around lines 29 - 30, The workflow lacks Actions API read scope so the `gh run list` queries fail; update the permissions block by adding an `actions: read` entry alongside the existing `contents: write` permission in finalize-release.yml (i.e., update the `permissions:` stanza to include `actions: read`) so the `gh run list` commands can query workflow run statuses successfully.
25-27:⚠️ Potential issue | 🟠 MajorHarden the workflow_run guard with same-repo check and positive allowlist.
The guard still uses a negative check (
!= 'pull_request') and lacks protection against fork-based trigger runs. To prevent external forks from reaching the publish step, addgithub.event.workflow_run.head_repository.full_name == github.repositoryand consider using a positive allowlist (== 'push') instead of the negative check.🔒 Proposed hardening
if: >- - github.event.workflow_run.event != 'pull_request' + github.event.workflow_run.event == 'push' + && github.event.workflow_run.head_repository.full_name == github.repository && startsWith(github.event.workflow_run.head_branch, 'v')🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/finalize-release.yml around lines 25 - 27, Update the workflow_run guard expression to use a positive allowlist and same-repo check: replace the negative check "github.event.workflow_run.event != 'pull_request'" with a positive equality "github.event.workflow_run.event == 'push'" and add "&& github.event.workflow_run.head_repository.full_name == github.repository" while keeping the branch check "startsWith(github.event.workflow_run.head_branch, 'v')"; ensure the final if uses all three conditions combined so only push events from the same repository on version branches proceed.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In @.github/workflows/finalize-release.yml:
- Around line 29-30: The workflow lacks Actions API read scope so the `gh run
list` queries fail; update the permissions block by adding an `actions: read`
entry alongside the existing `contents: write` permission in
finalize-release.yml (i.e., update the `permissions:` stanza to include
`actions: read`) so the `gh run list` commands can query workflow run statuses
successfully.
- Around line 25-27: Update the workflow_run guard expression to use a positive
allowlist and same-repo check: replace the negative check
"github.event.workflow_run.event != 'pull_request'" with a positive equality
"github.event.workflow_run.event == 'push'" and add "&&
github.event.workflow_run.head_repository.full_name == github.repository" while
keeping the branch check "startsWith(github.event.workflow_run.head_branch,
'v')"; ensure the final if uses all three conditions combined so only push
events from the same repository on version branches proceed.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 7e4e330f-3123-4db6-828f-6580d1cc30ad
📒 Files selected for processing (1)
.github/workflows/finalize-release.yml
📜 Review details
🧰 Additional context used
📓 Path-based instructions (1)
.github/workflows/**/*.yml
📄 CodeRabbit inference engine (CLAUDE.md)
All GitHub Actions workflows must be security-hardened: use SHA-pinned actions, implement SLSA L3 provenance attestations via
actions/attest-build-provenance, sign Docker images with cosign, and scan with Trivy/Grype before pushing
Files:
.github/workflows/finalize-release.yml
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T00:35:49.651Z
Learning: Dependabot: use `/review-dep-pr` to review Dependabot PRs before merging. Dependabot auto-updates uv + github-actions + npm + pre-commit + docker + gomod daily with grouped minor/patch updates and no auto-merge
📚 Learning: 2026-03-15T00:35:49.651Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T00:35:49.651Z
Learning: Applies to .github/workflows/**/*.yml : All GitHub Actions workflows must be security-hardened: use SHA-pinned actions, implement SLSA L3 provenance attestations via `actions/attest-build-provenance`, sign Docker images with cosign, and scan with Trivy/Grype before pushing
Applied to files:
.github/workflows/finalize-release.yml
📚 Learning: 2026-03-15T00:35:49.651Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T00:35:49.651Z
Learning: Applies to .github/pre-commit-config.yaml : Pre-commit hooks must include: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint. Pre-push hooks: mypy + pytest unit tests + golangci-lint + go vet + go test (CLI). Autoupdate disabled — Dependabot owns version bumps
Applied to files:
.github/workflows/finalize-release.yml
📚 Learning: 2026-03-15T00:35:49.651Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T00:35:49.651Z
Learning: Signed commits required on main via branch protection — all commits must be GPG/SSH signed
Applied to files:
.github/workflows/finalize-release.yml
🔇 Additional comments (2)
.github/workflows/finalize-release.yml (2)
32-43: LGTM!Template values are correctly passed through environment variables before shell evaluation, preventing template injection risks.
44-99: LGTM!The shell logic is well-structured with appropriate error handling:
- Strict mode via
set -euo pipefail- Early exits prevent unnecessary API calls when preconditions aren't met
--status completedfilter correctly excludes in-progress runs- TOCTOU race condition handling (lines 86-98) gracefully accepts concurrent publish attempts
- Error annotation surfaces failures to the Actions UI
- Remove branches: [main] filter — it blocks tag-triggered runs, making the workflow completely inoperative for releases (Greptile) - Fix WORKFLOW_NAME: use workflow_run.name (triggering workflow) instead of workflow.name (current workflow = always "Finalize Release") (Greptile) - Add actions: read permission — gh run list needs it to query workflow run statuses, otherwise 403 (CodeRabbit)
There was a problem hiding this comment.
♻️ Duplicate comments (1)
.github/workflows/finalize-release.yml (1)
24-62:⚠️ Potential issue | 🟠 MajorConstrain publish to same-repo push runs and match workflow runs by commit.
The current guard is still permissive (non-PR), and the
gh run list --branch "$TAG"lookups can select an unintended run when branch/tag names overlap. Tighten both the job gate and run queries to prevent premature publish decisions.🔒 Proposed hardening diff
if: >- - github.event.workflow_run.event != 'pull_request' + github.event.workflow_run.event == 'push' + && github.event.workflow_run.head_repository.full_name == github.repository && startsWith(github.event.workflow_run.head_branch, 'v') @@ env: GH_TOKEN: ${{ github.token }} TAG: ${{ github.event.workflow_run.head_branch }} + HEAD_SHA: ${{ github.event.workflow_run.head_sha }} WORKFLOW_NAME: ${{ github.event.workflow_run.name }} WORKFLOW_CONCLUSION: ${{ github.event.workflow_run.conclusion }} @@ CLI_CONCLUSION=$(gh run list --repo "$GITHUB_REPOSITORY" \ - --workflow cli.yml --branch "$TAG" --limit 1 \ + --workflow cli.yml --branch "$TAG" --event push --commit "$HEAD_SHA" --limit 1 \ --status completed \ --json conclusion --jq '.[0].conclusion // "pending"') @@ DOCKER_CONCLUSION=$(gh run list --repo "$GITHUB_REPOSITORY" \ - --workflow docker.yml --branch "$TAG" --limit 1 \ + --workflow docker.yml --branch "$TAG" --event push --commit "$HEAD_SHA" --limit 1 \ --status completed \ --json conclusion --jq '.[0].conclusion // "pending"')For GitHub Actions `workflow_run`, what are the recommended guards to ensure only same-repository `push` runs can trigger privileged jobs (including use of `github.event.workflow_run.head_repository.full_name`)?In current GitHub CLI docs, does `gh run list` support filtering by both `--event` and `--commit`, and is that the recommended way to avoid branch/tag ambiguity when selecting a run?🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In @.github/workflows/finalize-release.yml around lines 24 - 62, The job should only run for same-repo push workflow_run triggers and should match runs by commit to avoid branch/tag ambiguity: update the top-level if/guard to require github.event_name == 'workflow_run' AND github.event.workflow_run.event == 'push' AND github.event.workflow_run.head_repository.full_name == github.repository, set an env HEAD_SHA=${{ github.event.workflow_run.head_sha }} (or head_commit.id) and HEAD_REPO=${{ github.event.workflow_run.head_repository.full_name }}, and replace the gh run list calls for CLI_CONCLUSION and DOCKER_CONCLUSION to filter by --event push --commit "$HEAD_SHA" (keep --workflow cli.yml / docker.yml and --status completed) instead of --branch "$TAG" so you reliably select the exact run for that commit in the same repo.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In @.github/workflows/finalize-release.yml:
- Around line 24-62: The job should only run for same-repo push workflow_run
triggers and should match runs by commit to avoid branch/tag ambiguity: update
the top-level if/guard to require github.event_name == 'workflow_run' AND
github.event.workflow_run.event == 'push' AND
github.event.workflow_run.head_repository.full_name == github.repository, set an
env HEAD_SHA=${{ github.event.workflow_run.head_sha }} (or head_commit.id) and
HEAD_REPO=${{ github.event.workflow_run.head_repository.full_name }}, and
replace the gh run list calls for CLI_CONCLUSION and DOCKER_CONCLUSION to filter
by --event push --commit "$HEAD_SHA" (keep --workflow cli.yml / docker.yml and
--status completed) instead of --branch "$TAG" so you reliably select the exact
run for that commit in the same repo.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro
Run ID: 7fdd344a-4e2a-48da-89e5-809232226893
📒 Files selected for processing (1)
.github/workflows/finalize-release.yml
📜 Review details
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
- GitHub Check: Greptile Review
🧰 Additional context used
📓 Path-based instructions (1)
.github/workflows/**/*.yml
📄 CodeRabbit inference engine (CLAUDE.md)
All GitHub Actions workflows must be security-hardened: use SHA-pinned actions, implement SLSA L3 provenance attestations via
actions/attest-build-provenance, sign Docker images with cosign, and scan with Trivy/Grype before pushing
Files:
.github/workflows/finalize-release.yml
🧠 Learnings (4)
📓 Common learnings
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T00:35:49.651Z
Learning: Dependabot: use `/review-dep-pr` to review Dependabot PRs before merging. Dependabot auto-updates uv + github-actions + npm + pre-commit + docker + gomod daily with grouped minor/patch updates and no auto-merge
📚 Learning: 2026-03-15T00:35:49.651Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T00:35:49.651Z
Learning: Applies to .github/workflows/**/*.yml : All GitHub Actions workflows must be security-hardened: use SHA-pinned actions, implement SLSA L3 provenance attestations via `actions/attest-build-provenance`, sign Docker images with cosign, and scan with Trivy/Grype before pushing
Applied to files:
.github/workflows/finalize-release.yml
📚 Learning: 2026-03-15T00:35:49.651Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T00:35:49.651Z
Learning: Applies to .github/pre-commit-config.yaml : Pre-commit hooks must include: trailing-whitespace, end-of-file-fixer, check-yaml, check-toml, check-json, check-merge-conflict, check-added-large-files, no-commit-to-branch (main), ruff check+format, gitleaks, hadolint. Pre-push hooks: mypy + pytest unit tests + golangci-lint + go vet + go test (CLI). Autoupdate disabled — Dependabot owns version bumps
Applied to files:
.github/workflows/finalize-release.yml
📚 Learning: 2026-03-15T00:35:49.651Z
Learnt from: CR
Repo: Aureliolo/synthorg PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-03-15T00:35:49.651Z
Learning: Signed commits required on main via branch protection — all commits must be GPG/SSH signed
Applied to files:
.github/workflows/finalize-release.yml
🔇 Additional comments (1)
.github/workflows/finalize-release.yml (1)
33-47: Good shell hardening for workflow context values.Using env variables for workflow context before shell evaluation is the right pattern here.
- Restrict to push events only (not just != pull_request) - Block fork workflows via head_repository.full_name check - Pin gh run list queries to exact HEAD_SHA commit
🤖 I have created a release *beep* *boop* --- ## [0.2.3](v0.2.2...v0.2.3) (2026-03-15) ### Bug Fixes * use draft releases to support immutable release policy ([#429](#429)) ([a6c7444](a6c7444)) --- This PR was generated with [Release Please](https://github.com/googleapis/release-please). See [documentation](https://github.com/googleapis/release-please#release-please).
## Summary - Add explicit git tag creation step to `release.yml` after Release Please creates a draft release - GitHub doesn't create git tags for draft releases (only on publish), so Docker/CLI workflows never triggered - Uses `RELEASE_PLEASE_TOKEN` (PAT) so the tag push event triggers downstream workflows - Idempotent: skips if tag already exists ## Context The `v0.2.3` release was stuck as a draft because no tag existed to trigger Docker/CLI workflows. The `"draft": true` config from PR #429 works for immutable release support, but requires this explicit tag creation step to complete the pipeline. **Release flow after this fix:** 1. Push to main → Release Please creates draft release (no tag) 2. This new step creates and pushes the git tag via API 3. Tag push triggers Docker (images) and CLI (binaries) workflows 4. Both attach assets to the draft 5. `finalize-release.yml` publishes the draft when both succeed ## Test plan - [ ] Verify next release creates tag automatically after draft - [ ] Verify Docker/CLI workflows trigger from the tag push - [ ] Verify finalize-release publishes the draft after both succeed
Summary
"draft": truein config)Context
Immutable releases are enabled on this repo (good for supply chain security). But the current flow breaks because Release Please creates a published release, then Docker and CLI workflows try to modify it (upload assets, append notes → 422 error).
New flow:
finalize-release.ymltriggers on each workflow completion → publishes draft when both succeedFiles changed
.github/release-please-config.json— added"draft": true.github/workflows/finalize-release.yml— new workflowCLAUDE.md— updated Release and added Finalize Release docsdocs/security.md— documented immutable releasesTest plan