googleapis / googleapis/release-please-action

Single transient GraphQL failure during pass-2 strands an already-cut release

Open
#1,203 0 comments 2 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
TypeScript
Stars
2.5k
Forks
328
PR merge metrics
No merged PRs in 30d

Description

Environment details
  • Programming language: TypeScript / Node (project uses Bun, action runs on Node)
  • OS: GitHub Runner ubuntu-latest
  • Language runtime version: Node 20 (action's runtime)
  • Package version: googleapis/release-please-action@v4. Also reproducible on @v5. Underlying release-please 17.3.0 and 17.6.0.
Steps to reproduce
  1. Have a workflow that runs release-please-action on push to main, with a downstream deploy job gated on needs.release-please.outputs['<path>--release_created'] == 'true'.
  2. Merge a release-please PR (e.g. chore(main): release X.Y.Z).
  3. While the workflow runs, encounter a transient GraphQL failure during pass-2 (createPullRequests's commit.history query). The easiest natural reproduction is running during a GitHub Disruption with some GitHub services incident. We caught this on 2026-04-27 16:48Z to 19:02Z. Symptom in logs:
    Creating 1 releases for pull #41                    (pass 1 succeeded, tag v1.10.1 cut)
    Fetching merge commits on branch main with cursor: undefined  (pass 2 starts)
    ##[error]release-please failed: Request failed due to following response errors:
     - Something went wrong while executing your query on 2026-04-27T18:46:36Z.
       Please include `2401:3F4A02:1120138:44A0780:69EFAF0C` when reporting...
    
  4. Observe: the GitHub release and tag for X.Y.Z are created (pass-1 completed), but the release-please job's conclusion is failure, the downstream deploy job is skipped, and re-running the workflow does not recover. The action no longer re-emits release_created=true on subsequent runs because the release already exists.
What's wrong

release-please-action's main() (verified byte-identical in v4 and v5) runs two passes in one step:

  1. manifest.createReleases() cuts the release and writes <path>--release_created=true via core.setOutput.
  2. manifest.createPullRequests() scans commit.history to draft the next release PR. Independent of the release just cut.

If pass-2 throws, core.setFailed marks the entire step failed. The job's release_created output is no longer trusted by needs: consumers, even though pass-1 wrote it before pass-2 ran. Once a tag exists, no future workflow run will re-emit it. So the deploy gate becomes permanently unreachable for that release.

Same symptom independently reported in #1202 today (Rust project, v5), confirming this is neither language- nor repo-specific. Recurring reports: #867 (2023), #976, #977 (2024), #1202 (2026). Each prior issue has been closed without addressing the coupling.

Three independent root issues

(A) Retry only catches HTTP 502. From release-please/src/github.ts graphqlRequest:

if ((err as GitHubAPIError).status !== 502) {
  throw err;
}

The 2026-04-27 incident returned a GraphqlResponseError (HTTP 200, errors: [{message: "Something went wrong..."}] in body). No 502 means no retry. Retry should also cover 503, 504, and GraphqlResponseError whose messages match /something went wrong|server error/i. Identical in 17.3.0 and 17.6.0.

(B) Pass-1 and pass-2 share a single failure surface. Wrapping pass-2 in its own try/catch and surfacing pass-2 errors as core.warning (rather than core.setFailed) would let pass-1's outputs survive a transient pass-2 failure.

(C) Pass-2 is unconditional even when pointless. When HEAD is the release commit pass-1 just cut, pass-2 has zero new commits to scan and zero PRs to open. A short-circuit (if (headSha === justCutReleaseSha) return;) would skip the API call entirely for the most common trigger and eliminate this failure mode there.

Workaround for affected users

continue-on-error: true on the action step preserves pass-1's outputs even if pass-2 throws. Pass-1 outputs are written via core.setOutput before pass-2 begins (verified in src/index.ts main()), so downstream gates on <path>--release_created remain accurate.

Confirmed v5 does not fix this: src/index.ts main() is byte-identical between v4 and v5. The v5.0.0 release notes are limited to a Node 24 bump and a release-please lib bump from 17.3.0 to 17.6.0, neither of which changes retry coverage or pass coupling.

Asks (in priority order)
  1. Decouple pass-2 failures from pass-1 outputs at the action level (issue B). Easiest, fully within this repo.
  2. Broaden retry coverage in release-please to include 503, 504, and matching GraphqlResponseError (issue A).
  3. Short-circuit pass-2 when no work is possible (issue C).

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in src/index.ts at main() to trace how createReleases(), createPullRequests(), setOutput, and setFailed interact during the two passes. Then inspect release-please/src/github.ts and graphqlRequest for the retry behavior, and reproduce the workflow with a transient GraphQL failure. Done means the requested pass coupling, retry coverage, or pass-2 short-circuit is addressed without losing release outputs.

Written by the indexing model from the issue text.

Assessment

Tech stack
github-actions, typescript
Domain
ci-cd, devops, release
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Quiet
Clarity
Clearly specified
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.