Skip to content

ci(release): extract post-publish main sync into its own job#20

Open
isaacbmiller wants to merge 1 commit intomainfrom
isaac/harden-release-3
Open

ci(release): extract post-publish main sync into its own job#20
isaacbmiller wants to merge 1 commit intomainfrom
isaac/harden-release-3

Conversation

@isaacbmiller
Copy link
Copy Markdown

Isolates git-push from the OIDC-holding PyPI publish job (Critical #3).

   build-and-publish-pypi:
     permissions:
       id-token: write
-      contents: write
+  # git steps removed from this job
+
+  sync-version-to-main:
+    needs: [extract-tag, build-and-publish-pypi]
+    permissions:
+      contents: write
+    steps:
+      # re-apply sed edits + git-auto-commit + merge + push

Rationale

build-and-publish-pypi currently holds both id-token: write (required for Trusted Publishing) and contents: write (used only for the post-publish 'Update versions' merge back to main). A compromised step in that job could both publish a package and push to main.

This PR moves the git work into a new sync-version-to-main job with only contents: write. The publish job keeps only id-token: write. The new job re-derives file contents from the tag ref via sed (deterministic), rather than accepting any artifact from the privileged publishing job.

Net behavior: identical. Same branch name, same commit message, same merge-to-main.

@isaacbmiller
Copy link
Copy Markdown
Author

@greptile

@greptile-apps
Copy link
Copy Markdown

greptile-apps Bot commented Apr 22, 2026

Greptile Summary

This PR isolates the post-publish version sync into its own sync-version-to-main job, removing contents: write from the OIDC-privileged build-and-publish-pypi job. It also adds twine check --strict validation before all three publish steps and updates the dspy-ai package to use an inline readme.

  • P1: If the workflow is re-run after a partial failure, git-auto-commit-action will error because the release-X.Y.Z branch already exists on the remote — consider using push_options: '--force' or a pre-step to delete any stale branch.
  • P2: The release-X.Y.Z branch is pushed to remote but never deleted after the merge, accumulating stale branches on every release.

Confidence Score: 4/5

Safe to merge after addressing the re-run failure risk for the release branch conflict.

The core security improvement is well-implemented and correct. The P1 finding (workflow re-run fails if the release branch already exists) is a real operational defect that could block recovery from a publish-job failure, warranting a score of 4 rather than 5.

build_and_release.yml — specifically the git-auto-commit-action step and the missing release-branch cleanup.

Important Files Changed

Filename Overview
.github/workflows/build_and_release.yml Key security refactor: removes contents: write from the OIDC-holding publish job and moves git operations into a new sync-version-to-main job. Also adds twine check --strict to all three build paths. Two concerns: release branches are never deleted after merge, and re-running the workflow for the same tag will fail because the release branch already exists on the remote.
.github/.internal_dspyai/pyproject.toml Changes readme from an external file reference to an inline markdown string, appropriate for a pure compatibility alias package. Marker comments for sed substitution remain intact.
uv.lock Version bump from 3.1.3 → 3.2.0 for the dspy editable install entry, consistent with the release.

Sequence Diagram

sequenceDiagram
    participant T as Tag Push
    participant ET as extract-tag
    participant TP as build-and-publish-test-pypi<br/>(id-token: write)
    participant PP as build-and-publish-pypi<br/>(id-token: write only)
    participant SV as sync-version-to-main<br/>(contents: write only)
    participant R as Remote (PyPI / GitHub)

    T->>ET: trigger on tag push
    ET->>TP: version output
    TP->>R: publish to TestPyPI
    TP->>PP: success signal
    PP->>R: publish dspy + dspy-ai to PyPI
    PP->>SV: success signal
    SV->>SV: checkout tag + sed re-derive files
    SV->>R: push release-X.Y.Z branch
    SV->>SV: merge release branch into main
    SV->>R: git push origin main
Loading

Comments Outside Diff (2)

  1. .github/workflows/build_and_release.yml, line 162-166 (link)

    P1 Re-run of same tag may fail if release branch already exists

    git-auto-commit-action is called with create_branch: true targeting release-${{ needs.extract-tag.outputs.version }}. If the workflow is re-run (e.g., after a transient failure in a later step), the branch already exists on the remote and the action will error, blocking recovery. Using push_options: '--force' or deleting the stale branch first would allow safe re-runs.

  2. .github/workflows/build_and_release.yml, line 162-180 (link)

    P2 Release branches accumulate on remote

    After git-auto-commit-action pushes release-X.Y.Z and the subsequent merge to main succeeds, the release branch is never deleted. Every release will leave a stale branch on the remote. Adding a cleanup step after the push would prevent this accumulation.

Reviews (1): Last reviewed commit: "ci(release): extract post-publish main s..." | Re-trigger Greptile

The build-and-publish-pypi job currently holds both id-token: write and
contents: write. id-token is required for PyPI Trusted Publishing; contents:
write is used only for the 'Update versions' commit + merge into main that
runs after the upload. Holding both permissions in the same OIDC-minting
job means any compromised step (malicious test dep, poisoned sed, etc.)
could both publish a package AND push to main before anyone notices.

This PR moves the git work into a new sync-version-to-main job with only
contents: write. The publish job now carries only id-token: write.

The new job re-applies the same sed edits deterministically from the tag
ref rather than accepting any artifact from the publishing job - that way
even a compromised build/publish step cannot plant arbitrary file contents
into the commit that lands on main.

Refs PyPI security-model guidance: https://docs.pypi.org/trusted-publishers/security-model/
('Limit the scope of your publishing job').

Co-authored-by: factory-droid[bot] <138933559+factory-droid[bot]@users.noreply.github.com>
@isaacbmiller isaacbmiller force-pushed the isaac/harden-release-3 branch from 14c719f to de6933a Compare April 22, 2026 18:19
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