You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
pr-unblock: derive fix-ci-subscription's start and the restart cap from trusted values #8890
No related issue: follow-up hardening from the Phase 5b review (fix-ci on run-claude-cli).
Problem
Two pr-unblock decisions about fix-ci still read outputs of the read runner's model job (pr-run-activity-read.yml, job run). The 5b review treats those outputs as untrusted, because that job runs PR head code:
The read runner's report job skips its check-run post when the run reports handed-off == 'true'.
A run that misreports these can end with no pr-unblock / fix-ci check posted and no subscription call. fix-ci-dispatch then dispatches again, and the restart cap never counts it. The worst case is repeated lane runs on one PR, which spend prepaid API credits (auto-refill is off, so it costs credits, not a charge).
Fix
Derive the subscription start from values the model job cannot write. Two options: the job's own step outcome read through the jobs API, or the fix-ci-context job's result.
Make fix-ci-dispatch count a dispatched run that posted no check toward fix-ci-restarts, so the loop is capped whatever the run reports.
Acceptance
fix-ci-subscription's start condition reads no needs.fix-ci.outputs.* value. A test pins this.
A dispatched fix-ci run that posts no check counts toward the restart cap. A test covers it.
Priority: high within the lane-funding follow-ups.
Proposed work class: work-class: scoped
Basis: automated intake triage; the lanes App applied this class.
Size: Medium
Complexity: Medium
Uncertainty: Known
Flags: none
Blocked by: none
Understood: harden pr-unblock so the fix-ci and fix-docs subscription start conditions read no model-job outputs (use trusted values such as the jobs API step outcome or the context job's result). Also make the dispatchers count a dispatched run that posted no check, so the restart cap and the "handled" marking hold whatever the run reports. That covers the L1 re-fire, L2 cap starvation and L3 start-condition findings from the #8964 review. The acceptance tests are specified. Nothing is missing. The approach is a choice between the two named options, which the implementer can settle. Treated as ordinary hardening work (the findings are rated LOW and the spend is prepaid credit), so it is not held for a person. No likely duplicates found among the open issues.
fix-docs-dispatch.mjs marks a PR handled only when it finds a pr-unblock / fix-docs check run (:145-147, :187-188). A successful dispatch marks nothing (:199-212), and the in-flight guard skips only runs that are not completed (:177-181). A fix-docs run that ends without posting its check is therefore dispatched again on every push to main, with no per-PR cap. Fix: treat a completed pr-unblock fix-docs #<n> run (runTitle) as handled.
Acceptance additions:
fix-docs-subscription's start condition reads no needs.fix-docs.outputs.* value. A test pins this.
A PR whose fix-docs run completed without posting a check is not dispatched again. A test covers it.
Lands right after #8964 and before Phase 7.2, so the address-feedback and review-dependency subscription jobs don't copy the pattern.
Security review findings on #8964 (all LOW, all assigned here):
L1, re-fire: when the read runner's run job leaves its head output empty, or reports handed-off=true (pr-run-activity-read.yml:2424, :2534-2543), the report job posts no check. The PR is then dispatched again on every push to main, about once per PR per push and at most 2 per push. At last week's main push rate that is up to about 356 runs a day, each spending prepaid API credits. Precondition: a steered model on a listed author's PR. Fix: record the dispatched head at dispatch time (fix-docs-dispatch.mjs:212), or pass a base-derived head so the report posts a neutral not-assessed check.
L2, cap starvation: a same-repository PR by an author not on the trusted list stops at the trigger gate and posts no check. It takes a dispatch slot on every push (lowest number first, :160, :175), so two such PRs block fix-docs for every other PR. No model spend. The same fix covers it.
L3, subscription start: fix-docs-subscription starts on run-job outputs a forged run can set (pr-unblock.yml:516-517, :532), the same pattern as fix-ci. It widens no credential reach and triggers no metered charge (usage credits stay off).
Released for re-triage under the owner's 2026-10-24 rule: an issue stops for a person only for an intent or vision question, a security call, a host or attended run, or spend outside prepaid credit. The hold label came off and intake triage runs again now; it re-applies a hold, with a numbered question, when one of those applies.
To hold this issue again, add needs-human (or status: needs-decision) and say why in a comment.
Not admitted to the issue lane: no trusted-human acceptance criteria: a listed human must put the confirmed - [ ] criteria in the body or post them as their own comment, or, on an issue a listed human filed, intake's newest refinement comment must hold them unedited. Admission takes the issue once each of those is met, when intake triages it again (.github/actions/resolve-config/admission-rules.json).
Refinement. A read-only run explored this repository, searched its issues and pull requests, and read the allowed docs to draft what follows. On an issue a listed human filed, it counts as the issue's spec once posted, while unedited; it is still model text, so check it.
Intended outcome: Narrow the issue to the dispatcher half: count a completed fix-ci or fix-docs run that posted no check toward the restart cap or as handled, with tests; the subscription-start half is already in the code.
Is the subscription-start half (fix-ci and fix-docs) already done, so the issue shrinks to the dispatcher half?
RECOMMENDED: Yes. Retitle the issue to the dispatcher half and drop the subscription-start criteria, keeping only a regression test that pins the start conditions. Basis: .github/workflows/pr-unblock.yml:1782-1786 starts on fix-ci-handoff.outputs.posted; .github/workflows/pr-refine.yml:185-190 starts on fix-docs-handoff outputs; neither reads needs.fix-ci.outputs or needs.fix-docs.outputs for its start.
Where should the fix-docs dispatcher change be made, given that the issue cites pr-unblock.yml lines that no longer hold fix-docs?
RECOMMENDED: Make it in .github/scripts/pr-unblock/fix-docs-dispatch.mjs and its test, and in the pr-refine.yml fix-docs jobs. Treat a completed 'pr-refine fix-docs #<n>' run as handled, using the runTitle helper at fix-docs-dispatch.mjs:146. Basis: .github/scripts/pr-unblock/fix-docs-dispatch.mjs:146,195,210 (the in-flight guard skips only runs that are not completed)
How should fix-ci-dispatch count a dispatched run that posted no check toward fix-ci-restarts?
RECOMMENDED: Count completed workflow runs titled by runTitle(activity, number) on the current base that have no matching check run, alongside the failed check runs already counted at fix-ci-dispatch.mjs:298-302. Keep the runs-per-head cap. Basis: judgment; fix-ci-dispatch.mjs:298-302 counts only failed check runs, so a run that posts no check is never counted.
Should the fix-docs L2 finding (an untrusted-author same-repository PR takes a dispatch slot on every push) be part of this issue?
RECOMMENDED: Yes, and fold it into the handled-marking fix, since a completed run title marks that PR handled too. If the owner prefers, split it into a separate issue. Basis: fix-docs-dispatch.mjs:160,175 as cited in the 2026-10-22 comment; judgment on scope
Sources read
context.json: the issue body and the three maintainer comments, with no recorded owner decision
/home/runner/work/claude-code-plugins/claude-code-plugins/.base/.github/workflows/pr-unblock.yml:1743-1811: fix-ci-handoff reads the jobs API, and fix-ci-subscription starts on needs.fix-ci-handoff.outputs.posted != 'true'
/home/runner/work/claude-code-plugins/claude-code-plugins/.base/.github/workflows/pr-refine.yml:148-190: fix-docs-handoff and fix-docs-subscription start on handoff outputs
/home/runner/work/claude-code-plugins/claude-code-plugins/.base/.github/scripts/pr-unblock/fix-ci-dispatch.mjs:298-302: the restart cap counts failed check runs only; line 363 is the in-flight guard
/home/runner/work/claude-code-plugins/claude-code-plugins/.base/.github/scripts/pr-unblock/fix-docs-dispatch.mjs:129,195-220: handled means a check run exists, and the in-flight guard skips only runs that are not completed
Automated refinement by the lanes App. Answer with a comment whose first line is **Decision recorded.**.
Activity
github-actions commentedon Oct 21, 2026
Proposed work class: work-class: scoped
Basis: automated intake triage; the lanes App applied this class.
Size: Medium
Complexity: Medium
Uncertainty: Known
Flags: none
Blocked by: none
Understood: harden pr-unblock so the fix-ci and fix-docs subscription start conditions read no model-job outputs (use trusted values such as the jobs API step outcome or the context job's result). Also make the dispatchers count a dispatched run that posted no check, so the restart cap and the "handled" marking hold whatever the run reports. That covers the L1 re-fire, L2 cap starvation and L3 start-condition findings from the #8964 review. The acceptance tests are specified. Nothing is missing. The approach is a choice between the two named options, which the implementer can settle. Treated as ordinary hardening work (the findings are rated LOW and the spend is prepaid credit), so it is not held for a person. No likely duplicates found among the open issues.
Automated triage, applied by the lanes App.
kyle-sexton commentedon Oct 22, 2026
Scope widened after the #8964 security pass (fix-docs moved into pr-unblock):
fix-docs-subscriptionrepeats the same start condition: it readsneeds.fix-docs.outputs.lane-classandcredential-present(.github/workflows/pr-unblock.yml:516-517, fallback input at:532, as of feat(pr-pipeline): run fix-docs on run-claude-cli with an apply-patch push job #8964 head e89dc01). Its start must come from trusted values too.fix-docs-dispatch.mjsmarks a PR handled only when it finds apr-unblock / fix-docscheck run (:145-147,:187-188). A successful dispatch marks nothing (:199-212), and the in-flight guard skips only runs that are not completed (:177-181). A fix-docs run that ends without posting its check is therefore dispatched again on every push to main, with no per-PR cap. Fix: treat a completedpr-unblock fix-docs #<n>run (runTitle) as handled.Acceptance additions:
fix-docs-subscription's start condition reads noneeds.fix-docs.outputs.*value. A test pins this.Lands right after #8964 and before Phase 7.2, so the address-feedback and review-dependency subscription jobs don't copy the pattern.
kyle-sexton commentedon Oct 22, 2026
Security review findings on #8964 (all LOW, all assigned here):
handed-off=true(pr-run-activity-read.yml:2424,:2534-2543), the report job posts no check. The PR is then dispatched again on every push to main, about once per PR per push and at most 2 per push. At last week's main push rate that is up to about 356 runs a day, each spending prepaid API credits. Precondition: a steered model on a listed author's PR. Fix: record the dispatched head at dispatch time (fix-docs-dispatch.mjs:212), or pass a base-derived head so the report posts a neutral not-assessed check.:160,:175), so two such PRs block fix-docs for every other PR. No model spend. The same fix covers it.fix-docs-subscriptionstarts on run-job outputs a forged run can set (pr-unblock.yml:516-517,:532), the same pattern as fix-ci. It widens no credential reach and triggers no metered charge (usage credits stay off).fix(pr-pipeline): start fix-docs-subscription on a jobs-API handoff read
fix(pr-pipeline): label fix-docs-subscription fallback: handoff
fix(pr-pipeline): take apply-docs-patch inputs from the scan job
kyle-sexton commentedon Oct 25, 2026
Released for re-triage under the owner's 2026-10-24 rule: an issue stops for a person only for an intent or vision question, a security call, a host or attended run, or spend outside prepaid credit. The hold label came off and intake triage runs again now; it re-applies a hold, with a numbered question, when one of those applies.
To hold this issue again, add
needs-human(orstatus: needs-decision) and say why in a comment.melodic-automation-lanes commentedon Oct 25, 2026
Not admitted to the issue lane: no trusted-human acceptance criteria: a listed human must put the confirmed
- [ ]criteria in the body or post them as their own comment, or, on an issue a listed human filed, intake's newest refinement comment must hold them unedited. Admission takes the issue once each of those is met, when intake triages it again (.github/actions/resolve-config/admission-rules.json).melodic-automation-lanes commentedon Oct 25, 2026
The latest triage verdict holds nothing: admission moved the issue to
status: needs-decision; its comment says why.melodic-automation-lanes commentedon Oct 25, 2026
Refinement. A read-only run explored this repository, searched its issues and pull requests, and read the allowed docs to draft what follows. On an issue a listed human filed, it counts as the issue's spec once posted, while unedited; it is still model text, so check it.
Intended outcome: Narrow the issue to the dispatcher half: count a completed fix-ci or fix-docs run that posted no check toward the restart cap or as handled, with tests; the subscription-start half is already in the code.
Related work
Open questions
RECOMMENDED: Yes. Retitle the issue to the dispatcher half and drop the subscription-start criteria, keeping only a regression test that pins the start conditions. Basis: .github/workflows/pr-unblock.yml:1782-1786 starts on fix-ci-handoff.outputs.posted; .github/workflows/pr-refine.yml:185-190 starts on fix-docs-handoff outputs; neither reads needs.fix-ci.outputs or needs.fix-docs.outputs for its start.
RECOMMENDED: Make it in .github/scripts/pr-unblock/fix-docs-dispatch.mjs and its test, and in the pr-refine.yml fix-docs jobs. Treat a completed 'pr-refine fix-docs #<n>' run as handled, using the runTitle helper at fix-docs-dispatch.mjs:146. Basis: .github/scripts/pr-unblock/fix-docs-dispatch.mjs:146,195,210 (the in-flight guard skips only runs that are not completed)
RECOMMENDED: Count completed workflow runs titled by runTitle(activity, number) on the current base that have no matching check run, alongside the failed check runs already counted at fix-ci-dispatch.mjs:298-302. Keep the runs-per-head cap. Basis: judgment; fix-ci-dispatch.mjs:298-302 counts only failed check runs, so a run that posts no check is never counted.
RECOMMENDED: Yes, and fold it into the handled-marking fix, since a completed run title marks that PR handled too. If the owner prefers, split it into a separate issue. Basis: fix-docs-dispatch.mjs:160,175 as cited in the 2026-10-22 comment; judgment on scope
Sources read
Automated refinement by the lanes App. Answer with a comment whose first line is
**Decision recorded.**.