fix(source-control): don't let a worker's own bot-thread reply strand it outside every resolution scope - #1731
Conversation
… it outside every resolution scope babysit_resolve_thread.py's botOnly classifier inspected every fetched comment, so the worker's own documented reply to a bot thread (a classification reply, a `Fixed in <sha>` follow-up) was indistinguishable from a genuine third-party human joining -- botOnly flipped false the moment it posted, and since --include-human is unset by design in worker/safe modes, nothing lifted it back in. A bot thread the worker correctly replied to became permanently unresolvable. Add --self-logins (mirroring babysit_merge.py's existing flag and the already-configured but never-threaded babysit_self_logins userConfig key) so a self-authored comment is a third admissible authorship: neutral for botOnly, not sufficient to earn it alone. Also fixes the same-shaped bug one level deeper: the mandated classification-reply table restates the finding's own severity marker, so the --autonomous severity guard would have re-stranded the same thread under skipped-severity-marked the moment botOnly stopped blocking it. Self-authored classification-table rows (not whole bodies) are now stripped before that scan too, reusing babysit_classify's existing count_findings precedent instead of a second implementation. Fixes #1729 Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
|
Claude encountered an error after 1s —— View job I'll analyze this and get back to you. |
|
Claude encountered an error after 0s —— View job I'll analyze this and get back to you. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: d4a618c9ba
ℹ️ 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".
|
Claude finished @kyle-sexton's task in 3m 15s —— View job PR Review
SummaryThe core fix is correct and well-engineered. The stranding bug is real, the two-part fix (botOnly + severity scan) is exactly right, test coverage is thorough (11 targeted regressions), and every documented command form is updated consistently. One semantic gap in the new Finding:
|
|
Claude finished @kyle-sexton's task in 4m 50s —— View job Security ReviewTasks
One confirmed security issue. No command-injection, SQL-injection, credential-leak, or supply-chain issues found in the changed files. The IMPORTANT —
|
…here
`--self-logins` neutralized the caller's own posting identity so a
worker's reply to a bot thread would stop stranding it. But the
predicate it neutralized against was "some participant is a bot AND
none is a third-party human", which that widening turned into a hole
in the opposite direction: a SELF-OPENED thread joined by a bot has
every comment bot-or-self, so it reported `botOnly` true and became
resolvable with no `--include-human` -- and, once outdated, under
`--autonomous`.
`reference/review-discipline.md` D7.5 is the canonical policy and says
otherwise: "Resolve ONLY threads whose OPENING comment is authored by a
BOT reviewer... NEVER resolve your OWN threads... Thread author = login
of the THREAD-OPENING comment (replying into it does not change the
author)."
`project_thread`'s `botOnly` now requires the opening comment to be
bot-authored, in addition to the existing all-participants test:
- and any(_author_is_bot(c) for c in comments)
+ and bool(comments)
+ and _author_is_bot(comments[0])
That is the same opening-author test `humanThreadsActed` has applied
since #512, so both `is_bot` call sites now agree on what makes a
thread a bot's. It is strictly stronger than the `any(...)` clause it
replaces, so the stranding fix is untouched: a bot-OPENED thread with a
later self reply is still `botOnly`.
Fails closed with no extra code when the opener cannot be attributed --
no fetched comments, or an opener whose author the API withheld (a
deleted account lands on `is_bot(None, None)` -> False). Truncation
remains an independent fail-closed guard on top.
`classify`'s truth table is unchanged (`botOnly` is an input to it), so
`reference/guard-contract.md` needs no row change; re-running
`tests/guard_contract.py --emit` produces a zero-byte diff.
Tests: 453 -> 462. `BotOnlyRequiresABotOpener` covers the full matrix --
bot-opened+self-reply TRUE, self-opened+bot-reply FALSE,
bot-opened+third-party FALSE, bot-opened-alone TRUE, no-comments FALSE,
null-author opener FALSE, truncated FALSE -- plus the exploit path end
to end through the real projection under `--autonomous --only-outdated`.
Every prose surface asserting the old predicate is corrected: the module
docstring (which is the argparse description), `project_thread`'s
docstring, `classify`'s inline comment, `reference/safety.md`, and this
release's own unreleased CHANGELOG entry.
Conflicts resolved in the source-control plugin's version surfaces. Main shipped 0.34.0 and its own 0.33.3 entry (#1642) while this branch held a 0.33.3 entry of its own, so this branch's entry is renumbered to 0.34.1 and plugin.json follows. The babysit_self_logins description keeps this branch's resolve-thread wording. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TkLLDcB8Ht2tm3j688F5ZP
|
Claude finished @kyle-sexton's task in 1m 19s —— View job PR Review
SummaryThe Codex P1 finding, confirmed independently by both prior Claude reviews, has been addressed in commit Previous finding: resolved
bot_only = (
not truncated
and bool(comments)
and _author_is_bot(comments[0]) # opener must be bot-authored
and all(_author_is_bot(c) or _author_is_self(c) for c in comments)
)The Test coverage for
Everything else: correctNo new issues found in the additional commit. The primary fix ( |
|
Claude finished @kyle-sexton's task in 2m 55s —— View job Security Review
The Prior Critical Finding Has Been ResolvedThe Codex P1 finding and both earlier Claude passes (commits bot_only = (
not truncated
and bool(comments)
and _author_is_bot(comments[0]) # opener must be bot-authored
and all(_author_is_bot(c) or _author_is_self(c) for c in comments)
)The No Additional Security Issues FoundI reviewed all changed files for the vulnerability classes this lane covers. Findings for each surface:
API data trust boundary. Self-login impersonation.
Severity scan stripping scope. Stripping is limited to classification-table rows (lines matching Double-membership edge case ( No security issues to report. |
…rkers consumed markdownlint MD032/MD022 -- the merge resolution left the 0.34.1 entry's last list item abutting the `## [0.34.0]` heading. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TkLLDcB8Ht2tm3j688F5ZP
|
Claude finished @kyle-sexton's task in 1m 24s —— View job PR Review
SummaryThe latest commit ( Status of prior findings
All three findings are resolved. No new issues introduced. VerdictThe PR is correct and ready to merge. The fix is sound, test coverage is thorough (453 tests, 11 targeted regressions covering the exact failure modes), documentation is fully updated, and all CI gates ( |
|
Claude finished @kyle-sexton's task in 3m 16s —— View job Security Review
Prior Critical Finding: ResolvedThe prior critical finding — bot_only = (
not truncated
and bool(comments)
and _author_is_bot(comments[0]) # opener must be bot-authored
and all(_author_is_bot(c) or _author_is_self(c) for c in comments)
)The only change between the last reviewed HEAD ( No Additional Security Issues FoundI reviewed all 8 changed files against the vulnerability classes this lane covers.
API data trust boundary. Self-login impersonation. Severity scan strip scope. Stripping is gated on Double-membership edge case ( No security issues to report. |
Conflicts resolved in the source-control plugin's version surfaces. Main now carries 0.34.1 (#1731), so this branch's entry is renumbered from 0.33.3 to 0.34.2 and plugin.json follows. plugin.json's babysit_self_logins description takes main's #1731 wording, which this branch does not touch. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TkLLDcB8Ht2tm3j688F5ZP
safety.md's guarded-wrapper list takes BOTH sides' new bullets: this branch's review-settle pair and main's --self-logins resolve-thread flag (#1731). Version surfaces renumber this branch's entry from 0.34.0 to 0.37.0 over main's 0.36.0. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01TkLLDcB8Ht2tm3j688F5ZP
Summary
Fixes #1729.
babysit_resolve_thread.py's thread-resolution-scope classifier (project_thread'sbotOnlycomputation) inspects every fetched comment in a review thread, not just the opener, to decidebotOnly-- correct for catching a genuine human reply. But the worker's own documented reply to a bot thread (a classification reply, aFixed in <sha>follow-up --reference/orchestration.md) is also a real API comment, posted under the worker's own login, not the bot's.Before this fix: replying to a bot thread flipped
botOnlyfalse the moment the reply posted (indistinguishable from a genuine third-party human joining).classify()'s bright line then fires --if not thread["botOnly"] and not include_human: return "skipped-human-thread"-- and--include-humanis unset by design in worker/safe modes (must never touch a genuine human thread), so nothing lifts it back in. The thread is permanently stranded outside BOTH resolution scopes, even though replying to it was exactly the correct, expected action.Fix
--self-logins(new flag, mirroringbabysit_merge.py's existing flag of the same name, and the already-configured-but-never-threadedbabysit_self_loginsuserConfig key).project_thread'sbotOnlynow treats a self-login-authored comment as a third admissible authorship alongside bot and third-party human: neutral forbotOnly(does not disqualify it, unlike a genuine human reply) but not sufficient to earn it alone -- a thread still needs at least one ACTUAL bot comment to bebotOnly.botOnlyalone isn't enough. The mandated classification-reply table (reference/review-discipline.md) restates the source finding's own severity marker (e.g. aCRITICALcolumn, or "VALID -- not a security concern") as part of the worker's own reply -- so a raw severity scan over that self-reply would re-trip--autonomous'sskipped-severity-markedguard the instantbotOnlystopped blocking it, just moving the stranding to a different verdict. The severity scan now strips a self-authored comment's classification-table rows (not its whole body) before scanning, reusingbabysit_classify.count_findings's identical existing rule (the helper is promoted from private to shared rather than reimplemented) -- non-table self content still flags.SKILL.md,reference/safety.mdx3,reference/orchestration.mdx2) now carries--self-logins @me,<self-logins>alongside--extra-bot-logins, verified by this repo's owntest_every_documented_wrapper_commanddoc-parser-conformance gate.plugin.json'sbabysit_self_loginsdescription now names the resolve-thread bot-only test among the surfaces the self set covers.Test plan
python -m unittest discoveroverplugins/source-control/skills/babysit-prs/scripts/tests-- 453 tests, all green (includes 11 new regression tests: the bug reproduced without--self-logins, the fix proven with it, a self-only thread still correctly notbotOnly, a genuine third-party human still blockingbotOnlyregardless of--self-logins, truncation still failing closed, the severity-scan re-stranding scenario reproduced and fixed, non-table self content still flagging, third-party severity markers still flagging, and a full end-to-end--autonomous --only-outdatedrun in the exact tier the bug occurs in).test_guards.py's guard-contract suite (includingtest_reference_doc_matches_the_tablesandtest_every_documented_wrapper_command) -- green, noguard_contract.pychanges needed sinceclassify()'s predicate contract (which takesbotOnlyas an input) is unchanged.plugins/skill-quality/scripts/check-skill.sh babysit-prs-- PASS, 0 errors (SKILL.md at 497/500 lines).scripts/check-changelog-parity.sh --check-- passes.markdownlint-cli2over every changed.mdfile -- 0 issues.Related
--autonomousresolve path: the per-threadcomment pins do not stop a worker's own push from displacing a thread. This PR fixes the
authorship-classification half of the stranding; Autonomous thread-resolve: pin pre-push isOutdated state (per-thread comment pins don't stop own-push displacement) #571 tracks the
isOutdatedpinning half.classified, the same identity-classification family as
--self-loginshere.🤖 Generated with Claude Code