Files
Nicholas TindleandClaude Opus 5.5 cb690ed9cc ci(platform): let the credits PR run CI and flag advisories no release lists (#16259)
### Why / What / How

**Why:** two gaps in the public security sync from #16151.

1. **The credits PR's CI never starts by itself.** The sync opens its
credits PR with `GITHUB_TOKEN`. In this repo, every CI run on a PR
opened that way waits as `action_required` until a maintainer approves
it. #13152, the weekly Anthropic-rates PR, has sat like that since May.
So each credits update would need that click before it could even be
reviewed.
2. **A forgotten release link goes unnoticed.** An advisory is listed
under a release only when its description links that release. A
published advisory without the link is left out of every release's
notes, with no warning anywhere.

**What:** the credits PR is opened by the batch-bot App, so its CI runs
like any other PR's. The dry-run summary and the credits PR both name
any published advisory that no release lists.

**How:**

- The credits job mints a batch-bot App token with
`actions/create-github-app-token`, scoped to `contents: write` and
`pull-requests: write`, the same way `docs-gitbook-sync.yml` does. It
mints the token after the files are generated and passes it only to the
`create-pull-request` step. The job's own `GITHUB_TOKEN` now has no
permissions.
- The release writer keeps `GITHUB_TOKEN` on purpose. Release edits made
with it never start release workflows, which is what keeps old tags'
`release.edited` image builds from firing.
- A new `unlinked_advisories(snapshot)` helper returns the published
advisories with no release tags. The `credits` command's JSON output
carries that list, the credits PR body lists it, and the preview prints
it ("none." today).

### Changes 🏗️

- `.github/workflows/security-advisory-sync.yml` (credits job):
- The PR token is a batch-bot App token, and the job's own
`GITHUB_TOKEN` has no permissions.
  - The PR body gets a line listing unlinked advisories.
  - The checklist no longer asks the maintainer to approve CI runs.
- `security_public_sync.py`: adds `unlinked_advisories()`;
`prepare_credits()` returns it under `unlinked`.
- `security_public_publish.py`: the preview names unlinked published
advisories, or says none.
- `test_security_public_sync.py`: a new test covers the list in the
credits output and the preview, including that drafts never appear. The
exact-output assertions now include `unlinked`.
- `docs/engineering/security-advisory-release-notes.md`: the permissions
paragraph explains why each writer uses the token it does, and rollout
step 4 and the read-only job's description are updated.

### Agents and large language models used

- Claude Code with Claude Opus 5.5: design, implementation and
validation.

### Checklist 📋

#### For code changes:
- [x] I have clearly listed my changes in the PR description
- [x] I have made a test plan
- [x] I have tested my changes according to the test plan:
- [x] `python -m unittest discover -s .github/workflows/scripts -p
'test_*.py'`: 101 passed. The security tests also pass on Python 3.10,
which "Check PR Status" uses.
- [x] Three mutants are each killed by the new test: the inverted
filter, the list dropped from the credits output, and the preview always
saying none.
- [x] Live anonymous dry run against the public API: `credits` reports
`"unlinked": []`, and the preview still proposes sections for 19
releases and reports no unlinked advisories.
- [x] `actionlint` 1.7.12 is clean on the three security and Classic
workflows. The new step's `jq` expression and `errexit` handling were
exercised with both empty and non-empty lists and a failing `credits`
command.
- [x] `pre-commit` (Black, gitleaks, merge-conflict and debug checks) on
the changed files.
- [ ] Live App-token PR creation: this runs only after the variables are
enabled on `master`. The batch-bot App already opens and updates PRs
here (`batch-command-handler.yml` uses `permission-pull-requests:
write`).

#### For configuration changes:

- [x] `.env.default` is updated or already compatible with my changes
- [x] `docker-compose.yml` is updated or already compatible with my
changes
- [x] I have included a list of my configuration changes in the PR
description (under **Changes**). It uses the existing `BATCH_BOT_APP_ID`
variable and `BATCH_BOT_PRIVATE_KEY` secret; nothing new.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 (Claude Code) <[email protected]>
2026-10-27 01:10:03 +00:00
..