Skip to content

feat(source-control): resolve lane-answered outdated threads on owner PRs in the worker tier - #10841

Open
kyle-sexton wants to merge 7 commits into
mainfrom
feat/10763-worker-resolve
Open

kyle-sexton wants to merge 7 commits into
mainfrom
feat/10763-worker-resolve

Conversation

@kyle-sexton

Copy link
Copy Markdown
Member

Refs: #10763

Summary

The worker tier's thread resolver refused every PR authored by your own login (skippedOwnPr), so on owner-login PRs the worker resolved nothing, and the owner resolved lane-answered threads by hand. --own-pr-lane-logins (filled from babysit_lane_logins) lifts that refusal for one kind of thread only: an outdated, bot-opened thread that a lane login other than the opener has already replied to.

Fix

  • babysit_resolve_thread.py: project_thread computes laneAnswered. Every comment is a bot's (a self-login comment counts against it, because on your own PR the self login is the person), and some reply after the opener comes from a lane login other than the opener's. Logins are compared casefolded with [bot] removed. A truncated page fails closed.
  • classify takes own_pr and returns skipped-own-pr-not-lane-answered for any other thread on an own PR. This check comes after the existing human-thread, isOutdated and severity checks, so a CRITICAL or security thread still reports skipped-severity-marked and a person-opened thread still reports skipped-human-thread.
  • main keeps the skippedOwnPr refusal when the flag is absent or empty, refuses the flag outside --autonomous (exit 2), and leaves --independent-resolver refusing own PRs. The summary gains ownPr and skippedOwnPrNotLaneAnswered, and audit records gain own_pr.
  • babysit_gh.fetch_pull_request_author read login from the top of the gh pr view --json author payload, but the login is nested under author. It always returned "", so the own-PR refusal from fix(source-control): refuse autonomous resolve on own PRs #2451 never fired. On owner PRs, --autonomous could already resolve an outdated bot thread the owner had replied to, because a self reply is neutral for botOnly. It now reads the nested login and raises when it is missing, and the resolver reports that as a refusal. As a result, without --own-pr-lane-logins the worker now resolves nothing on owner PRs.
  • Before this change, the CRITICAL exclusion already held under --autonomous: CRITICAL, any security and P0/P1 markers set severityFlagged. That is unchanged. A lane reply is scanned raw, so a lane reply that contains the word "security" also blocks the resolve. That is stricter than the rule needs, and it errs in the safe direction.
  • Docs: the SKILL.md worker row, the config table and the babysit_lane_logins description, a safety.md bullet, the worker command lines in orchestration.md and safety.md, guard-contract rows (one refusal, three predicates, regenerated), and a provider-file pointer for who may resolve a conversation on a person-opened PR (no GitHub behavior is restated).
  • Permissions (criterion 4): no grant is added. The resolve runs under the PR author's own login through the existing wrapper. The host allow rules for that wrapper are prefix wildcards, so the new flag needs no rule change.

Verification

  • bash plugins/source-control/skills/babysit-prs/scripts/engine.test.sh: 3403 tests OK (5 skipped), ruff clean, wrapper checks pass.
  • New OwnPrLaneAnsweredException tests in test_babysit_resolve_thread.py run through the real project_thread. They cover: an answered lane thread is resolved; an unanswered thread is not; a reply from a bot that is not a lane does not count; a lane replying to its own finding does not count; a CRITICAL security-lane thread is skipped-severity-marked; a person-started thread and an owner-started thread are skipped-human-thread; an owner reply inside a bot thread blocks the resolve; a current thread is skipped; without the flag, or with it empty, skippedOwnPr is unchanged; the flag outside --autonomous exits 2.
  • FetchPullRequestAuthorTests in test_babysit_gh.py: the nested login is read, and a missing login raises. The first test fails on the old code, which returned "".
  • Guard-contract rows: resolve.own-pr-lane-logins-requires-autonomous, classify.own-pr-requires-a-lane-answer, classify.own-pr-keeps-the-severity-line, classify.own-pr-lane-answered-is-eligible. reference/guard-contract.md was regenerated.
  • check-skill.sh plugins/source-control/skills: babysit-prs PASS (SKILL.md at 499 lines). markdownlint is clean.
  • Read-only list runs against live PRs:
  • Left out: no pinned --resolve was run against a live thread, because no owner-login PR has a qualifying thread today (see Remaining).

Remaining

Related

🤖 Generated with Claude Code

kyle-sexton and others added 4 commits October 28, 2026 21:38
… PRs in the worker tier

The worker tier's --autonomous resolve refused every PR authored by a self
login, so on owner-login PRs no thread was ever resolved. --own-pr-lane-logins
(from babysit_lane_logins) lifts that refusal narrowly: it admits an outdated,
bot-opened thread that a lane login other than the opener has replied to, with
no comment by a self login. The severity line (CRITICAL, security, P0/P1)
and the person-thread line still hold. Without the flag the refusal is
unchanged.

Refs: #10763

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

fetch_pull_request_author read `login` from the top of the
`gh pr view --json author` payload, where it is nested under `author`.
It always returned an empty login, so --autonomous treated every PR as
someone else's and the own-PR refusal never fired. It now reads the
nested login and raises when it is missing, which the resolver
reports as a refusal.

Refs: #10763

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

Refs: #10763

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@kyle-sexton
kyle-sexton marked this pull request as ready for review October 29, 2026 02:20
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 29, 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-29T02:22:06.431069Z d478b0b 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 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

claude-security-review: 1 inline finding(s) on d478b0b6b515c1bb75885f0ba75cdb05d0d39972; the summary is in this lane's pull request comment.

claude-lane: claude-security-review

@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

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

No security findings.

critical-findings: 0

Scope read. I read the full diff and PR context. The code changes are babysit_resolve_thread.py, babysit_gh.py and babysit_merge_policy.py. babysit_gh.py and babysit_merge_policy.py are trusted paths, so I read their whole changes and what they gate. The remaining files are tests, docs and a changelog. The protected-class register was not consulted because the diff deletes no standing instruction.

Trust-control check (own-PR thread resolve, merge-hold inputs). I found no widening against the base:

  • Author fix. fetch_pull_request_author previously read login from the top level of the payload. It always returned "", so the own-PR refusal never fired on main. Now it reads author.login and raises when the login is missing, and main reports that as a refusal. Own PRs are refused where they were not before, so this narrows.

  • New exception. On an own PR, --autonomous resolves a thread only when all of these hold:

    • every comment is a bot's or a self login's;
    • the opener is a bot;
    • no comment is by a self login (babysit_resolve_thread.py:439-455);
    • the latest reply from a lane login other than the opener opens with the exact LANE_FIXED_HEADER;
    • the page is not truncated.

    classify evaluates it after the human-thread, isOutdated and severity checks, so CRITICAL, security and P0/P1 threads stay with a person.

  • Attacker input. A User-typed account cannot satisfy botOnly, and the lane-reply match needs a bot-authored reply. A person or outside author therefore cannot forge laneAnswered without the lane App posting the header. A lane reply is also scanned raw for severity markers.

  • Mode isolation. The flag is refused outside --autonomous. --independent-resolver and --autonomous are mutually exclusive (babysit_resolve_thread.py:1196-1208), so the exception cannot widen the independent mode.

  • Repository lane identity. With the setting unset, the fallback is merge.agent-identity. lane_identity reads it at the PR's base commit and the default-branch head, and returns a value only when both are set and agree. The PR head is never read. Every read failure returns None, which keeps the refusal. StopReadError subclasses PolicyReadError, which subclasses RuntimeError, so repo_lane_identity catches it. A PR author cannot change the identity without changing reviewed config on the default branch.

  • Self login as lane. If the lane identity were set to the owner's own login, the "no self-login comment" rule still blocks the resolve.

Noted, not a finding. babysit_lane_logins now also feeds the worker's own-PR resolve, as well as the autopilot merge tier. Users who already set it, and users who set nothing and have a repository merge.agent-identity, get the narrow exception. Because the refusal never fired before, the worker could already resolve an own-PR thread the owner had replied to, so the net effect is stricter.

manifest userConfig babysit_lane_logins description: pass (inert field: description only).

Spend: nothing is added. The change uses no new model runs or paid resources. No hold.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

claude-review: 1 inline finding(s) on d478b0b6b515c1bb75885f0ba75cdb05d0d39972; the summary is in this lane's pull request comment.

claude-lane: claude-review

Comment thread plugins/source-control/skills/babysit-prs/scripts/babysit_resolve_thread.py Outdated
@github-actions

github-actions Bot commented Oct 29, 2026 •

Copy link
Copy Markdown
Contributor

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

No blocking or advisory findings on this head (ca6b5f7). I read the whole diff (.claude-lane/pr.diff) and the PR context, and checked these against the source files:

  • normalize_login_set
  • StopReadError
  • _default_branch_config
  • merge.agent-identity in docs/conventions/pr-pipeline.yaml

I did not run the tests. The 3403-test and live-PR results in the PR description are the author's claims and I haven't verified them.

Trusted-path files (babysit_gh.py, babysit_merge_policy.py, babysit_resolve_thread.py): I read each change in full.

  • Own-PR refusal: it keeps its order. The human-thread, isOutdated and severity checks run before skipped-own-pr-not-lane-answered, so a CRITICAL or security thread still reports skipped-severity-marked.

  • laneAnswered: it needs all of these:

    • a bot opener
    • no self-login comment
    • no truncated page
    • the latest non-opener lane reply opening with LANE_FIXED_HEADER

    It is computed from bot_only, which already fails closed on truncation.

  • Flag scope: --own-pr-lane-logins is refused outside --autonomous, and --independent-resolver keeps the own-PR refusal.

  • merge.agent-identity fallback: it reads only the PR's base commit and the default branch head, never the PR head.

    • It returns None when the two differ or either read fails.
    • StopReadError subclasses PolicyReadError, so config-parse failures are caught in lane_identity.
    • The identity in docs/conventions/pr-pipeline.yaml is the lanes App (melodic-automation-lanes[bot]), which matches the live check in the PR description.
  • fetch_pull_request_author: it now reads the nested author.login and raises when it is missing, and main turns that into a refusal. This closes the old case where the empty author made the own-PR refusal never fire.

No critical-security finding. No change that can cause a real charge.

Manifest checks (plugin.json):

  • manifest userConfig default/removal: pass — only a description string changed.
  • manifest userConfig options: pass — none touched.
  • manifest new key reader: pass — babysit_lane_logins is an existing key, now also read by the resolve-thread forms in SKILL.md.
  • manifest version bump: pass — the fragment declares bump: minor for a new capability plus a fix.
  • manifest description: pass — the babysit_lane_logins description now names the worker use.

Closing issues: none. The PR only references #10763, so there are no acceptance criteria to check. The PR body's own "Remaining" section says criterion 1 is unmet on current data. That is a rollout and configuration matter, not a code defect.

Paved paths: I did not resolve the standards index, because this run has no shell and I judged no paved-path row relevant to this change.

Minor, not counted:

  • Stale PR body: it says the worker resolves nothing on owner PRs without --own-pr-lane-logins. The head also falls back to merge.agent-identity, which the changelog fragment and safety.md do describe. Update the PR body so the merge record matches.
  • Spacing in babysit_merge_policy.py: lane_identity has no blank line before it and extra blank lines after it, and the new LaneIdentity test class has one blank line before if __name__. The formatter or linter will catch these.

kyle-sexton and others added 3 commits October 28, 2026 23:16
… lane logins

With babysit_lane_logins unset, the worker's own-PR resolve takes its lane
login from merge.agent-identity in docs/conventions/pr-pipeline.yaml, read
at the PR's base commit and the default branch head and used only when both
declare the same one. It is never read from the PR head. A person merges
that file, so a repository value that widens what the worker resolves stays
operator-reviewed (ADR 0039). The setting wins when set; with neither, the
own-PR refusal holds.

Refs: #10763

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

laneAnswered held for any reply from a lane login, but address-feedback
also posts INCORRECT, UNCERTAIN and VALID (defer) replies that leave the
thread open. The latest lane reply must now open with the fixed
disposition header renderReply writes, so a left-open thread stays with a
person.

Refs: #10763

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

This branch has not been deployed

No deployments
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