Skip to content

feat(pr-pipeline): add an hourly sweep that re-dispatches intake triage - #8180

Merged
kyle-sexton merged 3 commits into
mainfrom
feat/self-healing-triage-sweep
Oct 18, 2026
Merged

kyle-sexton merged 3 commits into
mainfrom
feat/self-healing-triage-sweep

Conversation

@kyle-sexton

Copy link
Copy Markdown
Member

No related issue: owner-approved self-healing triage sweep

Summary

Adds an hourly triage-sweep job to maintenance-audit-lane-runs.yml. It finds open issues that intake triage never reached and dispatches intake-triage.yml for them, using only the job's GITHUB_TOKEN. It spends no lanes App token.

Fix

  • Schedule. A second cron (17 * * * *) runs only triage-sweep. The daily cron and manual runs run all four jobs. The hourly run has its own workflow concurrency group, so it never waits behind the daily audit. The job also has its own concurrency group, so two passes never overlap.
  • Selection (triage-sweep.mjs). An issue qualifies when all of these hold:
    • no <!-- claude-lane intake-triage --> comment from github-actions[bot];
    • no label matching needs-triage, needs-human, needs-spec, agent-ready, status: *, priority: * or work-class: *;
    • it is not a pull request;
    • its body does not end with the born-triaged marker;
    • it is over an hour old;
    • its author is a listed human or the lanes App.
  • Dispatch. At most 5 per pass, oldest first. Each dispatch's run id (from the 200 response) goes into one hidden-marker state comment on the issue, and later dispatches edit that same comment.
  • Give-up. Once 2 dispatched runs have completed and the issue is still untriaged, the sweep files one needs-triage issue that names the issue and both runs. It then marks the state comment and labels the original needs-triage, and dispatches it no more.
  • Security review fixes (review:security-reviewer, opus):
    • github-actions[bot]-authored issues are left out, because lane-sweep findings can quote log lines a fork controls.
    • An issue with more than 300 comments, where the state comment could be hidden, is labeled needs-triage and not dispatched.
    • Labeling the original issue on give-up stops it from crowding out newer candidates.
    • The pass stops before any dispatch or filing that the remaining call budget cannot record.
  • Accepted exposure. Intake reads issue comments, and anyone can comment during the hour before the sweep dispatches. Intake treats comments as untrusted data, applies only allowlisted labels and never applies agent-ready. This is recorded in the script header.

Tokens: the sweep uses GITHUB_TOKEN only (actions: write, contents: read, issues: write). The dispatched intake run uses its own GITHUB_TOKEN plus CLAUDE_CODE_OAUTH_TOKEN, and its actor github-actions[bot] (41898282) is in intake's bot list (.github/workflows/intake-triage.yml, "Compose the bot id list").

API cost per pass, against the 1,000 requests an hour per repository that GITHUB_TOKEN gets:

  • Today: about 8 calls (761 open issues and PRs make 8 list pages, and no issue currently qualifies).
  • Ceiling: 120 calls, enforced by CALL_BUDGET. That covers up to 15 list pages; up to 20 candidates at 3 comment pages plus 1 run read each; 5 dispatches at 2 calls each; and 5 filings at 4 calls each.
  • In the 06:xx hour the daily run adds its own budget of 350 + 50 calls.

Stranding: this changes no lane runner. The broker checks a run's caller and reusable files against main only for lanes in docs/conventions/pr-pipeline.yaml (azure-iac src/lanes-token-broker/src/binding.mjs, checkBinding). Those lanes are work-resolve-issue, pr-refine, pr-unblock, pr-merge and work-unblock.

Verification

  • node --test in .github/scripts/maintenance-audit-lane-runs/: 36 pass, 0 fail. The tests were written first, and the new tests failed before the code existed.
  • actionlint: clean. zizmor 1.30.0 (offline): no findings.
  • scripts/check-affected.sh --tests: 5 passed, 4 skipped.

Basis: dispatch returns workflow_run_id with a 200 status (https://docs.github.com/en/rest/actions/workflows#create-a-workflow-dispatch-event). GITHUB_TOKEN gets 1,000 requests an hour per repository (https://docs.github.com/en/rest/using-the-rest-api/rate-limits-for-the-rest-api). A workflow_dispatch sent with GITHUB_TOKEN still creates a run (https://docs.github.com/en/actions/concepts/security/github_token). The intake marker and the routing that drops the floor label come from ci-workflows intake-triage.yml at ff8cd36 (the comment-marker and floor steps).

Related

🤖 Generated with Claude Code

kyle-sexton and others added 2 commits October 18, 2026 05:55
The lane sweep workflow gains an hourly triage-sweep job. Each pass lists
open issues with no intake-triage comment and no triage or status label,
by a listed human or the lanes App, over an hour old and not born triaged,
and dispatches intake-triage.yml for at most 5 with the job's own token.
One bot comment per issue records the runs; after 2 that left the issue
untriaged, the sweep files one needs-triage issue naming the runs and
labels the original needs-triage. No App token is spent.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… section

Says what the triage-sweep job selects, what it does, and which token
it holds.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton marked this pull request as ready for review October 18, 2026 10:07
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 18, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-18T10:09:24.028584Z c50206d Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

claude-security-review reviewed a0f32001cccd86b8d85340c6bbd3c5464dfcf868: 0 finding(s), 0 as inline comments.

No security findings in the incremental change since c50206d (triage-sweep.mjs, its test, and docs/conventions/pr-pipeline/README.md).

  • checked counter. Issues with an intake comment no longer count toward MAX_CHECKED, so the loop can read more candidates. Each read still goes through call, which the 120-REST-call budget bounds. The dispatch and filing caps are unchanged. This adds no new trust boundary or amplification.
  • return_run_details: true. This is a fixed literal in the dispatch body. It adds no attacker-controlled input, and issue-number is still String(number) from an API-returned integer.
  • Label before filing. handOff now runs before renderFiledIssue and the POST. If a later write fails, the original issue is already out of the candidate set, so the sweep cannot file duplicate issues. A failure after the label can leave the original labeled without a filed issue. That is a reliability trade-off, not a security issue, and a person still sees the needs-triage label.
  • Token scope. The job still holds only its own GITHUB_TOKEN (actions: write, contents: read, issues: write). The diff widens nothing.
  • Docs and tests. The README change only reorders the described steps. The tests add no secrets or transcripts.

The instruction-surface lens did not apply: the diff removes no standing instruction, and the register was not needed.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: c50206df1b

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread .github/scripts/maintenance-audit-lane-runs/triage-sweep.mjs
Comment thread .github/scripts/maintenance-audit-lane-runs/triage-sweep.mjs Outdated
Comment thread .github/scripts/maintenance-audit-lane-runs/triage-sweep.mjs Outdated
Comment thread .github/scripts/maintenance-audit-lane-runs/triage-sweep.mjs
@github-actions

github-actions Bot commented Oct 18, 2026 •

Copy link
Copy Markdown
Contributor

claude-review reviewed a0f32001cccd86b8d85340c6bbd3c5464dfcf868: 0 finding(s), 0 as inline comments.

No findings. I read the incremental diff (c50206d..a0f3200) and the full runSweep loop in triage-sweep.mjs. I did not run the tests.

  • MAX_CHECKED counter: checked increments only after the intake-comment continue, so intake-commented issues no longer use up the 20-check limit. Each of those issues still costs one or more call reads. CALL_BUDGET and the call budget catch cap the total, so the loop stays bounded.
  • Label before filing: handOff(number) now runs before POST /issues, so a failed state write can no longer lead to a second filed issue. The new test a filing whose state write fails is not filed again on the next pass exercises this. If the POST /issues call itself fails, the original keeps needs-triage with no filed issue, and the failure is recorded in result.failed. That is an acceptable trade-off, because the label already hands the issue to a person.
  • Budget guard: the file-action guard remaining() < state.runs.length + 2 still matches the call count. Filing makes runs.length - 1 run reads, then the label, the filing and the state write. That is runs.length + 2 calls.
  • return_run_details: true: the fake GitHub in the tests returns a run id only when this flag is set, so the dispatch tests do cover it. The claim in the code comment that API version 2022-11-28 answers 204 without the flag is the author's, and I did not verify it against GitHub's docs.
  • Docs: the docs/conventions/pr-pipeline/README.md wording matches the new order, label first and then file.

…l before filing in the triage sweep

The intake dispatch now sends return_run_details: true; under API
version 2022-11-28 the endpoint otherwise answers 204 with no run id,
so no attempt was ever recorded. Issues intake already commented on no
longer count toward the 20-issue check limit, so they cannot starve the
issues behind them. Filing labels the original needs-triage before it
creates the handoff issue, so a failed state write cannot file a second
one on the next pass.

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton added this pull request to the merge queue Oct 18, 2026
Merged via the queue into main with commit bf2bacb Oct 18, 2026
29 checks passed
@kyle-sexton
kyle-sexton deleted the feat/self-healing-triage-sweep branch October 18, 2026 10:35
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