Skip to content

feat(ci): add the upstream-apply-changelog release lane - #12508

Merged
kyle-sexton merged 46 commits into
mainfrom
feat/release-intake-workflow
Nov 3, 2026
Merged

kyle-sexton merged 46 commits into
mainfrom
feat/release-intake-workflow

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Nov 2, 2026 •

Copy link
Copy Markdown
Member

Refs: #7407

Summary

The #7407 lane (steps 3 onward; what stays open is under Remaining for #7407): .github/workflows/upstream-apply-changelog.yml, the lane that turns each Claude Code release into changelog-apply pull requests without a person. On schedule, or a dispatch by a listed human, it asks the lanes token broker for an App token keyed on the release, runs /harness-ops:changelog apply --ci (from #12444) in a read-only model job, scans everything the model produced, then pushes each planned branch as one signed lanes App commit, opens its pull request, and files the release's handoffs as one issue.

Owner decisions this builds on, verbatim:

  • feat(lanes): Claude Code release intake lane #7407 (issuecomment-6579430471, kyle-sexton): "the lanes token broker may accept schedule and workflow_dispatch from release-intake.yml, keyed on the release. Hold released."
  • Q2, lane name: "upstream-apply-changelog" (renamed from release-intake.yml; broker caller renamed in melodic-software/azure-iac#107, deployed).
  • Q3, scan-carrier key: "Second holder (Recommended)": the scan job may name the pr-pipeline-scan-carrier environment (main only, no id-token, no write token, read-only cache).

Fix

  • Jobs: gate (trigger and kill switch, one concurrency group, cancel-in-progress false, per-day cap) -> model (contents: read, sealed transcript, first attempt only) -> scan (plan, each branch, and the decoded pull request and issue text) -> push-branches (contents write) -> publish (pull-requests and issues write). The broker refuses contents and pull_requests write in one mint, so mutate and publish are separate jobs, one mint per release.
  • Model output never reaches a write token unscanned: the scan job rebuilds each planned head as one synthetic commit (the head's tree on the base, a fixed identity, date and message), so the model's history, merges and intermediate blobs are never bundled; push and publish pin the bundle by artifact id and SHA-256 and the commit by id, parent and scanned tree. Binary files, links, gitlinks and paths outside the plugin are refused.
  • Every job that runs after the model sets GIT_CONFIG_GLOBAL=/dev/null and GIT_CONFIG_NOSYSTEM.
  • One handoff issue per release, not one per handoff; overflow goes to comments on it.
  • The changelog is text authored outside the org, so the scan job (no write token) stops a release whose branch changes any hold path (the generated hold-paths block in pr-review-security-hosted.yml, matched with merge-holds.mjs matchesHoldPath), as stop_reason: hold-path; push checks again before minting. A plan claiming that stop itself is refused.
  • The lane's GitHub calls go through the rate guard (rateLimitLatch.guard).
  • A range-not-computed stop files nothing and leaves the release undone, so the next scheduled run retries it; other stops file and mark the release done.
  • Artifact retention rows for the three uploads; API budget logging on the model job; floor pins and CARRY_REFUSES take the new workflow and changelog-status.sh.

Verification

  • Three independent security review rounds (R85); round 3 verdict "ship", no CRITICAL or HIGH. Its LOW and informational findings are deferred to the hardening phase (owner direction s10), below.
  • upstream-apply-changelog.test.mjs, callers.test.mjs, request-lane-token.test.mjs, live-config.test.mjs, log-api-budget.test.mjs; scripts/check-affected.sh --base origin/main.

Residuals, deferred to the hardening phase:

  • The model runs as the runner user, so its writable workspace and runner temp files are a class only a separate OS user closes.
  • Hook-code review finding 8.
  • The branch diff scan reads diffs.txt as plain text, so a token split across two added lines can pass (LOW); fix by running scan-reuse.mjs's scanBranch per head.
  • The done marker keeps plan.json (scanned model output) for 90 days; uploading only the release key would keep no model text.
  • Owner Q3 record: feat(lanes): Claude Code release intake lane #7407 (comment)
  • A sandbox acceptance run in melodic-software/pr-pipeline-sandbox is still owed; the first real dispatch is the live check.

Remaining for #7407

Done in this PR, against the #7407 requirements the verifier named:

  • Schedule. The lane polls every ten minutes (5-59/10 * * * *). A tick with no new release makes at most ten GITHUB_TOKEN REST calls (one done-marker read per candidate), at most 60 an hour of the token's 1,000; a tick that finds a release adds one call for the cap. Checkout runs over git transport and setup-pinned-node makes no API call (its action.yml header).
  • Per-day cap. limits.upstream-apply-changelog-per-day is 3, the most anthropics/claude-code releases published in one UTC day or any 24 hours from 2026-05-02 to 2026-11-01 (158 releases, GitHub releases API, read 2026-11-02). Dispatches and retries count against it.
  • Burst coverage. Each run takes one release. A scheduled gate re-reads the unread list each tick and takes the newest release among the ten newest unread ones that has no done marker, so every release of a burst is taken on later ticks. Queued runs do not carry the burst: GitHub keeps one pending run per concurrency group, so a pending tick can be replaced by a newer one, and the newer tick takes the same release. Only a pending workflow_dispatch can be lost that way, when a later run replaces it; dispatch it again.

Left for later, or by design:

  • Read marker (by design). --ci never writes the ledger (plugins/harness-ops/skills/changelog/context/ci-mode.md, marker.advance is always false). The marker moves in the ledger pull request once every row is final. The lane uses the upstream-apply-changelog-done-<release> artifact instead, so this lane does not meet feat(lanes): Claude Code release intake lane #7407's "advances the read marker" acceptance line as written.
  • Releases outside the window. The scheduled gate checks only the ten newest unread releases, to keep the per-tick cost bounded while the marker stays put. Older unread releases wait for a person to move the read marker in docs/upstream/claude-code.md (today it is at 2.1.296, with 16 releases unread). The ten-release window also keeps the gate inside the done marker's 90-day retention at the observed release rate.
  • Version match. The model job runs run-claude-cli with its default cli-version: latest, not the analyzed release. The skill's version warning (plugins/harness-ops/skills/changelog/SKILL.md:45-47) fires only when the installed CLI is older than the newest release in the range, and latest is never older than a release the changelog already lists (on 2026-11-02 the status script read installed 2.1.317 against changelog latest 2.1.316). Pinning the exact release would run backlog releases on an older CLI than the plugins on main expect, fail the install if npm lags the changelog, and conflict with the lanes' no-pin decision (docs/conventions/pr-pipeline/README.md, Claude Code CLI releases, ci(pr-pipeline): re-run the lanes' shell-permission sweep on each Claude Code version bump #8595).
  • Fetch outages. With a ten-minute schedule, an outage of the changelog fetch from code.claude.com makes the gate throw on each tick until it recovers, so a person sees repeated red runs.
  • Sandbox acceptance run on melodic-software/pr-pipeline-sandbox (ADR 0053), as already listed.
  • Hardening residuals already listed: the plain-text diffs.txt scan (split-token, LOW) and the 90-day done marker keeping plan.json.

Related

🤖 Generated with Claude Code

https://claude.ai/code/session_01VcupojVgxFqBnrah9DwFLE

kyle-sexton and others added 30 commits November 2, 2026 12:35
apply --ci <release|range> decides every row on evidence with nobody to
answer: only a cited, verified correct row confined to one owner plugin
and not security-relevant is committed on a local branch per owner
plugin, and every other row becomes a handoff with a dedupe key. It asks
nothing, pushes nothing, opens no pull request, files nothing and never
moves the read marker. It writes ci-plan.json for the release-intake
workflow's push and publish jobs.

Two eval cases cover the capped-range stop and an adopt row handed off.

Refs: #7407

Co-Authored-By: Claude Opus 5.5 <[email protected]>
release-intake.yml runs `/harness-ops:changelog apply --ci <release>` in a
model job that holds only the API key and a read-only token, seals the plan
and branch bundle to the scan-carrier key, scans them, then pushes each
changelog/<plugin>/v<release> branch (mutate-branch) and opens its pull
request and files handoffs (publish-artifact) with lanes App tokens minted
through the broker, keyed on the release. Adds the lane, its two activities
and limits.release-intake-per-day to pr-pipeline.yaml, the lane's rules and
grant rows, and a `release` input to request-lane-token.

Refs: #7407

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Rename the workflow, scripts, lane, limit, artifacts and handoff marker to
upstream-apply-changelog (stage upstream, function apply-changelog), and add
the reserved vocabulary entry the standards change adds. The gate now reads
the unread releases from changelog-status.sh and skips a release only on its
done artifact, so a run after a failed publish opens the missing pull
request. lane-rules.json keeps its original formatting.

Refs: #7407

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The cap line carries a count suffix, so match its prefix; releases are
space-separated, oldest first.

Refs: #7407

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The schema's stage enum lists upstream, the scheduled producer that files
work into intake, and the lane's name carries that stage word.

Refs: #7407

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Add the workflow to the log-api-budget job table, the kill-switch-ok
caller list, the read-only cache list and the lane-funding catalog; give
live-config a grant test for both activities and request-lane-token
release cases (valid, malformed, with another id, check mode). Regenerate
the hold floor, which now gates the workflow. Document the release field
in the request-lane-token README and the broker body and refusal rows.

The workflow's kill-switch-ok is a YAML boolean, publish skips on any
push-branches skip-reason, scan and push-branches restore the cache
read-only, and a missing space in the scan job's outputs is fixed.

Refs: #7407

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The gate's trusted-actor check passed trusted-actors-path and
triggering-actor at their action defaults; it now passes only
denied-actor-ids, require-pr and require-kind. The step's comment says
that actor-only mode refuses a schedule event, so a re-run of a scheduled
run stops and the release is dispatched instead.

Refs: #7407

Co-Authored-By: Claude Opus 5.5 <[email protected]>
push-branches now recreates each planned branch through the Git Data API
(blobs, trees, commits with no author or committer, then the ref) with
apply-patch.mjs's createGitHubApi, so GitHub authors and signs every
commit as the lanes App, as work-resolve-issue's apply-patch does. It
refuses a merge commit, reads blobs as bytes, and refuses a replay whose
tree is not the planned head's, so what is pushed is what the scan job
scanned. The mutate-branch token's contents write covers every call.

A branch that already exists counts only when its tip holds the planned
tree. push reports each branch's head as a job output, and publish opens
a pull request only for a branch at that head.

Refs: #7407

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The model job stages upload-lane-transcript, the seal-lane-output and
scan-upload actions it loads, and lib/publish-gate.mjs into RUNNER_TEMP
before the root checkout replaces .base. After the leg it copies them back
to a fresh .lane-transcript and uploads the sealed transcript of the run
step's execution file under the job's id, always and without changing the
job's outcome. Nothing loads from ./.base after the root checkout.

The skill's memory directory is now its default .work/ (decisions.md
Persistence; plugin-artifact-protocol.md), which it resolves on its own
because this job removes every project instruction file; .work/ is
gitignored, so the plan stays out of the skill's commits. The seal step
reads the plan from there.

publish restores the Actions cache read-only, since it posts the model's
plan text, and the read-only cache test lists it.

Refs: #7407

Co-Authored-By: Claude Opus 5.5 <[email protected]>
shellcheck SC2016, which actionlint reports, read the backtick template's
${...} inside the single-quoted node -e script as a shell expansion that
never happens; plain string joins print the same line.

Refs: #7407

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…utable

The lane changes docs/conventions/pr-pipeline/, so it carries a minor
pr-pipeline fragment describing the upstream-apply-changelog lane, and
upstream-apply-changelog.mjs, which starts with #!, is 100755 in the index.

Refs: #7407

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…rier key

Owner decision (2026-11-02, in the Automation Lanes session): "Second
holder (Recommended)". The lane's scan job names the
pr-pipeline-scan-carrier environment so its open step gets the key.

The callers test pins the holder to that one job and one open step, with
contents: read alone, a read-only cache, and the main-only gate before
it. The scan also covers the branch commit messages, which push
replays as public commits.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The publish job filed every plan handoff as its own issue. It now files one
issue per release, titled "Claude Code v<release> changelog: <n> handoffs",
opening with a release marker and holding one checklist item per handoff,
each body indented under it with its key marker. Bodies over GitHub's issue
body limit are cut in proportion and a line says so.

An issue carrying the release marker, in any state, is never filed twice; an
open one missing keys gets the missing items appended in one update, and a
closed one is left alone. The update stays within the existing issues write
grant.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
When an open release issue lacks keys and appending them would pass
GitHub's issue body limit, publish now posts the missing items as one
comment on that issue instead of throwing, each with its key marker. Dedupe
reads the issue's comments for key markers too, a full page at a time up to
a fixed cap, and files nothing when a read fails or the cap is reached.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…orkflow

# Conflicts:
#	.github/workflows/pr-require-checks.yml
A partial or full re-run of upstream-apply-changelog skips the model job,
so a retry of the model is a new dispatch.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
fileReleaseHandoffs searches with author:app/melodic-automation-lanes,
takes an issue only when its user is the lanes App, and reads key markers
only from the App's comments, so an outsider's issue or comment carrying
the markers cannot suppress a handoff.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Before the credential scan, nothing prints plan or bundle text:
validatePlan and checkRawLog name the field and index, the plan parse
throws a fixed message, the bundle verifies quietly, and its list-heads
output is checked against the validated plan heads without being printed.
A plan naming branches with no bundle, or a head equal to the base, fails.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Push no longer replays the model's commits: each branch becomes one lanes
App commit on the base whose tree is the planned head's (still checked) and
whose message is the plan's scanned pull request title. The path check reads
the same net diff, so a merge commit cannot hide a change from it. The scan
job scans the net diff and the plan, and no longer logs commit messages,
which are no longer published.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The credential scanners read text, so the scan job and push both refuse a
branch whose net diff numstat marks any file binary, naming no path.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The scan job rebuilds the bundle from the refs/scan heads it fetched and
checked, and uploads that copy with the plan from a fresh directory, so
nothing else the model's bundle carried reaches push.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
After the plan's checks, the scan job writes every string publish can post
(each pull request title and body, the release issue title and body from
the same renderer publish uses, and the untruncated handoff items an append
or comment posts) decoded to a text file, and scans it before the upload.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…fact

The model job takes the sealer, its key and the transcript uploader from a
fresh checkout of the base made after the leg, at a cleared path, instead
of a RUNNER_TEMP stage the model could rewrite; only the plugins stay
staged. The scan job outputs the uploaded artifact's id and the SHA-256 of
plan.json and branches.bundle; push and publish download by artifact-ids
and check the hashes before the lane token is minted.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The scan job's binary check captured numstat before grep -q, so a SIGPIPE
under pipefail can no longer read as no match. Publish verifies the bundle
hash as push does, and refuses a bundle the scan job recorded no hash for.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
The rebuilt bundle push and publish download is an artifact and still
holds each commit's message and blobs, so the scan now reads the
per-commit log and refuses a binary file in any commit. The seal step's
git calls ignore global and system config and fsmonitor the model could
have set during its leg.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
…history

The scan job rebuilt the bundle from the model's heads, so merge commits,
commit headers and blobs a merge added then removed reached the artifact
unscanned: git log -p shows no merge diffs and never prints author or
committer fields.

The scan job now commits each planned head's tree on the base with a fixed
identity, date and message (git commit-tree, deterministic) and bundles only
those commits. What it scans is the net diff, the plan and the publish text;
the per-commit log -p and numstat are gone, and the binary refusal reads the
net numstat. The scan job outputs each branch's synthetic commit and tree;
push fetches that commit, checks it is one commit on the base holding the
recorded tree, and builds its published commit from that tree. The plan's
head_sha is only the scan's input.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VcupojVgxFqBnrah9DwFLE
A ~/.gitconfig the model writes during its leg reached the checkout after
it, which copies the global config into its temporary HOME, and every later
git call; only the seal step ignored it. GIT_CONFIG_GLOBAL=/dev/null and
GIT_CONFIG_NOSYSTEM=1 are now job-level env on the model, scan,
push-branches and publish jobs, and the seal step's copies are gone.

The leg needs no global config: it commits with the repository-local
identity the prepare step sets, and the pinned run-claude-cli and
apply-claude-managed-settings actions read none. actions/checkout's
safe.directory write under --global is caught and logged, not fatal.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VcupojVgxFqBnrah9DwFLE
scan-upload reads diffs.txt as plain text, so a token split across
added lines joined with its `+` prefixes and never matched. The scan
job now runs the lane-branch scan (scan-reuse.mjs) on every planned
head: the diff read as a diff, and each changed file's bytes. The net
diff ignores external diff and textconv drivers, and checkouts under
GIT_CONFIG_GLOBAL=/dev/null skip the safe.directory write that cannot
land there.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VcupojVgxFqBnrah9DwFLE
kyle-sexton and others added 3 commits November 2, 2026 17:11
…ase done

- A stopped plan keeps and files its handoffs, publishes no branch, and
  fails publish so the done marker is not uploaded.
- An existing branch with another tree and no pull request fails push
  instead of being skipped.
- The start record has one fixed name and the per-day cap lists it by
  name, so other workflows' artifacts no longer push it off the page.
- Create .lane before copying the staged plugins into it.
- Restore set-safe-directory: false on the six checkouts whose job sets
  GIT_CONFIG_GLOBAL=/dev/null: checkout's --global write cannot lock it.
- Run the suite through its own Node package in the test-node lane.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VcupojVgxFqBnrah9DwFLE

@github-actions github-actions Bot left a comment

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.

claude-security-review: 1 inline finding(s) on 00c84c499cebd1b8f6a9d6bdf21da27a34decb31; the summary is in this lane's pull request comment.

claude-lane: claude-security-review

kyle-sexton and others added 3 commits November 2, 2026 17:51
…filed

A stopped plan is the same on every run for its release, so failing
publish on purpose only re-ran the model on that release up to the
per-day cap.

- validatePlan requires a stopped plan's stop_reason to be cap-exceeded
  or range-not-computed and returns it.
- The release issue opens with a line naming the stop_reason, and a
  stopped plan files it even with no handoffs; the scan text matches.
- Publish no longer throws on a stopped plan: once the issue holds
  every key it succeeds and the done marker uploads. A closed issue
  missing a key, or a failed write, still fails publish so the release
  is retried.
- ci-mode.md, the workflow comments and the harness-ops fragment say so.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VcupojVgxFqBnrah9DwFLE

@github-actions github-actions Bot left a comment

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.

claude-review: 1 inline finding(s) on 47d614542fa79fb512c54f290639c9593e814efa; the summary is in this lane's pull request comment.

claude-lane: claude-review

kyle-sexton and others added 2 commits November 2, 2026 18:51
…x the branch's CI failures

A range-not-computed stop can pass on a later run (the changelog fetch
failed, or the page lags the tag), so publish now files nothing for it and
outputs done=false; only the done output uploads the done marker. A
cap-exceeded stop still files its issue and marks the release done.

CI fixes for the new gated workflow:
- artifact-retention.json: rows for the started, plan and done artifacts.
- model job: API_BUDGET_TOKEN on resolve-config and a job-end
  log-api-budget step from the trusted checkout.
- CARRY_REFUSES (both copies) holds the changelog skill's scripts, which
  the workflow runs.
- Floor pins: 126 classes, 73 person-cleared globs, 54 gate, new digests.
- read-lane-broker-check.test.sh: the check-mode refusal names release,
  with a case for it.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@kyle-sexton kyle-sexton added the do-not-merge Hard merge gate: do not merge while applied. label Nov 3, 2026
kyle-sexton and others added 3 commits November 2, 2026 20:04
…hold path

The lane's model acts on the Claude Code changelog, text written outside the
org, and the path check allowed any regular file under plugins/<plugin>/, so
injected text could steer it to edit the security review's own skill or the
merge risk scorer.

The scan job (no write token, after the model) now runs the script's scan
subcommand once it fetched each planned head: each net diff must stay in its
plugin and change no path in the security review's generated hold-paths list
(the hold floor, the trusted-actor list and the security review's own files,
written by scripts/sync-hold-floor.py). A hold path rewrites the plan to
stopped with stop_reason hold-path and no branch, so publish files the
release issue naming it and marks the release done; push refuses a hold path
again before minting anything.

The lane script's API calls now go through the rate guard, which the
rate-guard test requires of every lane script that calls fetch.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VcupojVgxFqBnrah9DwFLE
…h stop

Only the scan job's path check sets stop_reason hold-path; a model plan that
claims it would file an issue saying a branch changed a hold path. The scan
subcommand now refuses it. The scan test's child process runs git with the
isolated config the scan job uses.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01VcupojVgxFqBnrah9DwFLE
@kyle-sexton kyle-sexton removed the do-not-merge Hard merge gate: do not merge while applied. label Nov 3, 2026
@kyle-sexton
kyle-sexton added this pull request to the merge queue Nov 3, 2026
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

The pr-verify verifier failed head b4bda83f344716c2adc265f498ece479a3bbc448. What it wrote, as data:

What it wrote (47 lines)
Summary: Fail. PR #12508 (Refs #7407) adds the upstream-apply-changelog lane: gate, a read-only
  model job, a scan job, then push-branches and publish with lanes App tokens. The security design
  is careful, and the owner signed off in #7407 on both the broker widening and the second
  scan-carrier holder. All CI jobs on the head passed. But the lane's own triggers and run setup
  miss stated requirements of #7407, and the PR body does not list these as later work. The issue
  asks for a schedule that polls often enough to start within minutes of a release; this one runs
  once a day. It asks for a per-day cap sized to the most releases shipped in one day; this one is
  fixed at 2 by judgment. It asks that the run install the Claude Code version it analyzes; nothing
  does. It also expects a run to advance the read marker; this lane never moves it and the body
  doesn't say so. The only remaining work the body names is the sandbox acceptance run and the
  hardening residuals.
- Fix: .github/workflows/upstream-apply-changelog.yml:35: the schedule `53 9 * * *` runs once a day.
  #7407 asks for polling often enough to start within minutes of a release. Poll every few minutes,
  which the gate's done marker and concurrency group already make safe, or name the slower schedule
  as later work in the PR body.
- Fix: docs/conventions/pr-pipeline.yaml (limits.upstream-apply-changelog-per-day: 2): #7407 asks
  for a per-day cap sized to the most Claude Code releases shipped in one day. The value 2 is
  labeled 'judgment: one scheduled run plus one dispatch a day', and each run takes only one
  release, so a day with several releases falls behind. Size the cap to the observed daily maximum
  and note the source, or name this as later work.
- Fix: .github/workflows/upstream-apply-changelog.yml:296 (model job, run-claude-cli step): #7407
  requires the run to install the Claude Code version it analyzes, because the skill warns when the
  installed version is older than the range (SKILL.md:45-47). No step installs or pins the `release`
  version before the leg. Add that step, or name it as later work in the PR body.
- Fix: .github/workflows/upstream-apply-changelog.yml:59: the header says --ci never moves the read
  marker and uses a done artifact instead. #7407's acceptance expects a dispatched run to advance
  the read marker, and the PR body doesn't list this as later work. Either advance the marker or
  name it in the body as remaining.
- Fix: PR body: it calls this 'Step 3 of #7407' but delivers the whole lane, triggers, concurrency
  and cap included. For a `Refs:` delivery, list each #7407 requirement left for later (the items
  above, plus the sandbox dry-run it already names).
- Note: .github/workflows/upstream-apply-changelog.yml: the 'Two releases in quick succession
  produce two queued runs' acceptance item depends on GitHub concurrency, which keeps only one
  pending run per group, so a third queued run would cancel the second pending one. The body should
  say how a burst of releases is covered, for example by the gate picking up every unread release on
  later runs.
- Note: .github/lane-funding.json: the new model job runs on ISSUE_LANE_CLAUDE_API_KEY with the
  resolve-issue model route, timeout 90 min and leg timeout 70 min. This is spend a human should be
  aware of; it does not loosen a hold.
- Note: .github/actions/request-lane-token/action.yml and .github/actions/resolve-config/*: changes
  in the takeover set (a new `release` field and new lane grants). Both match owner decisions
  recorded on #7407 (broker widening, Q3 scan-carrier second holder). The scan job's environment,
  permissions and gate dependency are pinned by tests in
  .github/actions/resolve-config/callers.test.mjs. No takeover risk found.
- Note: .github/scripts/upstream-apply-changelog/upstream-apply-changelog.mjs: the PR body lists the
  plain-text diffs.txt scan (split-token, LOW) and the 90-day done marker keeping plan.json as
  hardening residuals. Neither blocks; a person should track them.

This pull request leaves the merge queue, and ci-status holds it out while it carries pr-verify-failed. A pr-verify pass at a new commit removes the label.

@github-actions github-actions Bot added the pr-verify-failed pr-verify failed this head; a pass at a new commit removes it. label Nov 3, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Nov 3, 2026
#7407 asks for a start within minutes of a release and a per-day cap
sized to the most releases in one day.

Schedule: every ten minutes (5-59/10, minutes no other schedule here
uses). A tick with nothing new makes at most 10 GITHUB_TOKEN REST calls
(one done-marker artifact read per candidate); a tick that finds a
release adds one cap read, so at most 11. Checkout uses git transport
and setup-pinned-node makes no API call. 6 ticks/h x 11 = 66 calls/h,
6.6% of the 1,000/h GITHUB_TOKEN limit and 9.4% of its 700/h plan
(rate-limits.json plan 0.7), leaving the rest to the other lanes.

Window: --ci never moves the read marker, so the unread list only
grows (16 today past 2.1.296), and changelog-status.sh's replay cap
omitted the list beyond 10 releases, which made the scheduled gate throw.
The gate now raises the status caps so the list always prints and checks
only the ten newest unread releases, newest first, which bounds the
per-tick cost; older ones wait for a person to move the marker. Each tick
re-reads the list, so every release of a burst is taken on a later tick.

Cap: 3, the most anthropics/claude-code releases published in one UTC
day or any 24 hours from 2026-05-02 to 2026-11-01 (158 releases, GitHub
releases API, read 2026-11-02). The schema default and the workflow's
fallback follow.

Refs: #7407

Co-Authored-By: Claude Opus 5.5 <[email protected]>
@github-actions github-actions Bot removed the pr-verify-failed pr-verify failed this head; a pass at a new commit removes it. label Nov 3, 2026
@kyle-sexton
kyle-sexton added this pull request to the merge queue Nov 3, 2026
Merged via the queue into main with commit 018396c Nov 3, 2026
89 checks passed
@kyle-sexton
kyle-sexton deleted the feat/release-intake-workflow branch November 3, 2026 01:57
@github-actions

github-actions Bot commented Nov 3, 2026

Copy link
Copy Markdown
Contributor

Post-merge mutation testing

Killed 3, survived 2, timeout 0, no coverage 0, invalid 0, not run 2.
Covered-code score 60%; mutation score 60%.

File Killed Survived Timeout No coverage Invalid Not run
.github/scripts/pr-merge/merge-holds.mjs 0 0 0 0 0 1
.github/scripts/upstream-apply-changelog/upstream-apply-changelog.mjs 3 2 0 0 0 0
plugins/source-control/skills/babysit-prs/scripts/babysit_merge_policy.py 0 0 0 0 0 1

Surviving mutants

Location Operator Disposition Cause
.github/scripts/upstream-apply-changelog/upstream-apply-changelog.mjs:70 ROR productive input-gap
.github/scripts/upstream-apply-changelog/upstream-apply-changelog.mjs:225 ROR productive input-gap

The run's summary has the full report.

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