Skip to content

feat(pr-pipeline): let fix-ci adopt idle owner PRs - #8997

Merged
kyle-sexton merged 7 commits into
mainfrom
feat/fix-ci-adopt-idle-owner-prs
Oct 22, 2026
Merged

kyle-sexton merged 7 commits into
mainfrom
feat/fix-ci-adopt-idle-owner-prs

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Oct 22, 2026 •

Copy link
Copy Markdown
Member

No related issue: owner decision Q22 (2026-10-22)

Summary

This gives the lanes new unattended power. pr-unblock's fix-ci will now run a model on PRs that a person opened, not only on lanes App PRs, and push fix commits to those branches. Without anyone asking, it picks up a non-draft PR from an account listed in the new top-level adoption.owner-ids (this repository lists kyle-sexton, user id 153232337) when all of these hold:

  • the PR is a same-repository PR into main;
  • it has no do-not-merge, needs-human or tracker human-gated label (config.role_labels["human-gated"] in the base .work-item-tracker.json) and no body line starting Hold:;
  • its updatedAt and its head commit's committer date are both at least adoption.idle-hours (24) old;
  • its ci-status failed.

This is the fix-ci half of Q22. The pr-merge half reads the same adoption section and lands separately.

docs/conventions/pr-pipeline.yaml is a lane-power file, so a human merges this PR.

Fix

  • fix-ci-dispatch.mjs:
    • LIST_QUERY also selects ... on User { databaseId }, updatedAt, body, and the head commit's committedDate. pushedDate is not used: GitHub deprecated it and it is mostly null.
    • skipReason admits an adoption.ownerIds author once both dates are idleHours old. A missing or unreadable date counts as not idle.
    • For owner PRs only, skipReason also skips a body line starting Hold:. Lanes App PRs carry Hold: a human merges this (report-outcome.mjs:99), which holds the merge, not a CI fix, so their rules are unchanged.
    • Default ownerIds is [], so nothing is adopted when the section is absent.
  • dispatch.mjs: main takes an injectable loadAdoption (default loadBaseAdoption) and passes adoption and now to dispatchCiFixes.
  • The tracker's human-gated label (read by baseHumanGatedLabel, the way pr-merge resolves it):
    • It is read only while adoption.owner-ids lists anyone. While it is read, it is added to the hold labels for every fix-ci candidate, so it also holds a lanes App PR that carries it. This is intended: the human-gated label holds any PR. With owner-ids empty the file is not read and lanes App PRs keep the hold labels they had (do-not-merge, needs-human).
    • A base .work-item-tracker.json that is not JSON holds only the adopted owner PRs: each is skipped with "base tracker config unreadable, so its human-gated label is unknown" (adoption.held in skipReason). Lanes App PRs still get fix-ci, and the dispatcher run fails so the broken file shows.
  • resolve-config.mjs: adds adoptionFromBase, built like redMainFromBase. This file is not in BROKER_PATHS, and resolve.mjs is unchanged.
  • Schema: adds the adoption object (idle-hours integer from 1 to 720, default 24; owner-ids unique positive integers, default []). pr-pipeline.yaml sets the section.
  • Docs:
    • The README gets a new ## Adoption section and an updated fix-ci scope paragraph.
    • The fix-ci comment in pr-run-activity.md and two comments in pr-unblock-dispatch.yml now name the new scope.
    • Adds a changelog fragment.

Downstream checks (none refuses an owner-authored head):

  • check-trusted-trigger.mjs:812 and the broker's authorize.mjs:184 require only that the author is on the trusted-actor list, and kyle-sexton is listed.
  • The broker's dispatcher.mjs checkDispatcher has no author key on the dispatch path.
  • The fix-ci and apply-patch applies-when checks only labels and draft state.
  • apply-patch.mjs TARGETS.pr checks for a same-repository head, draft state, and hold labels. It has no author check.
  • fix-ci-context.mjs countFixRounds counts only the App's trailer commits, so an owner PR with zero App commits starts at 0 rounds. The existing stranger-commit test covers this.
  • The broker reads the schema from the tip of main along with the config (binding.mjs TRUST_FILES), so a new top-level key needs no pin bump. No file in BROKER_PATHS changed, so the body names no broker pin.

A consequence to know: fix-ci's own push restarts the idle clock, so an owner PR gets at most one fix round per idle window.

Verification

  • I wrote the new tests first and watched them fail (not opened by the lanes App, and the missing on User field in the query):
    • fix-ci-dispatch.test.mjs:
      • an owner PR idle 25h with a failed ci-status is dispatched;
      • idle 2h is skipped with the exact reason; 25h/2h and 2h/25h are both skipped;
      • an owner draft, either hold label, a Hold: line, an unlisted user, an unreadable date, or no adoption list means no dispatch;
      • a fresh lanes App PR with Hold: a human merges this is still dispatched.
    • dispatch-limits.test.mjs: the live config adopts the owner PR end to end, and the same config with the adoption section removed does not. A mapped human-gated label holds an adopted PR. Under a malformed tracker file, lanes App PR 63 is dispatched and an idle owner PR is not (exit 1); with no owner-ids the file is not read (exit 0). This test was rewritten first and failed before the fix.
    • fix-ci-dispatch.test.mjs also covers adoption.held, and an adopted owner PR whose ci-status failed on infra only is left to the reconciler, as lanes App PRs are.
  • node --test at the current head over .github/scripts/pr-unblock, .github/actions/resolve-config and .github/scripts/branch-cleanup: 1118 pass, 0 fail. Earlier results:
    • .github/scripts/pr-unblock: 445 pass, 0 fail.
    • .github/actions/resolve-config: 552 pass, 0 fail.
    • check-trusted-trigger 298, check-wip-cap 29, report-check-run 99, and pr-merge/refire-refused 22, all pass.
    • scripts/check-convention-yaml.test.sh: 43 pass.
  • markdownlint is clean, and the pre-push check-affected passed.
  • scripts/read-runner-dispatcher-gate.test.sh fails the same way on main (runner-policy not installed locally).

Related

  • Owner decision Q22 (2026-10-22); the pr-merge half is a separate PR that reads the same adoption section.
  • ADR 0051

🤖 Generated with Claude Code

Owner decision Q22 (2026-10-22): pr-unblock-dispatch now starts fix-ci
for a non-draft PR opened by an account in the new top-level `adoption`
section (owner-ids; this repo lists kyle-sexton) once the PR's updatedAt
and its head commit's committer date are both idle-hours (24) old, with
no hold label and no body line starting `Hold:`. Lanes App PRs keep their
rules. The listing query also selects User databaseId, updatedAt, body
and the head commit's committedDate.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review of #8997: the owner Hold: line now uses babysit's
BODY_MERGE_HOLD_RE semantics (any case, any leading non-newline
whitespace, a bare CR ends a line), and fix-ci's hold labels add the
tracker's human-gated label from the base .work-item-tracker.json, as
pr-merge.yml resolves it (needs-human when unmapped). An unreadable
tracker config stops fix-ci alone. Docs say pr-merge's adoption path is
still to land. Tests cover spoofed logins, author null, an unreadable
commit date and per-node skip reasons.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton marked this pull request as ready for review October 22, 2026 02:59
@kyle-sexton
kyle-sexton added this pull request to the merge queue Oct 22, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 22, 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-22T03:01:11.715715Z 6a27e31 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 22, 2026 •

Copy link
Copy Markdown
Contributor

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

Code review: PR #8997 (fix-ci adopts idle owner PRs)

I found nothing to flag, so I posted no inline comments. This is a read-through of the diff. I did not run the test suites; the PR body's test counts are author-claimed and unverified by me.

What I checked

  • Gate logic in fix-ci-dispatch.mjs:
    • A missing or unparseable date makes Date.parse return NaN, so <= since is false and the PR counts as not idle.
    • The head-commit oid must match headRefOid.
    • A null author is skipped as unlisted. The author id decides, never the login.
    • A lanes App PR keeps its old path: no idle wait, and a Hold: line does not hold it.
  • Hold: regex: I compared HOLD_LINE with BODY_MERGE_HOLD_RE in plugins/source-control/skills/babysit-prs/scripts/babysit_merge.py:230. The JS lookbehind form matches the same line starts as Python's (?:^|\r) with re.M. The claim of the same reading holds.
  • Human-gated label: baseHumanGatedLabel and humanGatedLabel follow the jq in .github/workflows/pr-merge.yml (label trimmed, needs-human when absent or empty). The base checkout in pr-unblock-dispatch.yml is .base at refs/heads/main, so .base/.work-item-tracker.json is the file read.
  • Unreadable tracker config: this holds only adopted owner PRs, fails the run through fixes.failed, and still lets lanes App PRs through. The tests cover it.
  • Config wiring: adoptionFromBase follows redMainFromBase. The schema bounds idle-hours (1 to 720) and requires owner-ids to be unique positive integers. The default is no owners, so nothing is adopted when the section is absent.
  • Manifests: no plugin.json or marketplace.json changed, so there are no manifest <field> lines. The bump: minor fragment fits an added config section.
  • Closing issue: the PR states No related issue: owner decision Q22, so there are no acceptance criteria to check. I did not read any decision record for Q22.
  • Embedded text: the PR context holds no directives aimed at the reviewer.

Observations only (not findings)

  • The PR and the changelog both state that, while owner-ids is non-empty, the tracker's human-gated label also holds lanes App PRs from fix-ci. That is a behaviour change for existing PRs. It is documented and tested, and the PR labels it as a human-merge lane-power change.
  • The committer date is set by whoever makes the commit, so a backdated commit can satisfy the idle test. The adoption list limits this to trusted owner accounts.

@github-actions

github-actions Bot commented Oct 22, 2026 •

Copy link
Copy Markdown
Contributor

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

Security review: PR #8997 at 5db81ae

Findings: 0

PR #8997 lets pr-unblock-dispatch start fix-ci on an idle PR opened by an account listed in a new top-level adoption config section, as well as on lanes App PRs. I read all 12 changed files and the downstream workflows that consume the dispatch.

What I checked

  • Owner list. adoption.owner-ids comes from the default-branch config. dispatch.mjs reads it through adoptionFromBase, under BASE_PATH .base. A PR cannot widen the list it is judged against. The list is also schema-validated: integers of at least 1, and idle-hours bounded to 1–720.
  • Author match. Matching is by numeric databaseId with strict includes. A string id, a missing author, or an unlisted author all fail closed. An owner-id colliding with the App's id is not plausible, since both are user-id-space values.
  • Fork PRs. isCrossRepository !== false is skipped before any owner check, so fork PRs are never adopted.
  • Holds. Hold labels still apply to owner PRs. An owner PR's body Hold: line holds the lane off, using a regex that mirrors babysit's. A hold that fails to match could only start a CI fix on the owner's own PR, which stays inside the trust boundary and is not an exploit path.
  • Idle gate. An unreadable updatedAt or committedDate gives NaN, and a comparison with NaN is false, so the PR counts as not idle. A head whose last commit is not the head OID is also treated as not idle.
  • No extra authority downstream.
    • fix-ci still runs through the same read-only model job and apply-patch.
    • The round cap in fix-ci-context is unchanged.
    • pr-run-activity-read.yml runs check-trusted-trigger with pr-number and the trusted-actors file, which the schema description relies on. Every owner id must also be a trusted actor for the run to proceed. I did not read the action itself to confirm it checks the PR author.
    • The pr-merge candidate listing still selects only app/<slug> and Dependabot authors, so this diff does not make owner PRs mergeable. The README and the adoption comment in pr-pipeline.yaml say pr-merge merges adopted owner PRs, and nothing in this diff changes pr-merge to do so.
  • Workflow change. pr-unblock-dispatch.yml has comment-only changes. No triggers, permissions, or expressions changed, so there is nothing for this lane to add on top of zizmor.
  • Logging. The new log lines carry only PR numbers, SHAs and run ids. The PR body is read for the Hold: match and never logged.
  • Instruction surface. The diff deletes or narrows no standing instruction. I did not consult the instruction exception register, because nothing was removed.

Plugin manifests

  • No .claude-plugin/plugin.json or marketplace.json is touched.
  • manifest <field>: no field hit, so there are no pass or flag lines.

No security issues found.

@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Oct 22, 2026
@melodic-automation-lanes melodic-automation-lanes Bot added the needs-human Human-in-the-loop required; autonomous sessions must not resolve items carrying this. label Oct 22, 2026
@melodic-automation-lanes

Copy link
Copy Markdown
Contributor

The pr-unblock lane stopped on this pull request and needs a person: the merge stages a change under .github/workflows/, which the lane does not push.

  • Reason: workflow-staged
  • Resume key: 6a27e3198dcb4522ef0af92b2d93f77d10ed4ab5 (the head the lane stopped on)

To resume, deal with the reason above, remove the needs-human label (and do-not-merge, if set), then dispatch the pr-unblock workflow with pr-number=8997 and activity=resolve-conflicts:

gh workflow run pr-unblock.yml -R melodic-software/claude-code-plugins -f pr-number=8997 -f activity=resolve-conflicts

Conflicts resolved:
- dispatch.mjs: both sides renamed the same retry variable; kept main's name.
- pr-pipeline.yaml, pr-pipeline.schema.json: kept both the adoption and the
  branch-cleanup sections.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton kyle-sexton removed the needs-human Human-in-the-loop required; autonomous sessions must not resolve items carrying this. label Oct 22, 2026
@melodic-automation-lanes melodic-automation-lanes Bot added the needs-human Human-in-the-loop required; autonomous sessions must not resolve items carrying this. label Oct 22, 2026
@melodic-automation-lanes

Copy link
Copy Markdown
Contributor

The pr-unblock lane stopped on this pull request and needs a person: the merge stages a change under .github/workflows/, which the lane does not push.

  • Reason: workflow-staged
  • Resume key: 45448a4642f7891e1158c1e9985d4ae6cc4aa90a (the head the lane stopped on)

To resume, deal with the reason above, remove the needs-human label (and do-not-merge, if set), then dispatch the pr-unblock workflow with pr-number=8997 and activity=resolve-conflicts:

gh workflow run pr-unblock.yml -R melodic-software/claude-code-plugins -f pr-number=8997 -f activity=resolve-conflicts

Conflicts resolved:
- dispatch.mjs: the header comment names both fix-ci adoption and fix-docs;
  the imports keep fix-ci's adoption helpers and add fix-docs-dispatch.
- dispatch-limits.test.mjs: run() takes main's docsPulls third and the
  branch's tracker fourth; the tracker tests pass [] for docsPulls. Both the
  adoption tests and the fix-docs-per-run test kept.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton kyle-sexton removed the needs-human Human-in-the-loop required; autonomous sessions must not resolve items carrying this. label Oct 22, 2026
@kyle-sexton
kyle-sexton added this pull request to the merge queue Oct 22, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to a conflict with the base branch Oct 22, 2026
Conflicts resolved:
- fix-ci-dispatch.mjs: skipReason and dispatchCiFixes take both the
  branch's adoption and main's injectable now.

Semantic fixes the markers did not show:
- The infra-skip re-check main added after readRequiredRun now passes
  adoption, so an adopted owner PR is not rejected there as unlisted.
- fix-ci-dispatch.test.mjs: drop the branch's duplicate NOW (main declares
  the same value) and add an infra-only adopted owner PR case.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Comment thread .github/scripts/pr-unblock/dispatch.mjs Outdated
@melodic-automation-lanes melodic-automation-lanes Bot added the needs-human Human-in-the-loop required; autonomous sessions must not resolve items carrying this. label Oct 22, 2026
@melodic-automation-lanes

Copy link
Copy Markdown
Contributor

The pr-unblock lane stopped on this pull request and needs a person: the merge stages a change under .github/workflows/, which the lane does not push.

  • Reason: workflow-staged
  • Resume key: 03ec90057cddfbe2c8cf5c4c0a2ba605412868f7 (the head the lane stopped on)

To resume, deal with the reason above, remove the needs-human label (and do-not-merge, if set), then dispatch the pr-unblock workflow with pr-number=8997 and activity=resolve-conflicts:

gh workflow run pr-unblock.yml -R melodic-software/claude-code-plugins -f pr-number=8997 -f activity=resolve-conflicts

kyle-sexton and others added 2 commits October 22, 2026 01:05
…owner PRs

A base .work-item-tracker.json that is not JSON stopped fix-ci for every
PR, lanes App PRs included. Now lanes App PRs proceed under the default
hold labels, and each adopted owner PR is skipped with the stated reason;
the dispatcher run still fails so the broken file shows. The tracker file
is not read at all while adoption.owner-ids is empty. While it lists
anyone, the human-gated label holds lanes App PRs too, as documented.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
No conflicts.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton kyle-sexton removed the needs-human Human-in-the-loop required; autonomous sessions must not resolve items carrying this. label Oct 22, 2026
@kyle-sexton
kyle-sexton added this pull request to the merge queue Oct 22, 2026
Merged via the queue into main with commit 0783687 Oct 22, 2026
87 of 90 checks passed
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