Skip to content

fix(cicd): release notes never reach Claude — E2BIG on the prompt, then denied tool permissions - #37361

Merged
sfreudenthaler merged 2 commits into
mainfrom
issue-37201-release-notes-payload
Sep 3, 2026
Merged

fix(cicd): release notes never reach Claude — E2BIG on the prompt, then denied tool permissions#37361
sfreudenthaler merged 2 commits into
mainfrom
issue-37201-release-notes-payload

Conversation

@sfreudenthaler

@sfreudenthaler sfreudenthaler commented Sep 3, 2026

Copy link
Copy Markdown
Member

Closes: #37201

The problem

Release notes generation has been failing for every release since v26.08.28-01. Four releases shipped with empty descriptions and a green pipeline:

Release Notes job Release body
v26.09.02-01 0 chars
v26.08.31-02 0 chars
v26.08.31-01 0 chars
v26.08.28-01 0 chars

#37213 correctly replaced the (#N) squash-subject regex with the commits→PRs API. That made PR resolution complete for the first time — and complete is roughly twice as big. The payload crossed a limit nobody was watching.

Three defects, none of them AI-side

                          253 KB assembled prompt
                                    │
   ┌────────────────────────────────┼────────────────────────────────┐
   │                                │                                │
[1] GITHUB_ENV              [2] claude_args              [3] gather step
 MAX_ARG_STRLEN =            no --allowedTools            `bash -e` aborts
 131072 bytes/entry          → Read denied                before `cat stderr`
   │                                │                                │
   ▼                                ▼                                ▼
 E2BIG in every            7 turns, 6 permission        failures surface as
 subsequent step           denials, exits "success"     a bare "exit code 1"
                           having written nothing

1 — E2BIG was a Linux limit, not a context limit. The prompt was passed through GITHUB_ENV. Linux caps a single env var or argv entry at MAX_ARG_STRLEN (128 KiB); a full release's prompt is 253 KB. Before #37213 the regex silently dropped about half the PRs, which kept it under the ceiling by accident.

2 — The workaround dropped the tool allowlist. Switching to "read the prompt from a file" without restoring --allowedTools meant Read was denied. From run 33705418470: num_turns: 7, permission_denials_count: 6, is_error: false. It wasn't running out of turns — it was being denied and giving up politely, then the next step failed on a missing file.

3 — The real error was unreachable. bash -e kills the gather step the instant node exits non-zero, so the cat /tmp/gather-stderr.log two lines down never ran.

The fix

  • Prompt goes to a file in the workspace, never through GITHUB_ENV or argv.
  • --allowedTools Read,Write restored, --max-turns 20. (Turns were never the constraint — the failing run used 7 and cost $0.22.)
  • Gather stderr is trapped and re-emitted as ::error:: before the step exits.
  • PR bodies cap at 1 KB. They were ~90% of the payload and the changelog only needs the lede.

Full release prompt: 253 KB → 71 KB, verified end-to-end against v26.09.02-01.

Keeping allow_failure: true, adding a smoke alarm

An empty release description should never block a software release, so the flag stays. But it's also why this went unnoticed for four releases — so on failure the phase now posts to #dot-releases, matching the pattern changelog-site-publish already uses. Success posts nothing. The notice is opt-in via SLACK_BOT_TOKEN, so the backfill workflow (where an operator is already staring at a red run) stays quiet.

Testing

  • 36 unit tests pass.
  • gather-release-data run end-to-end against v26.09.02-01 (485 commits → 51 PRs) and v26.08.28-01 (228 commits → 29 PRs); both clean, no 404s.
  • Assembled prompt measured at 71,128 bytes vs the 131,072-byte ceiling.
  • Backfill verified against v26.08.28-01 on this branch before merge.

Not in this PR

Moving commit→PR resolution to GraphQL would collapse 485 REST calls into one query. That's a real win for speed and rate-limit headroom, but it fixes neither failure here, so it belongs in its own issue.

🤖 Generated with Claude Code

This PR fixes: #37201

sfreudenthaler and others added 2 commits September 2, 2026 23:50
Three defects kept the release-notes phase from ever writing notes once
PR resolution started returning the *complete* PR set:

1. The assembled prompt (253 KB for a full release) was passed through
   GITHUB_ENV. Linux caps a single env var at MAX_ARG_STRLEN (128 KiB),
   so every later step died with E2BIG. Hand Claude a file instead.
2. claude_args carried no Read permission for that file, so all tool
   calls were denied and Claude exited "successfully" having written
   nothing (permission_denials_count: 6).
3. `bash -e` aborted the gather step before it could cat the script's
   stderr, so failures showed up as a bare "exit code 1".

Also cap PR bodies at 1 KB — they were ~90% of the payload and the
changelog only needs the lede. Full release prompt: 253 KB -> 71 KB.

Closes: #37201

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
allow_failure: true is correct — an empty release description should
never block a software release — but it also meant four releases shipped
with empty bodies and a green pipeline. Post to #dot-releases on failure,
matching the changelog-site-publish phase's pattern.

The notice is opt-in via SLACK_BOT_TOKEN, so the backfill workflow (where
an operator is already watching a red run) stays quiet.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@claude

claude Bot commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

Claude finished @sfreudenthaler's task in 3m 1s —— View job


Code Review

I traced all four files and the surrounding job (checkout, working-directories, the notify-slack action contract, and JSON serialization). The three-defect diagnosis is accurate and the fixes are sound:

  • E2BIG fix — prompt now lands in release-prompt.md at the workspace root (not GITHUB_ENV/argv), and every consumer (Assemble prompt, the Claude step's ./release-prompt.md, Update release description's release-notes.md) resolves against the repo root, so the paths are consistent. ✅
  • Tool allowlist--allowedTools Read,Write restored; Read is genuinely required for the file-based prompt. ✅
  • Lost errorif ! node …; then cat stderr; exit 1; fi correctly sidesteps set -e (commands in an if condition don't trip it), and set -- … / "$@" handles the empty-PREVIOUS_TAG case safely. ✅
  • extractLinkedIssues(data.body) runs on the full body before the slice, so the 1 KB cap doesn't drop linked issues. ✅
  • Slack wiring matches the notify-slack action inputs (channel-id/payload/slack-bot-token; json defaults to 'false'), HAVE_SLACK_TOKEN keeps the backfill quiet, and job-level continue-on-error doesn't interfere with step-level failure(). ✅

New Issues

  • 🟡 Medium: .github/workflows/cicd_comp_ai-release-notes-phase.yml:189 — the failure() guard fires on any prior step failing (Validate release exists / Setup Node / npm ci / Build TypeScript / Gather), but the payload hardcodes "the GitHub release is published but its description is empty." For a pre-gather failure (e.g. the Validate release exists step exiting 1 because the tag isn't found) that statement is inaccurate. In the normal pipeline the release is already published by the time this phase runs, so this is an edge case — but it's an alert, and a misleading alert costs operator time. Consider softening to something like "release-notes generation failed for <tag> — check the run". Fix this →

  • 🟡 Medium: .github/scripts/gather-release-data/src/github.ts:260 — the body cap (50_000BODY_CHAR_LIMIT = 1 KB) is the crux of the payload fix and the constant was exported (signalling test intent), but no test in github.test.ts exercises it. A one-line test asserting a >1 KB body is truncated to 1000 chars while linkedIssues is still extracted from the full body would lock in the behavior and guard against a future bump silently blowing the prompt budget again.

Notes (non-blocking)

  • The new prompt tells Claude to "page through it until you have read all of it." You've verified this end-to-end against the largest release (51 PRs → 71 KB), so it works today. Worth keeping in mind that the ~128 KB argv ceiling now translates to roughly ~120 PRs of headroom given the 1 KB body cap — comfortable, but the same class of silent limit if PR counts ever grow dramatically.

No blocking issues — the 🟡 items are worth addressing but don't gate merge.
· issue-37201-release-notes-payload

@sfreudenthaler

Copy link
Copy Markdown
Member Author

Backfill verified — all four empty releases now have notes

Ran the backfill workflow off this branch, oldest first (auto-detected from-tag skips undocumented releases, so the ranges only chain correctly in order):

Release Previous tag resolved Commits PRs Prompt bytes Notes
v26.08.28-01 v26.08.24-01 228 29 71,128 6,775 chars ✅
v26.08.31-01 v26.08.28-01 118 8 46,673 2,810 chars ✅
v26.08.31-02 v26.08.31-01 90 7 46,869 2,552 chars ✅
v26.09.02-01 v26.08.31-02 52 7 48,276 2,187 chars ✅

Every range chains to the one backfilled before it — no gaps, no double-counted PRs. Zero Could not fetch PR #N warnings, versus 16 on the last pre-fix release run (33659151567), where issue numbers were being fed to pulls.get.

Largest prompt is 71 KB against the 131,072-byte MAX_ARG_STRLEN ceiling — and it no longer passes through argv or GITHUB_ENV at all, so that ceiling is off the critical path regardless.

The 1 KB body cap did not cost anything in output quality: v26.08.28-01 came back with correctly categorized sections, issue links, and a deprecation notice for the removed dotcms SDK.

Runs: v26.08.28-01 · v26.08.31-01 · v26.08.31-02 · v26.09.02-01

Comment thread .github/workflows/cicd_6-release.yml

@sfreudenthaler sfreudenthaler left a comment

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

backfill worked, ready for human review

@sfreudenthaler
sfreudenthaler added this pull request to the merge queue Sep 3, 2026
Merged via the queue into main with commit aeb2518 Sep 3, 2026
83 checks passed
@sfreudenthaler
sfreudenthaler deleted the issue-37201-release-notes-payload branch September 3, 2026 17:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

AI: Safe To Rollback Area : CI/CD PR changes GitHub Actions/workflows Area : Documentation PR changes documentation files

Projects

Status: No status

Development

Successfully merging this pull request may close these issues.

Release notes & QA status: replace (#N) squash-subject regex with commits→PRs API

2 participants