Repository navigation
[qa-lead S1] Provision <id>:hands-off labels across the fleet — the opt-out is inert where the persona actually runs #1644
Description
Story
As a maintainer who is told by every qa-lead advisory that I can opt out with qa-lead:hands-off,
I want that label to actually exist in the repos where personas act,
so that the escape hatch mandated by persona-standards §4 rule 4 is real rather than inert.
The problem, verified 2026-09-01
combsmith/.github#756 made scripts/apply-repo-settings.sh derive <id>:hands-off from the persona manifests, and the labels exist in petry-projects/.github. They exist nowhere else. Via the labels API:
| Repo | persona opt-out labels present |
|---|---|
.github |
all 8 (business-analyst, dev-lead, devops-lead, pr-review, qa-lead, scrum-master, security-lead, sre-lead) |
.github-private |
only dev-lead:hands-off |
| TalkTerm, markets, ContentTwin, bmad-bgreat-suite, repo-template | only dev-lead:hands-off |
| broodly, incubator | none |
.github-private is the only repo where a persona mention is routable — it is the sole adopter of .github/workflows/persona-mention.yml. So the one repo where qa-lead can act is a repo where its opt-out label does not exist.
The router matches the opt-out by exact label name. In persona-mention-reusable.yml:
if [ -n "$opt_out" ] && printf '%s\n' "$LABELS" | grep -qxF "$opt_out"; then
echo "Item carries '${opt_out}' — persona '${slug}' is opted out here. Skipping."A label that does not exist can never match, so the check silently reads as "not opted out." This is fail-open by omission — note the contrast with the adjacent guard, which explicitly refuses to route when the label set is unreadable precisely so that "unreadable" is never mistaken for "no labels." The missing-label case defeats that care.
Likely root cause — check this first
#1209 (apply-repo-settings 100% fail: check-suites API needs classic-PAT/App admin (GH_PAT_WORKFLOWS rejected, 403)) is open. If the settings-apply path is 100% failing org-wide, that is very likely why #756's derived labels never reached any repo but the one they were authored in. Diagnose that before hand-creating anything: a label fanout that works once by hand and then rots is not a fix.
Acceptance Criteria
- Every org repo that has adopted — or may adopt — a persona surface carries the full derived
<id>:hands-offfamily, one per persona directory inpersonas/. Verified by an API check recorded in the PR (before/after table). .github-privatespecifically carriesqa-lead:hands-off, because it is the only repo where the mention router is live today. This is the acceptance bar that actually matters.- The family stays derived, not enumerated — adding a new persona requires no edit to any label list. This is fix(pr-review-sweep): dispatch with GH_PAT_WORKFLOWS, not the bot comment PAT #756's explicit constraint and must not be regressed.
- The reason the fanout never happened is identified and fixed at the cause, not worked around. If apply-repo-settings 100% fail: check-suites API needs classic-PAT/App admin (GH_PAT_WORKFLOWS rejected, 403) #1209 is the blocker, either fix it or scope label application so it does not depend on the failing check-suites call — and say which, in the PR description.
- Re-running is idempotent;
--dry-runreports what it would create and mutates nothing. - Drift is surfaced, not discovered by hand: a repo missing a persona opt-out label appears in an existing report. Extend
scripts/fleet_report.sh/ the Fleet Monitor rather than adding a new scheduled workflow (AGENTS.md, "Scheduled workflows"). - The advisory footer's opt-out sentence is true everywhere it can be printed. If AC test issue from agent #1 cannot be met for some repo, then
prompts/qa-lead/advisory.mdmust stop printing an instruction that does not work there. Prefer making it true.
Tasks / Subtasks
- Reproduce the fanout failure and determine whether apply-repo-settings 100% fail: check-suites API needs classic-PAT/App admin (GH_PAT_WORKFLOWS rejected, 403) #1209 is the cause. Record the finding in the PR. (AC: Optimize review: small-PR and incremental fast paths #4)
- Fix the fanout so the derived family reaches every repo. Do not hand-create labels as the fix; hand-creation is acceptable only as a one-time backfill alongside a working automated path. (AC: test issue from agent #1, Add @claude delegation, auto-merge, and rebase handling #3, Optimize review: small-PR and incremental fast paths #4)
- Verify
qa-lead:hands-offexists in.github-privateand that the router'sgrep -qxFnow has something to match. (AC: Go-live improvements for PR review agent #2) - Add the missing-opt-out-label drift check to the fleet report. (AC: feat: feature ideation framework — adversarial proposer/challenger/synthesizer #6)
- Confirm idempotency and
--dry-run. (AC: feat: add Copilot engine support via REVIEW_ENGINE toggle #5) - Re-check every org repo via the labels API and paste the before/after into the PR. (AC: test issue from agent #1)
Dev Notes
dev-lead:hands-offis not a special case. It predates the family and already exists in six repos; fix(pr-review-sweep): dispatch with GH_PAT_WORKFLOWS, not the bot comment PAT #756 explicitly calls it "undocumented drift" to be reconciled as a member of the derived family. Do not add a code path for it.- Do not hardcode
qa-lead:hands-off. fix(pr-review-sweep): dispatch with GH_PAT_WORKFLOWS, not the bot comment PAT #756 rejected enumeration by name: "Hardcodingqa-lead:hands-offnext to the other 7 just moves the problem." .github-privateis public despite its name (private: false), so an unauthenticatedraw.githubusercontentread of the manifests works and needs no token. fix(pr-review-sweep): dispatch with GH_PAT_WORKFLOWS, not the bot comment PAT #756 records that assuming otherwise already produced one false "critical" review finding.- persona-standards §7 (Definition of Done) requires that an
opt_out_labelbe defined. It does not require it to exist. That gap is what this story closes; consider whether the DoD wording deserves a follow-up inpetry-projects/.github. - This story gates [qa-lead S3] Serve the pull_request surface so qa-lead runs without being summoned #1646 (serve the
pull_requestsurface). The sequencing is deliberate: do not turn on an automatic trigger before the opt-out that contains it works.
Project Structure Notes
Changes land in petry-projects/.github (scripts/apply-repo-settings.sh, standards/github-settings.md) and in this repo's fleet reporting (scripts/fleet_report.sh + its bats suite, registered in lint.yml's bats list per the AGENTS.md exception).
References
- Provision <id>:hands-off labels so persona opt-out actually works .github#756 — the derivation this fans out
- apply-repo-settings 100% fail: check-suites API needs classic-PAT/App admin (GH_PAT_WORKFLOWS rejected, 403) #1209 — the likely blocker
- petry-projects/.github
standards/persona-standards.md§4 rule 4, §7 - petry-projects/.github
.github/workflows/persona-mention-reusable.yml— thegrep -qxFopt-out match prompts/qa-lead/advisory.md— prints the opt-out instructionscripts/fleet_report.sh, AGENTS.md "Scheduled workflows"
Update — 2026-09-01, cross-session reconciliation
This is already a tracked item — link, do not duplicate. #1318 (Epic: Persona framework — post-rollout hardening & follow-ups, open since 2026-07-19) carries:
- Provision the
<id>:hands-offopt-out labels in the fleet (the deriving script [Fleet Monitor] petry-projects/.github-private — .github/workflows/daily-pr-review-health.yml #765 is merged but hasn't been RUN against repos).
That confirms the root cause hypothesised in "Likely root cause" above, and narrows it: the derivation is merged (combsmith/.github#765/#756) and simply has never been run against the fleet. So AC #4's diagnosis is largely pre-answered — the fix is running/scheduling it, plus whatever #1209 blocks. Tick this item on #1318 when the fanout lands.
Live-state correction. .github-private now carries all nine <id>:hands-off labels (including qa-lead:hands-off and solution-architect:hands-off), where at the start of the review it carried only dev-lead:hands-off. PR #1655's diff does not provision labels, and no label mutation appears in its run log, so the provenance is unestablished (the org audit-log API is not readable with the available token). Treat "AC #2 is already met" as observed, not delivered — the remaining fleet repos (TalkTerm, markets, ContentTwin, bmad-bgreat-suite, repo-template, broodly, incubator) are still short, and .github itself is missing solution-architect:hands-off.
Activity
don-petry commentedon Sep 1, 2026
Dev-Lead Implementation Plan
Issue: #1644 — [qa-lead S1] Provision
<id>:hands-offlabels across the fleet — the opt-out is inert where the persona actually runsDiagnosis (AC #4) — recorded before touching code
Live label state re-checked via the labels API today (2026-09-01):
:hands-offlabels present.github.github-privateqa-lead:hands-offandsolution-architect:hands-offdev-lead:hands-offSo AC #2 is already met in live state:
.github-private— the only repo where the mentionrouter is live — now carries
qa-lead:hands-off, so the router'sgrep -qxFhas something tomatch. The remaining gap is (a) the derived family has not fanned out to the other consumer
repos, and (b) that drift is invisible — nothing surfaces a repo missing a persona opt-out label.
Scope boundary: the fanout mechanism (
scripts/apply-repo-settings.sh,standards/github-settings.md)lives in
petry-projects/.github, a different repo. It cannot be changed from a.github-privatebranch, and this automation only pushes here. The cross-repo fanout fix + one-time backfill is tracked
as a follow-up PR against
petry-projects/.github. This PR delivers AC #6 — the durable, in-repopiece: drift is surfaced, not discovered by hand.
Scope
Extend the Actions Fleet Monitor so any org repo missing a derived persona
<id>:hands-offlabel is surfaced in the fleet report — the family is derived from
personas/*/persona.yml(
opt_out_label:), never enumerated (AC #3).Implementation Checklist
scripts/fleet_report.sh— add pure functions: derive the opt-out family frompersonas/*/persona.yml; classify a repo's labels (COMPLETE / INCOMPLETE / ABSENT);render a "Persona opt-out label coverage" section; emit an alert JSON of INCOMPLETE repos.
scripts/fleet_monitor.sh— fetch each discovered repo's labels, build the drift TSV,append the new section, export the drift JSON. Best-effort (a label-read failure is a
per-repo
::warning::, never fatal), mirroring the stub-drift path.Tests to Write
tests/fleet_report.bats— derivation returns the full family from a fixture personas dir;classify COMPLETE / INCOMPLETE / ABSENT; report lists only INCOMPLETE repos with missing
labels named; alert JSON contains exactly the INCOMPLETE repos; empty/malformed inputs are
tolerated (no crash).
Test Command
scripts/run-bats.sh tests/fleet_report.batsandbash .dev-lead/scripts/dev-lead-lint.shdon-petry commentedon Sep 1, 2026
Dev-Lead: Progress (in progress — not yet durable)
What this branch does (AC #6 — the in-repo, durable piece)
Extends the Actions Fleet Monitor so a repo missing a persona
<id>:hands-offopt-out label is surfaced in the report, not discovered by hand:
scripts/fleet_report.sh— new pure functions:derive_persona_optout_labels(family derived from
personas/*/persona.yml, never enumerated — AC Add @claude delegation, auto-merge, and rebase handling #3),persona_optout_row(COMPLETE / INCOMPLETE / ABSENT),generate_persona_optout_report(a "Persona opt-out label coverage" section),
persona_optout_alert_json.scripts/fleet_monitor.sh— fetches each discovered repo's labels (best-effort;a per-repo read failure is a
::warning::, never fatal), builds the drift TSV,renders the new section, and writes
fleet_persona_optout.json.tests/fleet_report.bats— 12 new tests (already registered inlint.yml).Smoke-tested against live fleet labels:
.github-private→ COMPLETE (all 9,incl.
qa-lead:hands-off);.github→ INCOMPLETE (missingsolution-architect:hands-off);TalkTerm/markets/ContentTwin→ INCOMPLETE (onlydev-lead:hands-off);broodly→ ABSENT.solution-architect:hands-offshows up with zero list edits,demonstrating the derived-not-enumerated constraint.
Scope boundary — read before reviewing (AC #1/#3/#4/#5 fanout)
The fanout mechanism (
scripts/apply-repo-settings.sh,standards/github-settings.md)lives in
petry-projects/.github, a different repo. It cannot be changed from a.github-privatebranch and this automation only pushes here, so the cross-repofanout fix + one-time backfill is a follow-up PR against
petry-projects/.github,not this one. This PR delivers the drift visibility so the gap stops being silent.
Diagnosis (AC #4)
Re-verified live label state 2026-09-01: AC #2 is already met —
.github-private(the only repo where the mention router is live) now carries
qa-lead:hands-off, so therouter's
grep -qxFhas a real label to match. The remaining gap is fanout to the otherconsumer repos + the absence of any drift surfacing (now closed here).
Local check output (provisional — not the pushed run)
Notes
prompts/qa-lead/advisory.md(AC Workflow should skip no-op PRs in MAX_PRS count #7): its opt-out sentence is truein
.github-private(the only repo where qa-lead runs), so the footer stays — theright move is making the label real everywhere, which the fanout follow-up does.
don-petry commentedon Sep 1, 2026
Dev-Lead: Implementation Complete — PR #1655
The implementation for issue #1644 is durable: commits are pushed and a pull request is open. This record is backed by verifiable artifacts you can confirm in one click.
aecb133b189dff4448792536937003e5bb9eeec9Review happens on the PR. Acceptance criteria and test results are evidenced by the PR's diff and CI checks — not asserted here. If a later stage fails, this claim is retracted in place.
feat: implement issue #1644 — [qa-lead S1] Provision <id>:hands-off l…
don-petry commentedon Sep 8, 2026
Provisioning half split to #1740 (blocked on
petry-projects/.github#1045). PR #1655 merges on the observability scope; the<id>:hands-offlabels are not yet provisioned outside.github, so this story's opt-out guarantee is not yet real — tracked on #1740, not closed with this PR.feat: implement issue #1644 — [qa-lead S1] Provision <id>:hands-off l…
don-petry commentedon Sep 20, 2026
Closing — delivered and verified 2026-09-20
AC #1 / #2 — met. All nine
<id>:hands-offlabels are present in every fleet repo, verified via the labels API:.github,.github-private,TalkTerm,markets,ContentTwin,bmad-bgreat-suite,repo-template,broodly,incubator— 9/9 each. At the start of this epic only.githubcarried the full family and six repos carrieddev-lead:hands-offalone.qa-lead:hands-offexists in.github-private, so the router'sgrep -qxFopt-out now has something to match.AC #3 — met. The family remains derived from
personas/<id>/persona.ymlviaapply_labels()→persona_opt_out_label_configs(); no enumeration was introduced.AC #4 — cause identified and fixed. #1209 (the suspected blocker) closed 2026-09-01, but the labels still did not spread, so it was not the cause. The real gap was that nothing checked or remediated the derived family:
check_labels()incompliance-audit.shiterated a hardcoded seven-label list, andcompliance-remediate.shheld a second independent copy. Filed as combsmith/.github#1139 and fixed by combsmith/.github#1140 (merged 2026-09-16).AC #6 — met.
scripts/fleet_report.shcarries the missing-opt-out-label drift check, so a short repo is surfaced rather than found by hand.AC #5 / #7 — unchanged from #756's derivation and the advisory footer is now true everywhere it can be printed.
Remaining dependency, tracked elsewhere — not a reason to hold this open
combsmith/.github#1128 (
#1045Step D — addapply-repo-settings.ymltoDEPLOYABLE_WORKFLOWS) is still open. Four repos (ContentTwin,repo-template,broodly,incubator) still have no caller stub, andmarkets' workflow isdisabled_manuallywith a missingGH_TOKEN_ADMIN. Their labels are present today, but the self-heal path that keeps them present is not yet deployed everywhere. If a repo drifts, the fleet-report check added here is what will surface it.Closing as delivered. Epic #1643's S1 is complete.