Skip to content

fix: use draft releases to support immutable release policy#429

Merged
Aureliolo merged 5 commits intomainfrom
fix/draft-releases
Mar 15, 2026
Merged

fix: use draft releases to support immutable release policy#429
Aureliolo merged 5 commits intomainfrom
fix/draft-releases

Conversation

@Aureliolo
Copy link
Copy Markdown
Owner

Summary

  • Configure Release Please to create draft releases ("draft": true in config)
  • Add finalize-release.yml workflow that publishes drafts after both Docker and CLI workflows succeed
  • Preserves immutable release security — once published, assets/body can't be modified

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:

  1. Release Please creates draft release + tag
  2. Docker workflow builds/signs/attests images, appends container notes to draft
  3. CLI workflow builds binaries via GoReleaser, attests, appends install notes to draft
  4. finalize-release.yml triggers on each workflow completion → publishes draft when both succeed
  5. Published release is immutable from this point

Files changed

  • .github/release-please-config.json — added "draft": true
  • .github/workflows/finalize-release.yml — new workflow
  • CLAUDE.md — updated Release and added Finalize Release docs
  • docs/security.md — documented immutable releases

Test plan

  • Merge and verify next release creates a draft (not published)
  • Verify Docker workflow appends container notes to draft
  • Verify CLI workflow uploads binaries + appends notes to draft
  • Verify finalize-release publishes the draft after both succeed
  • Verify published release is immutable

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.
@github-actions
Copy link
Copy Markdown
Contributor

github-actions bot commented Mar 15, 2026

Dependency Review

✅ No vulnerabilities or license issues or OpenSSF Scorecard issues found.

Scanned Files

None

@coderabbitai
Copy link
Copy Markdown

coderabbitai bot commented Mar 15, 2026

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: b58461e7-a582-4b81-af82-21cf206aeb63

📥 Commits

Reviewing files that changed from the base of the PR and between ab632ff and 1b691fd.

📒 Files selected for processing (1)
  • .github/workflows/finalize-release.yml

📝 Walkthrough

Summary by CodeRabbit

  • Chores

    • Release flow now creates draft releases and adds an automated finalize step that publishes the draft only after all dependent build workflows and asset uploads complete; includes safeguards for concurrent runs, race conditions, and tag-based triggers.
  • Documentation

    • Updated docs explaining the draft-based release workflow, sequencing for attaching assets, and security guidance that published releases are treated as immutable.

Walkthrough

Adds draft-release behavior to Release Please via .github/release-please-config.json and a new .github/workflows/finalize-release.yml workflow that publishes draft releases only after both CLI and Docker tag workflows complete; updates docs to describe draft/finalize flow and release immutability. ( Thirty words )

Changes

Cohort / File(s) Summary
Release Configuration
​.github/release-please-config.json
Added top-level "draft": true so Release Please creates draft releases instead of immediately publishing.
Release Automation
​.github/workflows/finalize-release.yml
New workflow that triggers on completion of tag-based Docker/CLI runs, queries both runs via gh, and publishes the draft release with TOCTOU guards and early-exit conditions.
Documentation
CLAUDE.md, docs/security.md
Documented the new draft → finalize release flow and noted that GitHub Releases are treated as immutable once published.

Sequence Diagram(s)

mermaid
sequenceDiagram
participant Tag as "Tag push"
participant Docker as "Docker workflow"
participant CLI as "CLI workflow"
participant Finalize as "Finalize Release workflow"
participant Releases as "GitHub Releases (draft)"
Tag->>Docker: trigger tag-based run
Tag->>CLI: trigger tag-based run
Docker-->>Finalize: workflow_run.completed (success/fail)
CLI-->>Finalize: workflow_run.completed (success/fail)
Finalize->>Docker: query run status (gh)
Finalize->>CLI: query run status (gh)
alt Both succeeded
Finalize->>Releases: fetch draft release (gh)
Releases-->>Finalize: draft exists
Finalize->>Releases: publish release (gh release edit -> set draft=false)
Releases-->>Finalize: published
else Any failed or missing
Finalize-->>Finalize: exit without publishing
end

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly summarizes the main change: using draft releases to support an immutable release policy, which is the primary objective across all modified files.
Description check ✅ Passed The description is comprehensive and directly related to the changeset, explaining the motivation, implementation details, files changed, and test plan for the draft release workflow changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/draft-releases
✨ Simplify code
  • Create PR with simplified code
  • Commit simplified code in branch fix/draft-releases
📝 Coding Plan
  • Generate coding plan for human review comments

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, 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

  • Draft Releases: Configured Release Please to create draft GitHub releases by default, preventing immediate publication.
  • Release Finalization Workflow: Introduced a new workflow responsible for publishing draft releases only after all necessary assets (Docker images, CLI binaries) have been successfully attached by other workflows.
  • Immutable Release Security: Ensured the preservation of immutable release security by publishing releases only once all components are ready, preventing post-publication modifications and enhancing supply chain security.
Changelog
  • .github/release-please-config.json
    • Added "draft": true to configure Release Please to create draft releases.
  • CLAUDE.md
    • Updated the description of the Release workflow to mention draft GitHub Releases and the new Finalize Release workflow.
    • Added a new entry for the Finalize Release workflow, detailing its purpose and triggers.
  • docs/security.md
    • Added a new point under GitHub Releases to document immutable releases and their finalization process.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/finalize-release.yml
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 15, 2026 01:16 — with GitHub Actions Inactive
Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

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.

Suggested change
- **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-apps
Copy link
Copy Markdown

greptile-apps bot commented Mar 15, 2026

Greptile Summary

This PR introduces a draft-release workflow to work around GitHub's immutable release policy. Release Please is now configured to create draft releases ("draft": true), and the new finalize-release.yml workflow publishes the draft only after both the Docker and CLI workflows report success for the same commit. The approach is sound and all previously flagged issues (TOCTOU race, missing --commit filter, incorrect workflow.name vs workflow_run.name, and the broken branches: [main] filter) have been resolved.

Key changes:

  • .github/release-please-config.json — adds "draft": true in the per-package block to produce draft releases
  • .github/workflows/finalize-release.yml — new workflow that checks both pipeline conclusions (filtered by tag + commit SHA) before publishing, with correct TOCTOU handling for concurrent publish attempts
  • CLAUDE.md / docs/security.md — documentation updated to reflect the new flow

The only remaining nit is a stale inline comment (and its mirror in CLAUDE.md) that describes the if-condition guard as event != 'pull_request' when the actual code uses the stricter event == 'push'.

Confidence Score: 4/5

  • Safe to merge — the workflow logic is correct and all previously flagged issues have been resolved; only a minor comment inaccuracy remains.
  • The core publish logic is solid: draft creation, dual-workflow gating with SHA-pinned gh run list queries, TOCTOU-safe publish with fallback re-check, and correct event == 'push' scoping. No logic or security defects were found in the new code. Score is 4 rather than 5 only because the misleading event != 'pull_request' comment (present in both the workflow and CLAUDE.md) could cause a future regression if a maintainer "corrects" the code to match the comment.
  • finalize-release.yml comment on lines 21–23 (and the corresponding CLAUDE.md description) should be updated to accurately reflect event == 'push'.

Important Files Changed

Filename Overview
.github/workflows/finalize-release.yml New workflow to publish draft releases after Docker and CLI both succeed. All major previously flagged issues (TOCTOU race, branches filter, WORKFLOW_NAME, --commit filtering) have been addressed. One minor inline comment inaccuracy remains: the comment says "event != 'pull_request'" but the condition uses the stricter event == 'push'.
.github/release-please-config.json Adds "draft": true inside the per-package config to instruct Release Please to create draft releases. Change is minimal and correct.
CLAUDE.md Updated to document the new draft-release flow and the Finalize Release workflow. Minor documentation inaccuracy: describes the guard as event != 'pull_request' when the actual condition is event == 'push'.
docs/security.md Adds a one-line entry documenting immutable release enforcement. Accurate and consistent with the implementation.
Prompt To Fix All With AI
This 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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between e69263a and 0a99467.

📒 Files selected for processing (4)
  • .github/release-please-config.json
  • .github/workflows/finalize-release.yml
  • CLAUDE.md
  • docs/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.yml
  • CLAUDE.md
  • docs/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.md
  • docs/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": true configuration 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": true config
  • 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-level contents: write follows 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 view fails because the release doesn't exist, the command will error. The current exit 0 on line 66 handles "not a draft" but not "not found" separately.


8-11: ⚠️ Potential issue | 🔴 Critical

Add critical security guards for workflow_run trigger per 2025 best practices.

The workflow_run trigger requires two additional controls that are not present:

  1. Add branches: filter to the trigger to restrict which branches can fan out to this privileged workflow (typically [main] or release branches only).
  2. Add an event-type guard to reject pull_request events, 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_branch filter 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)
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 15, 2026 01:23 — with GitHub Actions Inactive
workflow_run is safe here: no checkout, no untrusted code execution,
guarded by event != 'pull_request' and tag prefix check.
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 15, 2026 01:26 — with GitHub Actions Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between 0a99467 and b2697e8.

📒 Files selected for processing (2)
  • .github/workflows/finalize-release.yml
  • CLAUDE.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

Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (2)
.github/workflows/finalize-release.yml (2)

29-30: ⚠️ Potential issue | 🔴 Critical

Add actions: read permission for workflow status queries.

The gh run list commands (lines 54-62) query the GitHub Actions API to check workflow run statuses. The current contents: write permission 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 | 🟠 Major

Harden 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, add github.event.workflow_run.head_repository.full_name == github.repository and 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

📥 Commits

Reviewing files that changed from the base of the PR and between b2697e8 and f4ab916.

📒 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 completed filter 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)
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 15, 2026 01:45 — with GitHub Actions Inactive
Copy link
Copy Markdown

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

♻️ Duplicate comments (1)
.github/workflows/finalize-release.yml (1)

24-62: ⚠️ Potential issue | 🟠 Major

Constrain 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

📥 Commits

Reviewing files that changed from the base of the PR and between f4ab916 and ab632ff.

📒 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
@Aureliolo Aureliolo merged commit a6c7444 into main Mar 15, 2026
13 of 15 checks passed
@Aureliolo Aureliolo deleted the fix/draft-releases branch March 15, 2026 01:52
@Aureliolo Aureliolo temporarily deployed to cloudflare-preview March 15, 2026 01:52 — with GitHub Actions Inactive
Aureliolo added a commit that referenced this pull request Mar 15, 2026
🤖 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).
Aureliolo added a commit that referenced this pull request Mar 15, 2026
## 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
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.

1 participant