Skip to content

next-release: commit-range races the API-pushed bump commit, silently emptying the release-PR changelog and crashing the labels step #262

Closed

Description

@Shinrai

Summary

On a next → master release-PR refresh where a fresh chore: bump version commit is pushed, get-pr-commit-range can compute a commit range whose head sha was never fetched into the job's local git checkout — because the bump commit that just became the PR's head was created entirely via the GitHub API, not via local git. Every downstream step that runs git log against that range then either silently produces an empty result (the changelog generator, which patches the PR with a "successful" but content-free body) or crashes outright (the raw commit-type-detection shell step), failing the whole refresh job.

This is live in production, not theoretical — reproduced on CLDMV/droidsock's persistent release PR.

Observed symptom

droidsock PR #19 ("release: v1.2.0 - add device discovery") currently has an essentially empty body — every changelog section reads its placeholder text ("No new features", "No bug fixes", "No other changes") — despite the release bundling real feat:/fix:/ci: commits from three merged PRs (#17, #18, #20). All CI on the release PR is green, so nothing visibly flags that the body is wrong.

Root cause

Three files in CLDMV/.github (all pinned @v4, from this workflow chain: workflow-next-release.yml → update-release-pr/action.yml):

1. github/api/commit/action.mjs ("Create signed commit" step) creates the version-bump commit and moves the branch ref entirely via the GitHub REST API — POST /git/blobs, POST /git/trees, POST /git/commits, PATCH /git/refs/heads/{branch}. It never runs git push, and nothing afterward runs git fetch. The job's local git checkout has no idea this commit exists.

2. github/steps/get-pr-commit-range/action.mjs, which runs later in the same job, re-resolves the range with a fresh live API call rather than anything derived from the local checkout:

const pr = await api("GET", `/pulls/${prNumber}`, null, { token, owner, repo });
const commitRange = `${pr.base.sha}..${pr.head.sha}`;

Because the PR's head branch is next (this is the persistent release PR), once the ref-update PATCH from step 1 has propagated, this GET returns the brand-new bump commit as pr.head.sha — a sha that was never fetched into this job's local .git.

3. update-release-pr/action.yml's "Compute commit types in PR range" step consumes that range with a raw, unguarded git command:

run: |
    types=$(git log --no-merges --format='%s' "$RANGE" \
        | { grep -vE '^(release|chore: bump version)' || true; } \
        ...

When $RANGE's head sha is unresolvable locally, this fails hard:

fatal: Invalid revision range 9e2c95c546d5fb1c5ec3ec614fe6fba797d4d71b..d04fd8c77fe982bb27b633823dec0b873d1fcdfe
##[error]Process completed with exit code 128.

— confirmed 9e2c95c is a real, valid, reachable commit (release: v1.1.1 … on master) in a normal full clone; the range itself is legitimate, only this job's particular checkout never has the newer endpoint.

The same broken range is also passed to generate-comprehensive-changelog (the "Generate updated changelog" step, which runs before the failing step). That action apparently tolerates the unresolvable range without erroring, producing a changelog with every section emptied — and the workflow still logs the update as a success:

✅ PR #19 has been updated with title 'release: v1.2.0 - add device discovery (subnet TCP sweep + mDNS)' and latest changelog
✅ Preserved 1 sticky block(s) in the PR body

This is the more damaging half of the bug: the hard crash at least fails the job loudly; the empty changelog ships silently as a "successful" update.

Why it's intermittent

This is a check-then-act race, not a deterministic failure: whether GET /pulls/{n} (step 2) reflects the just-updated ref from the PATCH (step 1) depends on propagation timing at the moment the job happens to query it. On CLDMV/droidsock, run #33703465364 (PR #17's merge, which also pushed a bump commit) succeeded; run #33755241538 (PR #18's merge, minutes later) hit the race and both silently emptied the changelog and then failed on the labels step. Any repo on the v4 flow that bumps a fresh version on a refresh (i.e. bump-already-applied != 'true') is exposed to this on every such refresh — it's a matter of timing, not a rare edge case.

Evidence

Suggested fix directions (not prescriptive)

  • Have get-pr-commit-range resolve head.sha from something guaranteed to be fetched locally (e.g. git rev-parse HEAD after step 1, or an explicit git fetch origin <head-ref> immediately after the bump-commit ref update) instead of a fresh live API GET that can race the API-only push.
  • At minimum, add error handling around the raw git log ... "$RANGE" in "Compute commit types in PR range" so an unresolvable range fails loudly with a clear message instead of a bare fatal:/exit 128 — and have generate-comprehensive-changelog do the same rather than silently rendering an empty-but-"successful" changelog.
  • Consider whether step 1's API-only commit creation should end with a local git fetch/git reset --hard <new-sha> so every subsequent step in the same job operates on a checkout that actually reflects what was just pushed.

Activity

  1. added
    type: bugSomething is broken or not behaving as expected
    priority: highNeeds attention soon — not blocking, but don't let it sit
    on Sep 3, 2026
  2. added theissue type on Sep 3, 2026
  3. cldmv-bot commented on Sep 6, 2026

    @cldmv-bot
    Contributor

    Closed in v4.26.0 — resolved by #265.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    priority: highNeeds attention soon — not blocking, but don't let it sittype: bugSomething is broken or not behaving as expected

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions