Skip to content

ci: replace the PR-title bump script with release-please - #86

Merged
mogita merged 3 commits into
masterfrom
chore/release-please
Sep 14, 2026
Merged

ci: replace the PR-title bump script with release-please#86
mogita merged 3 commits into
masterfrom
chore/release-please

Conversation

@mogita

@mogita mogita commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator

Ticket

CHA-2963. Third repo, after getstream-go and stream-py (#287).

Problem

The bump script computed the version from the merged PR title, committed version.rb locally so the tag pointed at a correct tree, and pushed only the tag. The default branch was never updated, so lib/getstream_ruby/version.rb on master still says 8.0.2 while RubyGems is at 12.1.0.

Solution

release-please keeps the bump in a reviewable Release PR, so the version on master matches the release.

  • release-please-config.json / .release-please-manifest.json, ruby strategy, manifest seeded at 12.1.0.
  • version-file is explicit: the strategy would otherwise derive lib/getstream/ruby/version.rb from the gem name, and the real path is lib/getstream_ruby/version.rb.
  • include-component-in-tag: false keeps the tag as vX.Y.Z. The ruby strategy sets tagSeparator to /, so a component would make it getstream-ruby/v12.1.1.
  • last-release-sha is the master commit v12.1.0 was released from. The tag itself sits on the off-branch bump commit the old workflow created, so release-please cannot reach it by walking master and would otherwise treat the whole history as unreleased.
  • The tag, the GitHub Release and the gem push are all irreversible, so the suite runs before them: a detect job finds the merged Release PR waiting to be tagged, and the release job refuses to tag a commit the suite did not run on.
  • The test jobs move into a reusable run_tests.yml that both ci.yml and the release gate call. ci.yml drops its push trigger: running on push and pull_request raced the duplicate integration legs against the Stream app several SDK repos share.
  • pr_title.yml added, matching getstream-go. With squash merges the PR title becomes the commit subject, so a non-conventional title silently ships nothing.
  • Deletes the hand-bump paths the auto-release already made unsafe: scripts/version-bump.sh, scripts/release/bump_version.rb, and the Makefile patch/minor/major targets.
  • CHANGELOG backfilled 11.0.0 through 12.1.0, and the [Unreleased] section moved under 12.1.1's actual release, 11.0.1.

How to verify

Dry-run against a throwaway branch carrying one fix: commit (release-please release-pr --dry-run --trace), now deleted:

  • proposes 12.1.1 from base v12.1.0
  • lib/getstream_ruby/version.rb 8.0.2 -> 12.1.1
  • CHANGELOG.md section inserted above ## [12.1.0]
  • compare link v12.1.0...v12.1.1, so the tag shape is unchanged

actionlint clean on all four workflows.

Expected on merge: no Release PR, because ci: is a hidden type.

Review instructions

Two things this needs from someone with admin on the repo, neither of which can be in the diff:

  1. allow_merge_commit: false and squash_merge_commit_title: PR_TITLE (getstream-go and stream-py both have these, plus allow_rebase_merge: false and squash_merge_commit_message: BLANK). release-please reads commit messages, never PR titles, and splitMessages in src/commit.ts has no ! branch, so a feat!: PR title inside a merge commit is dropped entirely. Concretely: PR feat!: [FEEDS-1830] regenerate OpenAPI client #81 was titled feat!: [FEEDS-1830] regenerate OpenAPI client and cut v12.0.0, but on master that lands as feat: under a merge commit, which release-please would score as a minor. Until this changes, pr_title.yml lints a string that never reaches release-please.
  2. master has no branch protection at all, so the "not pushed to the protected default branch" reasoning in the old workflow was never true. Not this PR's problem, but worth knowing.

The CHANGELOG.md now starts with a # Changelog title. That is load-bearing, not cosmetic: the updater searches for /\n###? v?[0-9[]/, and without a preceding line the newest ## [...] heading does not match, so every generated section lands underneath it instead of on top.

The bump script derived the version from the merged PR title, committed
version.rb locally so the tag pointed at a correct tree, and pushed the tag
without ever updating the default branch. lib/getstream_ruby/version.rb on master
therefore says 8.0.2 while RubyGems is at 12.1.0.

release-please keeps the bump in a reviewable Release PR instead, so version.rb
follows the release. Gemfile.lock is gitignored here, so the ruby strategy's
Gemfile.lock updater is a no-op. include-component-in-tag keeps the tag as vX.Y.Z:
the ruby strategy sets tagSeparator to "/", so a component would make it
getstream-ruby/v12.0.1.

bootstrap is via last-release-sha, the master commit v12.1.0 was released from.
The tag itself sits on the bump commit the old workflow created off-branch, so
release-please cannot find it by walking master.

The tag, the GitHub Release and the gem push are all irreversible, so the suite
now runs before them: a detect job finds the merged Release PR waiting to be
tagged and the release job refuses to tag a commit the suite did not run on.

The test jobs move to a reusable run_tests.yml that both ci.yml and the release
gate call, and ci.yml drops its push trigger. Running on push and pull_request
raced the duplicate integration legs against the Stream app several SDK repos
share.

Also deletes the hand-bump paths that the auto-release made unsafe:
scripts/version-bump.sh and the Makefile patch/minor/major targets.

@mogita mogita left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Reviewed at dc63d3f. Verdict: comment / needs discussion. 3 Must Fix, 5 Should Fix, all inline.

The design is sound and the two-half split (reversible Release PR, gated irreversible tag/publish) is the right shape. What needs attention is the recovery path: a single failed release run leaves autorelease: pending set, and from then on every push to master runs the full 30-minute suite and then fails the tag guard. That one is on release.yml:107.

No line to sit on:

  • The GCP load-balancer keep-alive suite has no home in run_tests.yml. Flagged inline on run_tests.yml:44 because that is where the other integration jobs live, but the decision is "port it or retire it", not a line edit.
  • master has no branch protection and the repo still allows merge and rebase merges, so nothing enforces the commit-subject shape the whole pipeline depends on. See the README.md comment.

Checked and clean, so nobody re-checks them: the needs/if wiring does gate correctly (an if without a status function still carries an implicit success(), so a failed tests does skip release); the reusable workflow's cancel-in-progress: true cannot cancel a release gate, because release.yml's own cancel-in-progress: false holds the second run pending and a pending run starts no jobs; environment: ci inside the called workflow does win over inherited secrets, so the integration jobs keep the same key/secret pairs they had; the CHANGELOG heading shape matches what the release-please updater looks for; and the 11.0.1 attribution of the old [Unreleased] block is correct (531f4cc first ships in v11.0.1, dated 2026-08-24).

Smaller things not worth a thread: no bundler-cache: true on the four jobs that each run an uncached bundle install; .ruby-version says 3.3.0 while all five workflow sites hardcode 3.1.0; the three integration jobs differ only in a make target and three var names, so they are a matrix; v10.1.0 is tagged but missing from the backfilled CHANGELOG; publish's permissions: contents: read restates the workflow default.

make security

integration-chat:
name: Chat Integration Tests

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[Must Fix] The integration-gcp-lb job is gone and nothing replaces it.

The old ci.yml had five jobs; this file has four. Makefile:43-44 still defines test-integration-gcp-lb and spec/integration/gcp_lb_keepalive_spec.rb still exists, but grep -rn -i gcp .github/ at this revision returns nothing, so no workflow runs it any more. The ci environment's STREAM_GCP_API_SECRET, STREAM_GCP_API_KEY and STREAM_GCP_BASE_URL become unreferenced at the same time.

Why it matters: that spec is the regression guard for the bug this repo shipped as 11.0.1, pooled TLS connections outliving the GCP SSL-proxy idle window and failing the next request with SSL_read: unexpected eof. It is now a test that exists and never runs, which is worse than not having it.

Fix: add an integration-gcp-lb job here mirroring the deleted one, or delete the make target, the spec and the three ci environment values together and say so in the PR body.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in d886781, and the cause is worse than a judgement call: I built run_tests.yml from a local ci.yml that was 13 commits behind origin/master, so the fifth job was never in front of me. git show origin/master:.github/workflows/ci.yml has integration-gcp-lb: at line 117.

Restored verbatim, including the vars.STREAM_GCP_API_KEY || vars.STREAM_API_KEY fallbacks, plus a timeout-minutes: 30 to match its siblings. STREAM_GCP_API_SECRET is declared required: false in the workflow_call block so the fallback still resolves.

run: make test-integration-chat

integration-feed:
name: Feed Integration Tests

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[Should Fix] Chat and feed now run in parallel on the release path, and both mutate the same app-global setting.

spec/integration/chat_channel_integration_spec.rb:898-937 and spec/integration/feed_integration_spec.rb:899-946 do the same thing: read file_upload_config, update_app to blank it, sleep 2, assert on an upload, then restore the original in an ensure. They race: one suite's restore can land while the other is still asserting against the blanked config.

This race already existed on the PR path, where the old ci.yml also ran them as separate jobs. What is new is the release path. The old release.yml ran chat, feed and video as sequential steps inside one job, so they could not overlap there. Now the release gate calls this workflow, so a flake here fails the gate and blocks the tag.

Fix is in the specs, not this file, but it is this change that makes it reachable: scope both tests to their own app, or drop the app-global mutation and assert against a per-test channel config.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in d886781, in this file rather than the specs: integration-feed now has needs: integration-chat.

Serialising the two is a one-line change that closes the race on both paths, where scoping the tests to their own app is a spec change in two files that I would rather not fold into a release-plumbing PR. It costs the feed suite its parallelism on every PR, which is the honest trade.

Your reading of the new exposure is right and is why it is worth paying now: the old release.yml ran chat, feed and video as sequential steps in one job, so the release path could not hit this.

if: github.event_name == 'workflow_dispatch' || github.event.pull_request.merged == true
# Reversible half: keep the Release PR current. Never gated.
release-pr:
name: Release PR

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[Should Fix] unverified: release-pr has no needs, so on the push that merges a Release PR it runs immediately while release waits behind the full suite, up to 30 minutes.

In that window the tag does not exist yet. v12.1.0 is not reachable from master (git merge-base --is-ancestor v12.1.0 283aabc exits 1), which is exactly why last-release-sha is pinned. So during the window release-please has no release commit to stop its walk at and falls back to the pin, which means the commits the merged Release PR already consumed are still in scope. The reachable outcome is a second Release PR proposing another bump with duplicated changelog entries.

I did not read Manifest.buildPullRequests to confirm the resolution order, so this is not stated as fact. The check that settles it: merge the first release-please Release PR after this lands and watch that same run. If the Release PR job opens a new Release PR while Tests is still going, it is real.

Cheap guard either way: needs: detect plus if: needs.detect.outputs.pending != 'true', so release-pr stands down while a release is mid-flight.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Guarded in d886781, without resolving the unverified:. release-pr now has needs: detect and if: needs.detect.outputs.pending != 'true', so it stands down for the whole window rather than racing it.

Went with the guard over reading Manifest.buildPullRequests because the guard is correct whichever way the resolution order falls, and standing down while a release is mid-flight is the behaviour I want regardless. Your check still settles the question if anyone wants the answer: watch the first real Release PR merge and see whether the Release PR job would have opened another.

Comment thread .github/workflows/release.yml Outdated
echo "should_release=false" >> "$GITHUB_OUTPUT"
echo "bump=none" >> "$GITHUB_OUTPUT"
sha="$(gh api "repos/${GITHUB_REPOSITORY}/pulls?state=closed&base=${GITHUB_REF_NAME}&sort=updated&direction=desc&per_page=50" \
--jq '[.[] | select(.merged_at != null and ([.labels[].name] | index("autorelease: pending")))] | .[0].merge_commit_sha // empty')"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[Should Fix] per_page=50 with no pagination can decide there is no release to make, and pass.

The query sorts all closed PRs on the branch by updated descending and takes the first one labelled autorelease: pending. Right after a Release PR merges it is at the top, so the normal path is fine. But once a release is stuck pending (see the comment on the tag guard below), that PR slides down as other PRs close and get updated. Past 50 entries it drops off the page, pending=false, and the workflow goes green having released nothing.

A silent no-release on the workflow that pushes gems is worth closing. Query the label directly instead:

gh api "search/issues?q=repo:${GITHUB_REPOSITORY}+is:pr+is:merged+base:${GITHUB_REF_NAME}+label:%22autorelease:+pending%22"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in d886781. detect now asks for the label directly:

gh api "repos/${GITHUB_REPOSITORY}/issues" -X GET -f state=closed -f labels='autorelease: pending' -f per_page=20

then fetches that one PR for merged_at, base.ref and merge_commit_sha. Two calls, no client-side filtering, no page to fall off.

Used the issues endpoint rather than search/issues to avoid the search index lag, which would bite exactly at the moment that matters, the seconds right after a Release PR merges.

Comment thread .github/workflows/release.yml Outdated
else
git commit -m "chore(release): v${VERSION}"
if [ "$TESTED" != "$PENDING" ]; then
echo "::error::The pending release is tagged at $PENDING but this run tested $TESTED."

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[Must Fix] One failed release run turns this workflow red on every later push to master, and burns the full suite each time.

autorelease: pending is cleared only when release-please runs in the release job below. If that job never succeeds, because tests went red (the integration suite runs against a Stream app five SDK repos share, so a flaky red is routine), or detect failed, or the run was cancelled, the merged Release PR keeps the label at its merge commit M. From then on:

  1. Next push lands commit N.
  2. detect still finds M, emits pending=true.
  3. tests runs the whole suite on N, because line 78 gates on pending only, not on the sha.
  4. This guard compares TESTED=N against PENDING=M, and exits 1.

Step 4 repeats on every push until somebody re-runs the old run for M, and nothing at the point of failure says that. The comment above says the two shas "diverge on a dispatch after the branch has moved", but this is a plain push and it diverges too.

It compounds: if a later Release PR is merged and tagged successfully, its label flips to tagged and this query selects the stale pending one again, so a healthy release does not clear the stuck state.

Fix: make the sha match part of the gate rather than a terminal error. Compare in detect, emit a second output, gate tests and release on it, and log ::warning:: when a pending release sits at a different commit. That keeps the "never tag an untested tree" property, stops spending 30 minutes per push, and leaves master green.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in d886781, along the line you sketched. The terminal guard is gone from the release job. detect now does the comparison itself and emits ready alongside pending:

  • no pending release: pending=false, ready=false, nothing runs
  • pending at this run's commit: both true, suite runs, tag follows
  • pending at a different commit: pending=true, ready=false, and a ::warning:: naming the commit and telling you to re-run that run

tests and release gate on ready, so the stuck case costs one API call instead of the full suite plus a red X, and master stays green. The never-tag-an-untested-tree property is unchanged, it just moved from a failure into the gate.

The compounding case you describe is also closed, from the other side: release-pr now stands down while anything is pending, so a later Release PR cannot be created and merged on top of a stuck one.

Comment thread .github/workflows/ci.yml
branches: [ master, main ]
pull_request:
branches: [ master, main ]

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[Should Fix] A PR into an N.x hotfix branch gets no tests at all.

This filter is [ master, main ], so a PR into 12.x triggers nothing. release.yml does push-trigger on '*.x', but its tests job is gated on a pending release, so the first time a hotfix is tested is the Release PR merge, when the release is already being cut. The README documents N.x as the supported hotfix path, so this is a live path with no gate.

One-line fix: branches: [ master, main, '*.x' ].

Separately, the comment above overstates the master coverage. release.yml only runs the suite when a release is pending, so an ordinary PR merge to master and a direct push to master (still possible, there is no branch protection) now run nothing. Nothing untested ships, since the Release PR merge is still gated, but the post-merge signal is gone. Worth saying that plainly in the comment rather than "master is covered by release.yml".

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Both fixed in d886781. The filter is [ master, main, '*.x' ].

The comment now says what is actually true rather than claiming master is covered:

# The trade is that a merge to master, and a direct push to it,
# now run nothing: release.yml runs this same workflow only when a release is pending,
# so the gate before a tag is covered but the routine post-merge signal is gone.

@@ -0,0 +1,3 @@
{
".": "12.1.0"
}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[Should Fix] The manifest says 12.1.0 but lib/getstream_ruby/version.rb still says 8.0.2.

$ git show 283aabc:lib/getstream_ruby/version.rb | grep VERSION
  VERSION = '8.0.2'
$ git show v12.1.0:lib/getstream_ruby/version.rb | grep VERSION
  VERSION = '12.1.0'

The manifest value is the correct one. The tracked file is four majors stale because the old workflow committed the bump off-branch and never pushed it, which is the problem this PR exists to fix. getstream-ruby.gemspec:8 reads that constant, so master today builds a getstream-ruby-8.0.2.gem and make version reports 8.0.2.

release-please will overwrite it at the next release either way, but the first Release PR then shows 8.0.2 -> 12.1.1, which reads as corruption to whoever reviews it. Set the file to 12.1.0 in this PR so the tree matches the manifest and the tag.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in d886781, version.rb set to 12.1.0.

You are right about the reason to do it here rather than let the first Release PR carry it: 8.0.2 -> 12.1.1 in a bot PR reads as something going wrong. 12.1.0 -> 12.1.1 reads as a release. It also means master stops building a getstream-ruby-8.0.2.gem in the meantime.

Comment thread README.md Outdated
Releases are driven by [release-please](https://github.com/googleapis/release-please).

- Merge PRs to `master` with conventional-commit titles. The repo is squash-only with
`squash_merge_commit_title: PR_TITLE`, so the PR title becomes the commit subject and

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[Must Fix] This states two repo settings as fact. Neither is set.

Actual settings today: allow_merge_commit: true, allow_rebase_merge: true, squash_merge_commit_title: COMMIT_OR_PR_TITLE.

The paragraph is right that both are load-bearing, which is why documenting them as done is the problem. Two ways a release goes wrong while this text says it cannot:

  • A maintainer picks "Create a merge commit". The subject is Merge pull request #N from ..., not conventional, so release-please ignores the change entirely. A feat!: title in the merge body is not rescued either: splitMessages only lifts a plain feat:/fix:/... prefix out of a merge body, it has no ! branch, so a breaking change silently downgrades.
  • COMMIT_OR_PR_TITLE uses the commit subject when a PR has exactly one commit. A PR titled feat: add X whose single commit says wip lands as wip. pr_title.yml passes, because it only ever looks at the PR title field, and the release does not fire.

getstream-go has allow_merge_commit: false and squash_merge_commit_title: PR_TITLE for exactly this reason.

Fix: flip the two settings before merging and keep this text, or keep the settings as they are and change the text to say what is still required. Right now a contributor reading the README has no way to know the guarantee is absent.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in d886781 by changing the text, since I have write and not admin on this repo and cannot flip the settings.

The paragraph now says squashing is a convention and not yet enforced, names the three current values, names the two required ones with getstream-go as the precedent, and spells out both failure modes: the merge commit whose body only ever yields a plain feat:, and COMMIT_OR_PR_TITLE taking a single commit's subject over the PR title. It also says pr_title.yml passes in both cases, so nobody reads a green check as protection.

The settings still need someone with admin. Flagged in the PR description too.

The reusable workflow was built from a stale local ci.yml and silently dropped
integration-gcp-lb, the regression guard for the pooled-TLS bug shipped as 11.0.1.
Restored, and integration-feed now runs after integration-chat because both blank
and restore the app-global file_upload_config.

A release that failed after its Release PR merged kept autorelease: pending, and
the old guard then failed every later push to master after running the whole
suite. detect now compares the pending release's merge commit with this run's
commit itself, gates tests and release on the result, and logs a warning naming
the run to go back to. release-pr stands down while any release is pending, so it
cannot open a second Release PR covering commits the first one already consumed.

detect also queries the autorelease: pending label directly. Listing closed PRs
and filtering client-side loses a release that has slipped past the first page,
which reads as nothing to release and passes.

version.rb is set to 12.1.0 so the tree matches the manifest and the tag, ci.yml
covers PRs into N.x hotfix branches, and the changelog gains 10.1.0, which is
where the HTTP-status fix actually shipped.
@mogita

mogita commented Sep 14, 2026

Copy link
Copy Markdown
Collaborator Author

Review addressed in d886781. All 3 must-fix and all 5 should-fix are fixed, replies on each thread.

The two items with no line to sit on:

GCP keep-alive suite. Ported, not retired. It is restored as integration-gcp-lb in run_tests.yml, so make test-integration-gcp-lb, spec/integration/gcp_lb_keepalive_spec.rb and the three ci environment values are all referenced again. Root cause was mine: I built the reusable workflow from a local ci.yml that was 13 commits stale, so the fifth job was never on screen.

Repo settings. Still not set, and I have write rather than admin here, so I cannot set them. Needed before this can be relied on, matching getstream-go: allow_merge_commit: false, allow_rebase_merge: false, squash_merge_commit_title: PR_TITLE, squash_merge_commit_message: BLANK. The README now documents them as required rather than done. Separately, master has no branch protection at all, which is not this PR's scope but worth someone deciding on.

From the "smaller things" list I took only the one with a correctness edge: v10.1.0 was missing from the backfill, and 0d973ba (the HTTP-status fix) belongs to it rather than to 11.0.0, so both entries moved. Left alone deliberately: bundler-cache, the 3.3.0/3.1.0 .ruby-version split, collapsing the integration jobs into a matrix (it would rename four required-check candidates), and publish's redundant permissions.

@mogita mogita left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Re-reviewed at d886781. All 3 Must Fix and all 5 Should Fix are fixed in the tree, checked against the files rather than the replies. The ready/pending split is a better shape than the terminal guard I sketched: it keeps the "never tag an untested tree" property, stops spending the suite per push, and leaves master green with a warning that names the commit to go back to.

Worth saying that the 10.1.0 CHANGELOG entry is a real correction, not just a backfill: git tag --contains 0d973ba puts the #76 fix first in v10.1.0, so it was misattributed to 11.0.0 before.

Two follow-ups from the fixes themselves, both inline. Neither blocks.

CI is red on this head from chat_misc_integration_spec.rb:264 (max_message_length expected 3000), which is an app-settings read-back on the app five SDK repos share, not this diff. A re-run should clear it, though the second comment below is about why feed went with it.

Comment thread .github/workflows/release.yml Outdated
# Query the label directly. Listing closed PRs and filtering client-side loses
# a release that has slipped past the first page, which reads as "nothing to
# release" and passes.
num="$(gh api "repos/${GITHUB_REPOSITORY}/issues" \

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[Should Fix] This picks one PR before checking which branch it is on, so a release on the other branch can silently swallow this one.

.[0] takes the first label-matched PR from the whole repo. The base.ref == env.BASE check happens afterwards, in the second call. When the one picked is on a different branch, sha comes back empty and the script falls into the [ -z "$sha" ] arm, which prints "No pending release on ${BASE}" and sets pending=false. The run then goes green having released nothing.

That needs two Release PRs pending at once, which is exactly the hotfix flow the README documents: branch N.x, cherry-pick, merge the Release PR that release-please opens against it, while master still has its own pending. The repo issues list sorts by created descending, so the newer hotfix PR wins .[0] and master's release is the one dropped. It stays dropped on every later push until the other clears.

Same shape as the paging bug this replaced: a candidate is chosen before the conditions that decide whether it is valid. The unmerged-but-labelled case lands in the same arm.

Fix: filter, then pick. Ask for the numbers rather than one number, and take the first whose base matches:

nums="$(gh api "repos/${GITHUB_REPOSITORY}/issues" \
  -X GET -f state=closed -f labels='autorelease: pending' -f per_page=20 \
  --jq '.[] | select(.pull_request != null) | .number')"

sha=""
for n in $nums; do
  sha="$(gh api "repos/${GITHUB_REPOSITORY}/pulls/${n}" \
    --jq 'select(.merged_at != null and .base.ref == env.BASE) | .merge_commit_sha // empty')"
  if [ -n "$sha" ]; then num="$n"; break; fi
done

num is used in both the warning and the success line, so it has to be set in the loop too.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in the new head. Took your loop shape: ask for the numbers, try each against base.ref, and set both num and sha on the first match so the warning and the success line still name the right PR.

You are right that it is the same shape as the paging bug, and I reintroduced it while fixing that one. The --jq producing a candidate and the validity check living in a later call is the pattern to avoid, not the specific .[0].

# Runs after chat rather than beside it: both blank and restore the app-global
# file_upload_config, so in parallel one suite's restore lands mid-assertion in
# the other.
needs: integration-chat

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

[Consider] needs: does not only delay feed, it skips feed whenever chat fails.

A job whose if: carries no status check function gets an implicit success() on its needs, so a red chat means feed never runs at all. That happened on this head: chat failed at chat_misc_integration_spec.rb:264 and feed reports skipped. So a chat flake now costs the feed result too, and re-running means re-running both. On the release path the effect is the same as before, since release was already skipped by a red tests, but you get less information about why.

Serialising was the right call and the trade you named in the reply is the real one. This is just the part of it that is probably not intended:

    needs: integration-chat
    if: '!cancelled()'

Feed still starts only after chat is done, so the file_upload_config windows still cannot overlap, but a chat failure no longer hides whether feed passes.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in the new head: if: '!cancelled()' alongside the needs:.

Not intended, correct. I wanted ordering and took the skip with it without noticing, and this head demonstrated it: chat red at chat_misc_integration_spec.rb:264, feed skipped. The file_upload_config windows still cannot overlap, because feed still starts only once chat is done.

For the record on that failure: the re-run of d886781 went green, so it was the propagation flake, not the diff. The test writes max_message_length: 3000 and polls 10 times at 0.5s for the read to catch up, which is a thin budget for a value that the spec itself documents as eventually consistent.

detect took the first label-matched PR and only then checked its base branch, so
with two releases pending at once, which is the documented N.x hotfix flow, the
newer one wins the pick and this branch's release falls into the no-release arm
on every run until the other clears. Filter first, then pick.

integration-feed inherited an implicit success() from its needs, so a red chat
skipped it outright rather than only ordering it. !cancelled() keeps the ordering
that the shared file_upload_config requires without losing the feed result.
@mogita
mogita merged commit 52ea46a into master Sep 14, 2026
9 checks passed
@mogita
mogita deleted the chore/release-please branch September 14, 2026 13:53
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