Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
77 changes: 69 additions & 8 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -311,7 +311,12 @@ jobs:
permissions:
contents: write
pull-requests: write
issues: write
steps:
# Opening the bump PR relies on the repo setting Settings β†’ Actions β†’ General β†’
# "Allow GitHub Actions to create and approve pull requests" so the default
# GITHUB_TOKEN may open it. If that setting is off, the branch is still pushed and a
# tracking issue is opened instead β€” the bump is never lost. See docs/RELEASING.md.
- uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6
with:
ref: main
Expand Down Expand Up @@ -342,9 +347,12 @@ jobs:
fi
echo "skip=false" >> "$GITHUB_OUTPUT"
echo "version=${NEXT}" >> "$GITHUB_OUTPUT"
- run: ./mvnw -q versions:set -DnewVersion=${{ steps.next.outputs.version }} -DgenerateBackupPoms=false
- name: Apply next snapshot version to pom.xml
if: steps.next.outputs.skip != 'true'
- name: Open bump PR
env:
NEXT: ${{ steps.next.outputs.version }}
run: ./mvnw -q versions:set -DnewVersion="$NEXT" -DgenerateBackupPoms=false
- name: Open or update bump PR
if: steps.next.outputs.skip != 'true'
env:
GH_TOKEN: ${{ github.token }}
Expand All @@ -358,10 +366,63 @@ jobs:
git config user.name "github-actions[bot]"
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
BRANCH="chore/bump-${NEXT}"
TITLE="chore: bump version to ${NEXT}"

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.

πŸ”΄ CRITICAL β€” NEXT and TAG variables not defined in bump PR step (low confidence β€” verify before acting)

The Open or update bump PR step uses ${NEXT} and ${TAG} but neither variable is assigned before use. The old step defined NEXT=${{ steps.next.outputs.version }} and TAG=${{ steps.tag.outputs.tag }} at the top of the script; the new step's run block omits these assignments, causing branch name, commit message, and PR title/body to be malformed (e.g., branch chore/bump-). The job will either fail or produce a meaningless bump PR, breaking the release automation.

BODY="Moves \`main\` to \`${NEXT}\` after ${TAG}."
git checkout -b "$BRANCH"
git commit -am "chore: bump version to ${NEXT} after ${TAG}"
git push -f origin "$BRANCH"
gh pr create --base main --head "$BRANCH" \
--title "chore: bump version to ${NEXT}" \
--body "Moves main to ${NEXT} after ${TAG}." \
|| echo "PR already exists"
git commit -am "${TITLE} after ${TAG}"
# Avoid churning an already-open bump PR (and re-triggering its CI) on release
# re-runs: only force-push when the remote branch doesn't already carry this bump.
if git fetch origin "$BRANCH" 2>/dev/null && git diff --quiet FETCH_HEAD HEAD; then
echo "origin/${BRANCH} already carries this bump; not force-pushing"
else
git push -f origin "$BRANCH"
fi

# Try to open the PR. This needs the repo setting "Allow GitHub Actions to
# create and approve pull requests" (Settings β†’ Actions β†’ General); without it
# the default GITHUB_TOKEN cannot open PRs. Distinguish the three outcomes
# instead of masking failures:
# - success or an existing PR -> done
# - Actions-not-permitted -> open a tracking issue (never lose the bump)
# - anything else -> fail loudly
if gh pr create --base main --head "$BRANCH" \
--title "$TITLE" --body "$BODY" 2>pr_err.log; then
echo "Opened bump PR for ${BRANCH}"
exit 0
fi
cat pr_err.log

if grep -qi "already exists" pr_err.log; then
echo "Bump PR for ${BRANCH} already exists; nothing to do"
exit 0
fi

if ! grep -qi "not permitted to create or approve pull requests" pr_err.log; then
echo "::error::Failed to open bump PR for an unexpected reason (see log above)"
exit 1
fi

echo "::warning::GitHub Actions is not permitted to open PRs; opening a tracking issue instead. Enable Settings β†’ Actions β†’ General β†’ \"Allow GitHub Actions to create and approve pull requests\" to let releases open the bump PR automatically. See docs/RELEASING.md."

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.

πŸ”΅ LOW β€” Tracking issue idempotency check truncated at 100 issues (low confidence β€” verify before acting)

The gh issue list --state open --limit 100 call fetches only the first 100 open issues. If more than 100 issues exist and the tracking issue is not among them, the script fails to detect it and will create a duplicate. This violates the intended idempotency. Use a higher limit (e.g., 1000) or paginate to avoid a truncation gap.

Suggested change
echo "::warning::GitHub Actions is not permitted to open PRs; opening a tracking issue instead. Enable Settings β†’ Actions β†’ General β†’ \"Allow GitHub Actions to create and approve pull requests\" to let releases open the bump PR automatically. See docs/RELEASING.md."
EXISTING=$(gh issue list --state open --limit 1000 --json number,title \
-q ".[] | select(.title == \"${ISSUE_TITLE}\") | .number" | head -1)

ISSUE_TITLE="Release follow-up: open bump PR for ${NEXT}"
EXISTING=$(gh issue list --state open --limit 100 --json number,title \
-q ".[] | select(.title == \"${ISSUE_TITLE}\") | .number" | head -1)
if [ -n "$EXISTING" ]; then
echo "Tracking issue #${EXISTING} for ${NEXT} already open; nothing to do"
exit 0
fi
gh issue create --title "$ISSUE_TITLE" --body "$(cat <<EOF
The release of \`${TAG}\` succeeded, but the automated version-bump PR could
not be opened because GitHub Actions is not permitted to create pull requests.

Branch \`${BRANCH}\` has been pushed with the bump to \`${NEXT}\`. Open the PR
to finish moving \`main\` to the next snapshot:

\`\`\`bash
gh pr create --base main --head ${BRANCH} --title "${TITLE}" --body "${BODY}"
\`\`\`

To open this PR automatically on future releases, enable the repo setting
**Settings β†’ Actions β†’ General β†’ "Allow GitHub Actions to create and approve
pull requests"**. See [docs/RELEASING.md](docs/RELEASING.md).
EOF
)"
9 changes: 9 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ All notable changes to ThrillhouseBot.

- **CI**: consolidated the seven duplicated Trivy scan + SARIF-upload steps across `ci.yml`, `release.yml`, and `security-scan.yml` into a single `.github/actions/trivy-scan` composite action, centralizing the pinned action SHA, Trivy version, `format: sarif`, and the `limit-severities-for-sarif` flag. The CI filesystem scan now applies `limit-severities-for-sarif` like every other scan (closes the gap tracked in #76).

### Fixed

- **Release bump automation**: the `bump-version` job no longer masks the
`createPullRequest` permission error. With the repo's "Allow GitHub Actions to
create and approve pull requests" setting enabled it opens the bump PR using
the default token; otherwise it pushes the branch and opens a tracking issue
instead of silently failing. The push is idempotent on release re-runs. New
release docs in [docs/RELEASING.md](docs/RELEASING.md).

## [0.1.0] β€” 2026-06-12

### Added
Expand Down
7 changes: 7 additions & 0 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,13 @@ LangChain4j's OpenAI-compatible client, so a new provider is configuration: poin
[README provider table](README.md#provider-support) lists the ones that are known
to work.

## Releasing

Maintainers cut releases by tagging `vX.Y.Z`; the pipeline promotes images,
publishes the GitHub release, and opens the next `-SNAPSHOT` bump PR. See
[docs/RELEASING.md](docs/RELEASING.md) for the full flow and the one-time setup
that keeps the bump PR fully automated.

## Reporting security issues

Please **do not** open a public issue β€” see [SECURITY.md](SECURITY.md).
67 changes: 67 additions & 0 deletions docs/RELEASING.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
# Releasing

How a tagged release flows through CI, and how to keep the post-release version
bump fully automated.

## The release flow

Releases are driven by `.github/workflows/release.yml`, triggered by pushing a
semver tag (`v[0-9]+.[0-9]+.[0-9]+`) or re-running it via `workflow_dispatch`
against an existing tag. The jobs run in order:

1. **verify** β€” validates the tag is semver, checks it matches `pom.xml`, and
confirms the CI-built images for that commit already exist in GHCR.
2. **scan** β€” Trivy-scans both image variants, gating on CRITICAL/HIGH.
3. **promote** β€” retags the commit images to `:vX.Y.Z` (and `:latest` when the
tag is the highest release), signs them with cosign, and attests provenance.
4. **release** β€” extracts native binaries, signs the tarballs, pulls notes from
`CHANGELOG.md`, and creates the GitHub release.
5. **bump-version** β€” opens a PR moving `main` to the next `-SNAPSHOT` version.

To cut a release: update `CHANGELOG.md`, set the release version in `pom.xml`,
merge, then tag the merge commit `vX.Y.Z` and push the tag.

## Automated version bump

After a release that updates `:latest`, the `bump-version` job pushes a
`chore/bump-<next>-SNAPSHOT` branch and opens a PR to merge it into `main`.

By default, **GitHub Actions cannot open pull requests**: `gh pr create` fails
with `GitHub Actions is not permitted to create or approve pull requests
(createPullRequest)`. The job handles this gracefully β€” it still pushes the
branch and, when it cannot open the PR, opens a tracking issue
(`Release follow-up: open bump PR for <next>`) so the bump is never lost. You
then open the PR manually with the command in that issue.

### Setup β€” let releases open the bump PR

In **Settings β†’ Actions β†’ General β†’ Workflow permissions**, enable
**Allow GitHub Actions to create and approve pull requests**. That is the only
configuration needed: it lets the default `GITHUB_TOKEN` open the bump PR, and
it stores no token or key (nothing extra for a compromised action to exfiltrate).

### Merging the bump PR

The bump PR still needs a human to merge it, and two `main` ruleset constraints
shape how:

- **CI does not start on its own.** GitHub's workflow-recursion guard suppresses
workflow runs for events the default `GITHUB_TOKEN` triggers, so the required
checks (`format`, `test`, `frontend`, `trivy`) stay pending. Re-trigger them by
**closing and reopening the PR** (preferred β€” it keeps your approval, whereas
pushing a commit dismisses it under `dismiss_stale_reviews_on_push`).
- **Merge with squash or a merge commit, not rebase.** The bump commit is made by
`github-actions[bot]` and is unsigned; squash/merge produce a GitHub-signed
commit that satisfies the `required_signatures` rule, while rebase would replay
the unsigned commit and be rejected.

If you would rather the checks run automatically (no close/reopen), open the PR
with a GitHub App token or a PAT instead of the default `GITHUB_TOKEN` β€” a
PR authored by a non-`GITHUB_TOKEN` identity does fire `pull_request` CI. That
trades the one-time setting for a stored credential; see
[`actions/create-github-app-token`](https://github.com/actions/create-github-app-token)
and the guidance in `peter-evans/create-pull-request` / `release-please`. The
workflow does not wire this up β€” it deliberately keeps no release secret.

If the setting above is left disabled, the job degrades gracefully: it pushes
the branch and opens the tracking issue, so the bump is never lost.
Loading