Skip to content

chore(workflow): added release workflow#2940

Open
poshinchen wants to merge 1 commit into
strands-agents:mainfrom
poshinchen:chore/release-workflow
Open

chore(workflow): added release workflow#2940
poshinchen wants to merge 1 commit into
strands-agents:mainfrom
poshinchen:chore/release-workflow

Conversation

@poshinchen

@poshinchen poshinchen commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

Description

Adds a manually-triggered, per-package release pipeline that pins main to a SHA, runs the gates against that SHA, drafts AI release notes via Bedrock, pauses on a 2-reviewer approval, then atomically tags + creates the GitHub release. The existing python-pypi-publish-on-release.yml / typescript-npm-publish-on-release.yml listeners do the actual PyPI / npm upload.

Pipeline steps (release.yml)

  1. scan-commits — checks out with full history + tags, validates the typed version is bare MAJOR.MINOR.PATCH, finds the highest existing python/v* or typescript/v* tag, rejects duplicates / non-monotonic / pre-existing tags, then pins release_sha = git rev-parse origin/main. Every later job uses this SHA — never main — so the pipeline is deterministic if main moves mid-release. Refuses if zero commits since the prev tag.
  2. Per-package gates (parallel, against the pinned SHA)if: inputs.package == ... selects:
    • Python: python-integration-test.yml, python-security-audit.yml (new), python-test-package-build.yml (new)
    • TypeScript: typescript-integration-test.yml, typescript-security-audit.yml, typescript-test-package-pack.yml
  3. draft-summary — assumes the AWS_ROLE_ARN Bedrock role in us-west-2, runs .github/scripts/draft-release-notes.py (Bedrock Converse → JSON {bump, notes}). On any failure (regional outage, throttling, IAM, unparseable JSON) the script exits non-zero and the workflow falls back to a banner + git shortlog. Uploads release-notes.md + bump.txt as a 30-day artifact and renders both into $GITHUB_STEP_SUMMARY.
  4. approve-release — pauses on environment: release-${{ inputs.package }}. That environment must be configured in repo settings with required_reviewers >= 2 and Deployment branches → main only.
  5. publish-release (skipped when dry_run) — gh release create "$NEW_TAG" --target "$RELEASE_SHA" --title "$NEW_TAG" --notes-file release-notes.md. The single-call --target <sha> form atomically creates the tag and the release, avoiding the orphan-tag failure mode where a separately-pushed tag survives a failed release create.

Supporting changes

  • .github/scripts/draft-release-notes.py (new, 173 lines) — collects git log (full bodies) + git diff --stat between the prev tag and the pinned SHA, prompts Bedrock (default us.anthropic.claude-sonnet-4-5-20250929-v1:0) for {bump, notes}, writes release-notes.md and bump.txt to $GITHUB_WORKSPACE. The bump suggestion is advisory only — the maintainer types the version at dispatch.
  • .github/workflows/python-security-audit.yml (new) — reusable workflow_call. Builds the full dependency closure in a temp venv and runs pip-audit. Mirrors typescript-security-audit.yml. continue-on-error: true (informational; PR-time vulns are gated by Dependency Review in ci.yml, pre-existing advisories by Dependabot).
  • .github/workflows/python-test-package-build.yml (new) — reusable workflow_call. python -m build in strands-py/, then pip install dist/*.whl in a venv created outside $GITHUB_WORKSPACE and python -c "import strands" from that tmp dir. The out-of-tree venv is load-bearing: a venv inside the repo can fall back to in-tree source and mask an incomplete wheel (the Python equivalent of the wheel-packaging bug typescript-test-package-pack.yml was written to catch).
  • python-integration-test.yml / typescript-integration-test.yml — added workflow_call(ref) entry point (so release.yml can reuse them against the pinned SHA while keeping AWS secrets) plus a defense-in-depth validate-call-ref job that rejects fork repos and refs/pull/* / owner:branch-shaped refs. The trust model is documented in a block comment above each new trigger: today's only caller (release.yml) is workflow_dispatch-gated, which is what justifies skipping authorization-check on the workflow_call path.

How this was exercised

The workflow_call trust changes and the release.yml orchestration are not fully exercisable until the release-python / release-typescript deployment environments exist on the repo (they do not yet — see step 4) — flagging this explicitly because the fork-rejection / ref-shape guards in validate-call-ref cannot be validated end-to-end from this PR alone.

Related Issues

Documentation PR

N/A — release pipeline is contributor-facing only.

Type of Change

Other: CI / release infrastructure.

Testing

  • I ran hatch run prepare (no Python source files changed; lint/format unaffected).

Checklist

  • I have read the CONTRIBUTING document
  • I have reviewed and understand every line of code in this PR, including any generated by AI tools, and I can explain why it works
  • My change is focused and reasonably small; I have split unrelated work into separate PRs
  • I have added any necessary tests that prove my fix is effective or my feature works
  • I have updated the documentation accordingly
  • I have added an appropriate example to the documentation to outline the feature, or no new docs are needed
  • My changes generate no new warnings
  • Any dependent changes have been merged and published

By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

@github-actions github-actions Bot added size/l area-community Related to community and contributor health chore Maintenance tasks, dependency updates, CI changes, refactoring with no user-facing impact strands-running labels Jun 24, 2026
@codecov

codecov Bot commented Jun 24, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Comment thread .github/workflows/release.yml Outdated
set -euo pipefail

# 1. Validate the typed version
if ! [[ "$NEW_VERSION" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]; then

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.

Issue: This step accepts any valid MAJOR.MINOR.PATCH and the drafting step even reasons about major bumps, but the downstream Python publish workflow only fires for 1.x:

# python-pypi-publish-on-release.yml
if: startsWith(github.event.release.tag_name, 'python/v1.')

So a python/v2.0.0 release here would tag the SHA and create the GitHub release, but silently never publish to PyPI — the most dangerous kind of release failure because the run looks green. (The TypeScript gate is typescript/v, with no major pin, so TS is unaffected.)

Suggestion: Either relax the publish gate to python/v to match this pipeline's capability, or, if the v1. pin is intentional, reject package == 'python' majors >= 2 here with a clear error so the limitation is explicit rather than a silent no-op.

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.

Resolved in 5c53369scan-commits now rejects package == 'python' majors >= 2 loudly (lines 88–99) with an error message pointing at the python/v1. gate. This is exactly the "reject loudly until the gate is widened" option. 👍 Thanks for closing the silent-no-op hole.

print(f"Could not parse Bedrock response as JSON: {exc}", file=sys.stderr)
return 2

bump = result.get("bump", "unknown")

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.

Issue: bump from the model is written straight to bump.txt without validating it's one of major/minor/patch. The docstring promises that constraint, and the run summary presents it as the advisory bump. A malformed/hallucinated value would flow through unchecked.

Suggestion: Low priority since it's advisory-only, but a quick if bump not in {"major","minor","patch"}: bump = "unknown" keeps the output honest with the documented contract.

publish-release:
name: Create GitHub release
needs: [scan-commits, draft-summary, approve-release]
if: inputs.dry_run != true

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.

Issue: approve-release runs unconditionally (it only needs scan + draft-summary), and publish-release is what's gated by dry_run != true. That means a dry run still pauses on the release-${{ inputs.package }} environment and burns two reviewer approvals just to produce the rendered notes — yet dry_run defaults to true, so this is the common path. It also trains reviewers to approve dry runs reflexively, which erodes the value of the gate on real releases.

Suggestion: Add if: inputs.dry_run != true to approve-release (and keep it as a needs of publish-release) so approvals are only requested for actual releases. The drafted notes in the run summary are already available without the approval pause.

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.

Resolved in 5c53369approve-release now carries if: inputs.dry_run != true (line 347) with a comment explaining the rubber-stamp concern, and publish-release still gates independently. Dry runs no longer consume reviewer approvals. 👍

@github-actions

Copy link
Copy Markdown
Contributor

Issue: The PR description is the unfilled template — no summary, no linked issue, and the Testing section is blank. For a release pipeline that adds a workflow_call entry point to the secret-bearing pull_request_target integ workflows, reviewers need to know how this was exercised (the workflow-call trust model especially can't be validated by CI alone — see AGENTS.md: "Actually exercise the change, don't just rely on the gate").

Suggestion: Fill in the description with the why, link any tracking issue, and note how the pipeline was validated end to end (e.g. a dry-run in a fork/test repo). Even "couldn't fully exercise X because it needs the configured release environment" is useful to state explicitly.

@github-actions

Copy link
Copy Markdown
Contributor

Assessment: Request Changes

Well-structured release pipeline with a genuinely thoughtful security model — the workflow_call trust invariant (fork rejection + fork-PR/cross-repo ref shape checks in validate-call-ref, plus the auth-skip justification) is documented carefully and reasons about the right threats. The SHA-pinning and atomic gh release create --target approach are also nice. A few correctness/operational gaps are worth resolving before merge.

Review themes
  • Publish-gate mismatch (most important): This pipeline allows arbitrary semver / major bumps, but python-pypi-publish-on-release.yml only fires for python/v1. — a Python major would tag + release but silently skip PyPI. Either align the gate or reject it loudly here.
  • Missing setup doc: RELEASE_WORKFLOW_SETUP.md is referenced but absent; the required environments/reviewers/branch restrictions are prerequisites for the pipeline to work.
  • Approval cost on dry runs: approve-release runs even for dry runs (the default), burning two reviewer approvals and conditioning reviewers to rubber-stamp.
  • Operational hardening: no timeout-minutes on the Bedrock/integ-bearing jobs; advisory bump isn't validated against its documented value set.
  • PR hygiene: description is the unfilled template — no why, no linked issue, no note on how the security-sensitive workflow_call change was exercised.

The security thinking behind the workflow_call entry point is the strongest part of this PR — nicely done.

@github-actions

Copy link
Copy Markdown
Contributor

Re-review after force-push (a833c83) — thanks for the update. I diffed the new commit against the prior one; the code for all five outstanding items is unchanged, so the earlier review still stands:

  • ⚠️ Setup doc: thread was marked resolved, but RELEASE_WORKFLOW_SETUP.md is still missing and release.yml:28 still references it.
  • 🔄 Publish-gate mismatch (most important): scan-commits still has no guard against Python majors, while python-pypi-publish-on-release.yml only fires on python/v1..
  • 🔄 Dry-run approvals: approve-release still lacks if: inputs.dry_run != true.
  • 🔄 bump validation: draft-release-notes.py:159 still writes the model value unchecked.
  • 🔄 Timeouts: no timeout-minutes on any job.

Happy to dig into any of these if a recommendation is unclear — assessment remains Request Changes until the publish-gate and setup-doc items are addressed.

@github-actions

Copy link
Copy Markdown
Contributor

Re-review after 5c53369 — all blocking items are resolved. 🎉

Prior finding Status
Publish-gate mismatch (Python major silent no-op) scan-commits now rejects python major >= 2 loudly with a message pointing at the v1. gate
Dangling RELEASE_WORKFLOW_SETUP.md reference ✅ Reference removed; setup checklist inlined into the release.yml header
Dry-run burning reviewer approvals approve-release now gated by if: inputs.dry_run != true
PR description was the unfilled template ✅ Thorough description with the why, per-step rationale, security model, and an explicit note on what can't be exercised until the environments exist

Two optional, non-blocking nits remain — neither needs to hold up merge:

  • bump validation (draft-release-notes.py:159): the model value is still written to bump.txt unverified. Advisory-only, so low risk; a one-line if bump not in {"major","minor","patch"}: bump = "unknown" would keep it honest with the docstring.
  • timeout-minutes: still absent on the Bedrock/integration jobs. A safety cap would prevent a hung run from holding resources, but it's a hardening nice-to-have.

Assessment: Approve — the publish-gate fix in particular turns the most dangerous failure mode into an explicit error. The two remaining nits are yours to take or leave.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area-community Related to community and contributor health chore Maintenance tasks, dependency updates, CI changes, refactoring with no user-facing impact size/l

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant