Table of Contents generated with DocToc
- Status-rollup comment — shared shape for skill-authored status updates
Every agent-authored status update on a <tracker> issue (the import
receipt, each sync pass, CVE allocation, dedupe merges, fix-PR
announcements, etc.) lands in one single rollup comment per
tracker. Each pass appends a new entry to that comment instead of
posting a fresh one. The result: scrolling a tracker's timeline shows
one rollup comment plus the human discussion, not twenty bot comments
drowning out the actual conversation.
This file is the canonical shape + upsert recipe. Skills (sync-*,
import-*, allocate-*, deduplicate-*, fix-*) reference this
file instead of re-specifying the shape.
One comment per tracker, identified by an opening HTML marker on the first line:
<!-- <tracker-name> status rollup v1 — all bot-authored status updates fold into this single comment. -->
<details><summary>YYYY-MM-DD · @user · <Action></summary>
<entry body>
</details>
---
<details><summary>YYYY-MM-DD · @user · <Action></summary>
<entry body>
</details>Rules (all load-bearing — breaking any of them breaks GitHub's Markdown rendering):
-
First line is the marker.
<!-- <tracker-name> status rollup v1 — … -->identifies the comment as the rollup, where<tracker-name>is the tracker repository's name (the part of<tracker>after the/). Detection matches<!-- <any-name> status rollup v<N>so a rollup is found whatever wrote it, and futurev2bumps remain findable. -
Every entry is its own
<details>block. Including the very first one (the import receipt). There is no always-visible preamble above the first<details>. The reason: every entry — current and historical — is collapsed by default per the user's rule; nothing is promoted above the fold. -
Open tag is one line. Write
<details><summary>…</summary>on a single line. Do not split<details>onto its own line and<summary>onto the next — that variant renders reliably on github.com too, but the one-line form is what the skills emit and detection passes match on. -
Summary contains three fields,
·-separated, in this order:YYYY-MM-DD · @handle · <Action>. No trailing text, no parenthetical headlines, nothing else. The details-open arrow on github.com already consumes horizontal space; keep the summary scannable at a glance. Optional fourth field in parentheses is allowed only for disambiguation — see Summary — action labels below. -
Exactly one blank line after
<summary>…</summary>. Markdown inside a<details>needs a blank line after the open to render. Two blank lines is fine; zero blank lines silently suppresses all markdown inside. -
Exactly one blank line before
</details>. Same reason, other end. -
No leading whitespace on any line inside the entry. A leading space or tab turns the line into a preformatted-code block and wrecks the rendering for every subsequent line. When a sync proposal pastes multi-line content into an entry, left-trim every line before writing.
-
Entries are separated by a bare
---on its own line, with one blank line on each side:</details> --- <details><summary>…</summary>
The ruler is GitHub's
<hr>. Skip the blank lines around it and the preceding</details>will stay attached. -
Chronological order — newest at the bottom. New entries append; the comment grows downward. A reader opens the rollup and scrolls to the latest entry at the end.
Each skill emits one of the following <Action> strings so the summary
line tells the reader at a glance what the entry represents:
| Emitting skill | <Action> value |
Optional parenthetical |
|---|---|---|
security-issue-import |
Import |
class + reporter, e.g. Import (Report, Jane Doe) |
security-issue-sync (ordinary pass) |
Sync |
one-phrase headline, e.g. Sync (pr merged → fix released) |
security-issue-sync (escalation, Step 4) |
Sync |
Sync (Step 4 escalation) |
security-issue-sync (reformat-only, migrating legacy comments) |
Reformat |
Reformat (N legacy comments folded) |
security-cve-allocate |
CVE allocated |
the allocated ID, e.g. CVE allocated (CVE-2026-40913) |
security-issue-deduplicate, on the kept tracker |
Merge (kept) |
dropped side's number, e.g. Merge (kept) (from #305) |
security-issue-deduplicate, on the dropped tracker |
Merge (dropped) |
kept side's number, e.g. Merge (dropped) (into #244) |
security-issue-fix |
Fix PR |
upstream PR number, e.g. Fix PR (<upstream>#65346) |
The parenthetical is optional; include it when it adds information a scroller actually wants (the CVE ID, the dedupe counterpart, the PR number). Do not restate the same fact inside the entry body and in the parenthetical; the body carries the detail, the summary carries the headline.
Inside the <details> block, write what the skill used to write in
its pre-collapse body — the bold headline, the **Next:** line, the
reporter-notification line, the full rationale. The entire body is
already inside <details>, so the "keep visible part under six
lines" rule from the legacy status-comment shape no longer
applies. Write what the auditor needs to reconstruct the decision;
the scroller sees the summary only and only expands entries they
care about.
That said — brevity still wins. Do not pad. Do not restate the previous entry. Each entry is incremental: what changed in this pass, what comes next, what the reporter now knows. Earlier state lives in earlier entries.
Required elements inside every entry body:
- Bold headline as the first line — the same bold-first-line rule
the old pre-collapse comments used. Example:
**Sync 2026-04-21 — pr merged → fix released.**. This is what a reader sees first when they expand the entry. **Next:**line — one sentence on what comes next. Omit only when the entry is terminal (e.g. dedupe on the dropped side, the "all triage continues on #" line replaces**Next:**).- Reporter-notification line when applicable — one of the four forms from the legacy spec (see each skill's dedicated section).
Outside that required frame, the content is free-form markdown.
Clickable <tracker> references (the Linking tracker issues and
PRs rule in AGENTS.md) apply everywhere, same
as before.
Every skill that emits a status update runs this recipe, through
github-rollup. The tool reads, appends
and writes in a subprocess, so the rollup body — the largest comment on
a long-running tracker — never enters the agent's context.
Nothing to do by hand: the tool finds the comment whose first line is
a rollup marker. Two rollups on one tracker is a bug; github-rollup list <N> shows what the tool sees, and the user picks which to keep.
Write the entry body (without the <details> envelope) to a file with
the Write tool, then:
uv run --directory <framework>/tools/github-rollup github-rollup \
--repo <tracker> append <N> --action "<Action>" \
--entry-body-file <scratch>/<entry>.mdThe tool adds the <details><summary>YYYY-MM-DD · @user · <Action></summary>
envelope and the ruler. --dry-run reports create-vs-append without
writing. amend-latest <N> --action "<Action>" --entry-body-file …
replaces the body of the most recent entry (keeping its date and user)
when a later step of the same pass has to fill in a value such as a
draft id; it refuses when the latest entry carries a different action.
The same append call creates the rollup, with the marker line, when
the tracker has none yet.
Trackers created before this convention carry one or more bot-authored status comments as separate top-level comments. Every sync pass runs a fold-legacy sub-step: detect each legacy bot comment, move its content into the rollup as its own entry, and delete (or minimise) the original.
A comment is a candidate for folding when all of the following hold:
- Not already a rollup. Its body does not start with a rollup
marker (
<!-- <tracker-name> status rollup v). - Author is on the security-team roster. Cross-check
.comments[].author.loginagainst the collaborator list (seeoperations.md) or the project's roster declared in<project-config>/release-trains.md. Human discussion from an external reporter is never folded; their content stays as a top-level comment. - Body matches one of the bot-shape prefixes (case-sensitive,
first ~500 characters of the body):
**Sync**Status update**Merged**Closing as duplicate**Split for scope clarity**Imported on**Process-step escalation**Allocated CVE/**CVE allocated/**Sync … — CVE- Legacy bare-text prefixes (no leading
**):Sync status (,Sync YYYY-MM-DD,Status update - Content tells when the prefix is idiosyncratic:
security-issue-sync skill,re-triage,Reporter notification still pending,Outstanding — Step, a verbatimgenerate-cve-jsonembed block.
A comment that matches 1 + 2 + 3 is foldable. A comment that matches only 1 + 2 (team-member comment with no bot-shape prefix) is regular human discussion — leave it alone.
For each foldable legacy comment, in chronological order:
-
Reconstruct the entry shape. Take the legacy body and wrap it in the rollup's
<details>envelope:<details><summary><createdAt date> · @<author.login> · <Action></summary> <legacy body — verbatim, left-trimmed> </details>
<createdAt date>is the first 10 chars of the legacy comment'screatedAt(YYYY-MM-DD).<Action>is derived from the legacy body's prefix via the table in Summary — action labels above; when the prefix does not map cleanly, useSyncand tag the fold asReformat (N legacy comments folded)on the overall rollup entry the sync is about to write.- Every line is left-trimmed (
folddoes it). Legacy comments that were hand-edited sometimes carry stray indentation on most lines; leaving it inside a<details>turns the whole entry into a preformatted-code block.
-
Append, then delete, oldest first, with one call per legacy comment:
uv run --directory <framework>/tools/github-rollup github-rollup \ --repo <tracker> fold <N> --comment-id <legacy-comment-id> --action "<Action>"
foldbuilds the entry from the legacy comment's own date and author, left-trims every line, appends it, and deletes the legacy comment only after the append succeeded. If the append fails, the content is still on the tracker via the legacy comment and the fold can be retried on the next pass.
Like every other skill action, surface each proposed fold as a
numbered item in the skill's Step 2 proposal. Show the legacy
comment's URL, its first ~3 lines, and the derived <Action> for
the summary. The user may accept all, accept some, or reject (for
example when a legacy comment has inline discussion from a reporter
mixed into a status update — in that case leave it alone, it is not
a pure bot comment).
The fold path still works: create the rollup (Step 2b) with the oldest foldable legacy comment as its first entry, then append the rest (Step 2a), then append the current pass's new entry last. The recap reports "created new rollup comment on #, folded N legacy comments into it" and lists the deleted comment IDs so the user can audit the change.
- Never touch a human-authored comment. Step 2 of the detection (author is on the security-team roster) is not optional. A reporter quoting a status update in their own words is not bot content — it is their message.
- Never delete a legacy comment before the append succeeds. If the PATCH lands a partial body (e.g. truncated), the only recoverable artefact is the original legacy comment.
- Never rewrite the content of a folded entry. Copy the legacy
body verbatim inside the
<details>block (with left-trim applied to fix indentation-induced rendering bugs, but nothing else). Paraphrasing a historical status update rewrites the audit trail. - Never promote a newer entry above an older one. The rollup is chronological. A sync pass that appends out of order hides the timeline.
- Never write markdown with leading spaces inside a
<details>block. A single stray indentation breaks rendering for every subsequent line of the entry. Compose entries with all lines flush-left. - Never create two rollups on the same tracker. If Step 1 of the
upsert finds more than one
status rollup vmarker, stop and ask the user which to keep — the cheapest recovery is a manual merge, not a silent overwrite. - Never name or describe other ASF projects' vulnerabilities in a
rollup entry body, even when the reporter or your own signal
mining has surfaced them. Cross-project observations belong in
the private mail channel they arrived on — not in the tracker.
See the "Other ASF projects — never name or describe their
vulnerabilities" subsection of
AGENTS.mdfor the full rule, the why, and the grep-list self-check to run before posting. Summarise load-bearing cross-project context in de-identified form ("the reporter has filed similar reports with other ASF projects") rather than naming the project.
.claude/skills/security-issue-import/SKILL.md— creates the rollup with the first entry..claude/skills/security-issue-sync/SKILL.md— appends per-sync entries and runs the fold-legacy sub-step..claude/skills/security-cve-allocate/SKILL.md— appends the CVE-allocation entry..claude/skills/security-issue-deduplicate/SKILL.md— appends the merge entry on both trackers..claude/skills/security-issue-fix/SKILL.md— appends the fix-PR entry.