Skip to content

fix(source-control): babysit snapshot classifies Approve-with-nits bot review as non-blocking (#499)#567

Merged
kyle-sexton merged 6 commits into
mainfrom
fix/499-babysit-snapshot-approve-nits-classification
Jul 20, 2026
Merged

fix(source-control): babysit snapshot classifies Approve-with-nits bot review as non-blocking (#499)#567
kyle-sexton merged 6 commits into
mainfrom
fix/499-babysit-snapshot-approve-nits-classification

Conversation

@kyle-sexton

@kyle-sexton kyle-sexton commented Jul 19, 2026

Copy link
Copy Markdown
Contributor

Summary

Babysit snapshot misclassified an Approve-with-nits bot review: a clean approval delivered as an issue comment (the #499 claude[bot] shape) was treated as blocking feedback, stalling merge-ready classification. This PR makes the approval downgrade structural and narrows every doc surface to the code's actual behavior. Composed atop the #606 attribution-drift arm (source-control 0.11.0) as 0.12.0.

Fix

Verification

Closes #499

Related

…cking

The babysit-prs snapshot classified a claude[bot] Approve verdict whose only
findings were self-deprioritized nits as a blocking bot-feedback item, because
the review body's prose contains the word "blocking" ("blocking criteria",
"blocking checks", "No blocking issues") which the text heuristic matched. The
snapshot disagreed with babysit-readiness-gate.sh, which reported findings=0 for
the same review.

Parse the verdict and severity markers: an explicit approval carrying no
CRITICAL/IMPORTANT or required-fix marker is downgraded structurally (for any
bot) to a non-blocking result, consistent with the gate. CRITICAL/IMPORTANT are
now recognized as blocking-severity markers and a Request-changes verdict is
detected, so genuinely blocking feedback still classifies as blocking. A login
named in approval_downgrade_logins opts its clean approvals into the material
bucket instead of ignored.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011V31qpAHP3jfs76B9d5Rfo
@kyle-sexton kyle-sexton added the automated Opened by automation. label Jul 19, 2026
@claude

claude Bot commented Jul 19, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 31s —— View job


PR Review — fix(source-control): Approve-with-nits classification

  • Gather context from PR description and changed files
  • Review babysit_feedback.py — core logic change
  • Review pr_queue_snapshot.py — classification change
  • Review test files for coverage and correctness
  • Review docs/version changes
  • Post review findings

The fix is well-reasoned and the implementation is clean. The behavioral change (structural approval downgrade for all bots, not just a configured allowlist) is the right direction and doesn't weaken blocker detection — it tightens it. A few notes:

Core logic (babysit_feedback.py)

BLOCKING_SEVERITY_RE + has_blocking_severity — case-sensitive, whole-word CRITICAL|IMPORTANT with the same negation-redaction wrapper as has_blocking_text. The case sensitivity rationale in the comment is correct and matches the gate's vocabulary.

approval_downgrade guard — the added BLOCKING_SEVERITY_RE check at line 272 correctly ensures "Verdict: Approve, but CRITICAL: hardcoded secret" stays blocking. This prevents the downgrade path from being exploited by a mixed approval.

Structural approval downgrade — moving the downgrade from login-gated to structural (with login as opt-in for the more-conservative material bucket) is the right semantic model. The inline comment explaining the reasoning is appropriately detailed for future maintainers.

request(?:s|ing)? changes in BLOCKING_TEXT_RE — this closes an active-voice gap that was already covered in REQUIRED_FIX_RE but missing from BLOCKING_TEXT_RE. Consistent now.

Known limitation — negated CRITICAL/IMPORTANT not redacted

NEGATED_BLOCKING_TERM_RE covers no blocking, no P0/P1/P2, etc. but does not cover no CRITICAL/IMPORTANT. So a phrase like "No CRITICAL issues found. Verdict: Approve." would:

  1. Pass has_blocking_severity as True (CRITICAL survives redaction)
  2. Enter the blocking branch
  3. Fail approval_downgrade at the new BLOCKING_SEVERITY_RE guard
  4. Land in blocking

This is a false positive — conservative direction (forces human review) rather than a miss, so it's safe. It also didn't exist in the pre-PR corpus (the real review body from #492 contains no CRITICAL/IMPORTANT tokens at all). Worth tracking as a follow-up if you see bots writing "No CRITICAL issues" in approval bodies. See has_blocking_severity at line 245 — this is where you'd add (?:no|zero|without)\s+(?:CRITICAL|IMPORTANT) to the redaction if it surfaces.

Tests (test_babysit_feedback.py, test_pr_queue_snapshot.py)

Coverage is thorough and the right scenarios are tested:

The ApproveWithNitsClassification end-to-end tests in test_pr_queue_snapshot.py drive the fix through classify_pr and assert both needs_worker=False and pr_clean_ready_for_direct_gate=True — directly encoding the gate/snapshot consistency invariant that was broken. The APPROVE_WITH_NITS_BODY fixture preserving all four "blocking" occurrences from the real #492 review is the right regression-test strategy.

pr_queue_snapshot.py CLI help

The updated --approval-downgrade-logins help text accurately reflects the new opt-in-to-material semantics. Line 512 is clear.

Docs / version

  • CHANGELOG entry is accurate and complete.
  • Version bump 0.9.3 → 0.9.4 is correct for a patch bug fix.
  • README.md + SKILL.md config table entries reflect the new default behavior correctly.

Summary: No blocking issues. The logic is correct, test coverage is strong, and the docs are consistent. The negated-CRITICAL limitation is worth a follow-up if it surfaces in practice but is not a regression introduced here — conservative direction is fine. Ready to merge.

The comment-hygiene check flagged an owner/repo#N issue reference
(melodic-software/claude-code-plugins-#492) in the test-fixture comment,
which the org policy bans in code comments. The workflow run id already
pins the fixture's provenance, so the issue reference was redundant
coupling; reword to reference the run alone and reflow the comment.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown

PR Review — fix(source-control): Approve-with-nits classification

  • Gather context from PR description and changed files
  • Review babysit_feedback.py — core logic changes
  • Review pr_queue_snapshot.py — CLI help updates
  • Review test files for coverage and correctness
  • Review docs/version changes
  • Post review findings

The fix is correct and well-scoped. The behavioral change (structural approval downgrade for all bots) makes the snapshot agree with the gate on the exact input from the issue, and the CRITICAL/IMPORTANT guard in approval_downgrade strengthens (not weakens) genuine-blocker detection. No blocking issues.

Core logic (babysit_feedback.py)

BLOCKING_SEVERITY_RE placement — the new regex is defined directly after REQUIRED_FIX_RE at line 73, adjacent to the regexes it conceptually belongs with. The case-sensitivity rationale in the comment is accurate — uppercase CRITICAL/IMPORTANT are structured severity labels; lowercase forms appear constantly in prose.

has_blocking_severity — correctly reuses the same two-step negation-redaction pipeline as has_blocking_text before applying BLOCKING_SEVERITY_RE. The parallel structure between the two helpers is clean and easy to audit.

approval_downgrade guard — the BLOCKING_SEVERITY_RE.search(redacted) check at line 272 runs on the same redacted string used for the subsequent REQUIRED_FIX_RE check. This means one redaction pass covers both checks — slightly more efficient than calling has_blocking_severity(text) (which would re-run the two-step pipeline) and worth the apparent duplication.

Structural approval downgrade (lines 370–386) — the new branching logic is correct:

  • approval_downgrade(text)True: routes to material (configured login) or ignored (all others), with downgrade = "approval_verdict" set in both cases
  • The inline comment fully explains the rationale; appropriate depth for a change to a non-obvious heuristic

BLOCKING_TEXT_RE additionrequest(?:s|ing)? changes closes the active-voice gap that existed between BLOCKING_TEXT_RE and REQUIRED_FIX_RE. Now "Verdict: Request changes" routes correctly in both the fast text path and the required-fix path.

Known limitation (pre-existing, conservative direction)

NEGATED_BLOCKING_TERM_RE does not cover the pattern no CRITICAL/IMPORTANT, so "No CRITICAL issues found. Verdict: Approve." would pass has_blocking_severity → True, fail the guard in approval_downgrade, and land in blocking. This is a false positive (conservative — forces human review) and not a regression introduced here; the real #492 review body contained no CRITICAL/IMPORTANT tokens at all. Already documented in the prior review; tracking it here for completeness. If it surfaces, the fix is a (?:no|zero|without)\s+(?:CRITICAL|IMPORTANT) addition to the redaction.

Tests (test_babysit_feedback.py, test_pr_queue_snapshot.py)

Coverage is thorough and the right scenarios are exercised:

Scenario Test Asserts
Approve + descriptive "blocking" prose → ignored test_bot_approval_with_descriptive_blocking_prose_is_ignored blocking=[], material=[], ignored[0]["downgrade"]="approval_verdict"
Configured login → material test_configured_login_surfaces_clean_approval_as_material material[0]["downgrade"]="approval_verdict"
Approval + CRITICAL → stays blocking test_approval_verdict_with_critical_marker_stays_blocking blocking has 1 item
"Request changes" verdict → stays blocking test_request_changes_verdict_is_blocking blocking has 1 item
has_blocking_severity case sensitivity test_has_blocking_severity_matches_only_uppercase_markers uppercase hits, lowercase doesn't, SUGGESTION excluded

The ApproveWithNitsClassification class in test_pr_queue_snapshot.py drives the fix end-to-end through delta.classify_pr with APPROVE_WITH_NITS_BODY — a faithful abbreviation of the real #492 review preserving all four "blocking" occurrences that caused the misfiring. Asserting both needs_worker=False and pr_clean_ready_for_direct_gate=True directly encodes the gate/snapshot consistency invariant.

Minor gap: test_approve_with_only_nits_is_not_blocking asserts material=[] and blocking=[] but doesn't assert that the item lands in feedback["ignored"]. The unit test in test_babysit_feedback.py covers the ignored bucket, so it's not a correctness hole — but adding self.assertEqual(len(result["feedback"]["ignored"]), 1) to the integration test would tighten the end-to-end invariant. Fix this →

CLI help / docs / version

  • --approval-downgrade-logins help at line 512 accurately reflects the opt-in-to-material semantics.
  • FeedbackConfig docstring, README.md, SKILL.md config table all updated consistently.
  • CHANGELOG entry is complete and accurate.
  • Version bump 0.9.3 → 0.9.4 is correct for a patch bug fix.

Summary: No blocking issues. The logic is correct, test coverage is strong, and docs are consistent. The one minor test gap (asserting the ignored bucket in the integration test) is worth a follow-up but not a blocker. Ready to merge.

View job run

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Post-green review pass (work lane)

Finding Classification Disposition
Integration test doesn't assert the ignored bucket for the Approve-with-nits path (unit test covers it, so no correctness hole) Valid, deferred (test-coverage nit, not blocking) Filed #578

Bot review concluded "no blocking issues... ready to merge." Moving this out of draft. The pre-existing NEGATED_BLOCKING_TERM_RE gap for "no CRITICAL/IMPORTANT" phrasing noted in the review is explicitly called out as pre-existing and conservative (forces human review, not a false negative) — not introduced by this PR, no action needed here.

🤖 Generated with Claude Code

@kyle-sexton
kyle-sexton marked this pull request as ready for review July 20, 2026 02:02
@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 35s —— View job


PR Review — fix(source-control): Approve-with-nits classification

  • Gather context from PR description and changed files
  • Review babysit_feedback.py — core logic changes
  • Review pr_queue_snapshot.py — CLI help updates
  • Review test files for coverage and correctness
  • Review docs/version changes
  • Post review findings

Two prior bot reviews have already covered this in detail and both concluded "no blocking issues — ready to merge." I've read all eight changed files and the diff independently. My findings agree.

Core logic (babysit_feedback.py)

Root cause and fix are correct. The old approval downgrade was login-gated (approval_downgrade_logins ships empty), so claude's clean approval landed in blocking. The fix makes the downgrade structural — any bot with a clean approval is downgraded — while keeping approval_downgrade_logins as an opt-in to the more-conservative material bucket. The semantics are correctly inverted: default is ignored, named logins opt in to material.

BLOCKING_SEVERITY_RE guard in approval_downgrade (line 272) reuses the already-computed redacted string rather than calling has_blocking_severity(text), which avoids re-running the two-step negation pipeline. Correct and efficient.

has_blocking_severity + collect_feedback guard (line 359) closes a prior under-detection: a bot comment containing only CRITICAL/IMPORTANT with no imperative BLOCKING_TEXT_RE terms previously fell through to ignored. Now it's caught.

request(?:s|ing)? changes in BLOCKING_TEXT_RE — aligns the text path with REQUIRED_FIX_RE, which already had this pattern. Active-voice "Verdict: Request changes" now routes blocking in both paths.

End-to-end trace for the #499 scenario:

  1. has_blocking_text("...blocking criteria...No blocking issues...") → True (word "blocking" survives redaction)
  2. Enter elif has_blocking_text(text) or has_blocking_severity(text) branch
  3. approval_downgrade(text) → True (Approve verdict, CRITICAL/IMPORTANT absent, no required-fix term)
  4. normalized_bot_login(item) ("claude") not in approval_downgrade_logins (empty) → ignored.append(record)

Known limitation (pre-existing, conservative direction)

NEGATED_BLOCKING_TERM_RE doesn't cover "no CRITICAL" / "no IMPORTANT". A review body like "No CRITICAL issues found. Verdict: Approve." would: pass has_blocking_severity → True, fail approval_downgrade's guard, land in blocking — a false positive. This is safe (forces human review, not a false negative) and was not in the pre-PR corpus (the real #492 body contains no CRITICAL/IMPORTANT tokens). If it surfaces, the fix is adding (?:no|zero|without)\s+(?:CRITICAL|IMPORTANT) to the redaction set. Already documented by prior reviews; tracked for follow-up.

Tests

Coverage is thorough. The APPROVE_WITH_NITS_BODY fixture faithfully preserves all four "blocking" occurrences from the real #492 review — the right regression-test strategy. All five core scenarios are covered:

Scenario File Asserts
Approve + descriptive "blocking" prose → ignored test_babysit_feedback.py blocking=[], material=[], ignored[0]["downgrade"]="approval_verdict"
Configured login → material test_babysit_feedback.py material[0]["downgrade"]="approval_verdict"
Approval + CRITICAL → stays blocking test_babysit_feedback.py len(blocking)=1
"Request changes" verdict → stays blocking test_babysit_feedback.py len(blocking)=1
has_blocking_severity case-sensitivity test_babysit_feedback.py uppercase hits, lowercase/SUGGESTION excluded
End-to-end through classify_pr test_pr_queue_snapshot.py needs_worker=False, pr_clean_ready_for_direct_gate=True

Minor gap (filed as #578): test_approve_with_only_nits_is_not_blocking asserts blocking=[] and material=[] but doesn't assert that the item lands in feedback["ignored"]. The unit test in test_babysit_feedback.py covers the ignored bucket directly, so there's no correctness hole — but the integration test could be tightened. Not a blocker. Fix this →

Docs / version

  • CHANGELOG entry accurate and complete
  • FeedbackConfig docstring, README.md, SKILL.md config table all consistent with the new structural-downgrade semantics
  • CLI help at line 512 reflects the opt-in-to-material semantics correctly
  • Version bump 0.9.3 → 0.9.4 correct for a patch bug fix

No blocking issues. The fix is correct, the logic is sound, test coverage is strong, and the docs are consistent. The integration-test gap is a nit filed as #578, not a blocker. Ready to merge.

@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: 44ac67b66d

ℹ️ 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".

…y blocking

The new structured-severity blocking path introduced in this PR
(BLOCKING_SEVERITY_RE / has_blocking_severity) re-created a false blocker for a
common clean-approval phrasing: a bot review concluding "No CRITICAL or
IMPORTANT findings" survived redaction and classified as blocking, and
approval_downgrade's guard then rejected the approval. This is the structured-
marker analogue of the already-handled "no P1/P2 issues" case.

Add NEGATED_SEVERITY_MARKER_RE (list-aware, severity tokens case-sensitive to
match BLOCKING_SEVERITY_RE; negator and noun case-insensitive) and apply it in
the redaction pipeline of both has_blocking_severity and approval_downgrade. A
live CRITICAL/IMPORTANT marker not preceded by no/zero/without still blocks.

Addresses chatgpt-codex-connector P2 "Redact negated severity markers before
blocking" (#567 review). Full unit + integration suite green (227 tests).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 32s —— View job


PR Review — fix(source-control): Approve-with-nits classification (post-Codex pass)

  • Gather context from PR description, comments, and Codex findings
  • Read babysit_feedback.py — full diff across all 3 commits
  • Verify Codex P2 findings against current HEAD (c86fc59)
  • Review test coverage for new scenarios
  • Post findings

This is a focused re-review covering the two Codex P2 findings and the latest commit (c86fc59) that addressed one of them. Prior reviews confirmed the core logic and test coverage were correct; I'm not re-treading that ground.

Codex P2 #1 — "Redact negated severity markers before blocking" ✅ RESOLVED

NEGATED_SEVERITY_MARKER_RE in the latest commit correctly handles the false-blocker case where a clean approval states "No CRITICAL or IMPORTANT findings". The implementation is sound:

  • Inline (?i:...) on the negator/noun while leaving (?:CRITICAL|IMPORTANT) case-sensitive — exactly right, matching the rationale in BLOCKING_SEVERITY_RE's comment
  • Runs before BLOCKING_SEVERITY_RE in both has_blocking_severity (line 267) and approval_downgrade (line 285) — consistent two-step pipeline
  • New tests in test_babysit_feedback.py verify "No CRITICAL or IMPORTANT findings."has_blocking_severity=False, "No CRITICAL issues found."False, "CRITICAL: null deref remains"True, and approval_downgrade with negated conclusion → True

CHANGELOG entry is accurate. This finding is closed.

Codex P2 #2 — "Preserve approved P2 feedback as material" ⚠️ UNRESOLVED — regression

This finding is not addressed in any of the 3 commits and is a correctness regression introduced by the structural approval-downgrade change.

Trace for "Verdict: Approve. [P2] Missing timeout." (bot comment):

  1. has_blocking_text(text)Truep2 matches BLOCKING_TEXT_RE (case-insensitive)
  2. Enters elif has_blocking_text(text) or has_blocking_severity(text): branch (line 374)
  3. approval_downgrade(text)TrueAPPROVAL_VERDICT_RE matches "Approve"; after redaction, BLOCKING_SEVERITY_RE is absent (no CRITICAL/IMPORTANT); REQUIRED_FIX_RE contains p0 and p1 but not p2, so it also doesn't fire
  4. Login not in approval_downgrade_logins (ships empty) → ignored.append(record)

Result: a real P2 advisory item is silently suppressed.

Pre-PR baseline for the same input:

  • has_blocking_text → True → enters blocking branch
  • OLD approval downgrade was normalized_bot_login(item) in approval_downgrade_logins and approval_downgrade(text) — login not in empty set → skipped
  • Falls to else: blocking.append(record)blocking

This is a regression: items classified as blocking before this PR are now ignored. The PR description explicitly excludes P0/P1 from downgrade ("a bot review that raises a genuine high-severity finding is blocking even when...") but P2 — which the gate vocabulary also counts as a finding ([P0-3] per the PR description) — is silently dropped.

There is no test for this scenario in either test_babysit_feedback.py or test_pr_queue_snapshot.py.

Fix: Add p2 to REQUIRED_FIX_RE (mirroring the gap between BLOCKING_TEXT_RE and REQUIRED_FIX_RE). That makes approval_downgrade return False for bodies containing [P2], routing the item back to blocking — conservative, consistent with pre-PR behavior. A follow-up could improve this to route P2-under-approval to material (advisory-surfaced-but-non-blocking) rather than blocking, which would be the ideal final state.

Alternatively: the regression can be explicitly called out as a known behavior change in the CHANGELOG and a follow-up issue filed — but given that the Codex finding was already raised and the pre-PR baseline was blocking, treating this as a known regression without a fix here would be a policy call. Fix this →


Summary: Codex P2 #1 (negated severity markers) is cleanly resolved by the latest commit. Codex P2 #2 (P2 advisory under bot approval → ignored) is a correctness regression from the structural downgrade change — the specific item p2 sits in BLOCKING_TEXT_RE but is absent from REQUIRED_FIX_RE, so approval_downgrade fires when it shouldn't and the item is silently suppressed. This is a blocker: a real advisory finding that was previously surfaced is now dropped without surfacing.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Triage pass — reconciling the bot reviews

The three claude[bot] review rounds all concluded Approve / no blocking issues, and I agree — no blocking findings. Two non-blocking items they raised are reconciled here, plus the two Codex P2 inline threads (replied in-thread).

# Source Finding Classification Disposition
1 claude[bot] × all rounds Negated CRITICAL/IMPORTANT (No CRITICAL or IMPORTANT findings) not redacted → clean approval false-blocks VALID — fixed (reclassified from my earlier "pre-existing, no action") Fixed in c86fc59. The reviews framed this as pre-existing; it is not — the severity-marker blocking path is new in this PR, so the false blocker is newly introduced. Codex's line-255 thread makes the same point.
2 claude[bot] × rounds 2–3 (#578) Integration test doesn't assert the ignored bucket for the Approve-with-nits path VALID — non-actionable at this layer The suggested assertion result["feedback"]["ignored"] cannot be added: classify_pr's feedback dict exposes only blocking/material/human_blocking/human — the ignored bucket is not surfaced at the integration layer. The unit test in test_babysit_feedback.py is the only layer that can observe it, and already does. #578's specific suggestion doesn't fit; the end-to-end invariant is already pinned via blocking=[], material=[], needs_worker=False, pr_clean_ready_for_direct_gate=True.
3 chatgpt-codex-connector[bot] (line 255) Redact negated severity markers before blocking VALID — fixed c86fc59; see in-thread reply.
4 chatgpt-codex-connector[bot] (line 390) Preserve approved P2 feedback as material VALID — deferred (author policy) Not fixed this cycle; see in-thread reply. Routing a live P2 under an approval to material vs ignored is a policy decision (no code-backed P2→material rule exists today) that trades against this PR's own thesis — left for the author. Follow-up needed.

Correcting my earlier "Post-green review pass" note: the negated-CRITICAL/IMPORTANT gap is not "pre-existing, no action needed here" — it is introduced by this PR's new severity path and is now fixed in c86fc59.

@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: c86fc59495

ℹ️ 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".

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Follow-up tracking for the two design-decision findings left open on this PR's live re-review pass (worker: babysit lane, 2026-07-20):

# Finding Classification Follow-up
1 Codex P2 (line 390) "preserve approved P2 as material" VALID (defer) #594
2 Codex P2 "surface configured-login clean approvals as material" VALID (defer) #595

Both are author-owned policy/contract calls, not mechanical fixes — tracked issues filed, threads left open pending that decision.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Config decision logged on #499: KEEP babysit_approval_downgrade_logins (Option 1), conditioned on a docs-accuracy fix. The code path stands; the doc surfaces need tightening before merge. (Control-tower decide-and-log, 2026-07-20. A tower worker is being dispatched to execute the items below.)

Remaining to unblock merge:

  1. Thread 2 (docs overstate the knob) — fix required. Adopt the thread's resolution (b): narrow every doc surface to the code's actual reach (fires only for an approval carrying blocking-looking prose; never for an actual APPROVED-state review or a plain clean approval). Surfaces: README config cell, SKILL config cell, the FeedbackConfig docstring, the --approval-downgrade-logins help, the CHANGELOG entry — and the plugin.json userConfig description, which this PR did not touch and still describes the pre-PR behavior. Then resolve the thread.
  2. Thread 1 (advisory P2 under approval → ignored) — formally resolve, not just leave open. The babysit merge gate counts ANY unresolved thread as a blocker. Spin the P2-routing decision out to its own issue and resolve this thread with that link — substance already deferred by the author; only thread state needs closing.
  3. Branch out of sync with main (mergeStateStatus: DIRTY). Update and resolve conflicts — note main's source-control plugin is now 0.11.0 (PR feat(source-control): surface babysit bot-to-personal identity fallback as an attribution-drift finding (#450) #606 merged 04:57Z), so expect plugin.json/CHANGELOG conflicts.
  4. Version bump — DECIDED: minor, from CURRENT main → 0.12.0. This PR changes a userConfig key's semantics; repo convention treats behavior/contract changes as minor. (Patch was defensible pre-1.0; minor is the convention-consistent call. Veto reversible.)

…arrow approval-downgrade docs; bump 0.12.0

Composes main's source-control 0.11.0 (#606 attribution_drift arm) with
this PR's Approve-with-nits classification fix — both sides preserved.

Conflict resolutions:
- plugin.json: version 0.9.4/0.11.0 -> 0.12.0 (minor from current main;
  this PR changes a userConfig key's semantics, and repo convention treats
  behavior/contract changes as minor). userConfig kept both #606's
  babysit_intended_write_identity and this PR's approval-downgrade keys.
- CHANGELOG.md: this PR's entry re-headed [0.9.4] -> [0.12.0], placed atop
  #606's [0.11.0].

Thread 2 (docs overstate the knob, #499 decision): narrowed all six doc
surfaces for babysit_approval_downgrade_logins to the code's actual reach
(README cell, SKILL cell, FeedbackConfig docstring, --approval-downgrade-logins
help, CHANGELOG entry, plugin.json userConfig description). The knob only
elevates to material an approval whose body carries blocking-looking prose
that parses as an approval verdict — the one case the structural downgrade
routes; a real APPROVED-state review or a plain clean approval is ignored
regardless. No change to collect_feedback logic.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Eu6ZSEy8ptQjCXbhbjBKpo
@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 3m 6s —— View job


PR Review — fix(source-control): Approve-with-nits classification (post-merge pass)


This pass covers the merge commit 07961c5 (narrow approval-downgrade docs; bump 0.12.0; compose #606 attribution-drift arm from main). Prior rounds confirmed the core logic and tests were correct; I'm focused on what changed here.

Attribution-drift arm

1f5f499 (feat: surface babysit bot-to-personal identity fallback as attribution-drift finding) is already on origin/main (landed via #606). The merge commit brings it into the branch's history but it is not part of this PR's diff — git diff origin/main...HEAD confirms the 8 changed files are all from the fix itself. Nothing to review here.

Docs narrowing — babysit_approval_downgrade_logins

The five surfaces kyle-sexton identified as needing narrowing have all been updated consistently:

Surface Status
plugin.json userConfig description ✅ Updated — correctly describes the structural downgrade, opt-in-to-material semantics, and both cases not affected (APPROVED-state reviews, plain clean approvals)
README.md config table default cell ✅ Updated — accurate one-liner
SKILL.md config table default cell ✅ Updated — matches README
--approval-downgrade-logins CLI help ✅ Updated — full accurate description
FeedbackConfig docstring ✅ Updated — correctly scopes the setting to the one case it reaches
CHANGELOG.md ✅ Updated — accurate description of the full fix including negation redaction

Accuracy check against the code: Three distinct cases for a bot review with no state == "APPROVED":

  1. Plain clean approval (has_blocking_text and has_blocking_severity both False): falls to else: ignored at line 421 — the elif approval_downgrade(...) branch is never reached. Docs now accurately state "plain clean approval with no blocking-looking prose — both are ignored regardless."
  2. APPROVED-state review: short-circuits at line 377 before any login or text check. Docs now accurately state "Real APPROVED-state reviews… are ignored regardless."
  3. Blocking-looking prose + approval verdict: enters the structural downgrade at line 393 — the one case the configured-login opt-in reaches.

The docs are accurate against the code. The approve_with_only_nits_is_not_blocking integration test not asserting result["feedback"]["ignored"] is also confirmed correct: classify_pr returns "feedback" with only blocking/material/human_blocking/humanbabysit_delta.py:1134–1139 shows ignored is deliberately omitted from the integration return. Prior reviews' #578 suggestion was not actionable; kyle-sexton's triage was correct.

NEGATED_SEVERITY_MARKER_RE

The regex introduced in c86fc59 is sound:

  • Covers "No CRITICAL or IMPORTANT findings", "No CRITICAL issues found", "No CRITICAL, IMPORTANT issues" (comma separator), "Without CRITICAL findings", "Zero actionable IMPORTANT issues" — the cases likely to appear in clean-verdict prose.
  • Severity tokens stay case-sensitive to match BLOCKING_SEVERITY_RE's rationale; negator and trailing noun are case-insensitive.
  • Applied in both has_blocking_severity (line 267) and approval_downgrade (line 285) — consistent pipeline.
  • A live CRITICAL without a negator still fires (test_approval_verdict_with_critical_marker_stays_blocking ✓).

One narrow gap: "No CRITICAL" with no trailing noun (e.g. "No CRITICAL.") is NOT redacted by the regex (trailing noun is required). Such a review body would still have BLOCKING_SEVERITY_RE match CRITICAL, causing a false positive. This is extremely unlikely in practice — any well-formed clean verdict would say "No CRITICAL issues" — and the conservative direction (forces human review, not a false negative) is acceptable.

Version bump

0.9.3 → 0.12.0 (via 0.9.4 on the PR branch, composed with 0.11.0 already on main): the final merged version is 0.12.0, correctly reflecting a minor bump for a behavior/contract change (semantics of approval_downgrade_logins inverted; structural downgrade introduced for all bots).

Open deferred threads

Both Codex P2 threads in review comments have been replied to with author-owned policy decisions and corresponding follow-up issues:

Threads remain open pending the author's design decisions, which is correct — the babysit gate counts any unresolved thread as a blocker, so the author will need to resolve or further defer these before the merge gate allows merge.


No blocking issues. The docs narrowing is accurate, the version bump is correct, the NEGATED_SEVERITY_MARKER_RE fix is sound, and the open threads are properly tracked. Ready to merge once the two deferred Codex threads are formally resolved (either closed-to-issue or acknowledged-and-accepted by the author).

@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: 07961c568f

ℹ️ 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".

…ty markers

A bot review in `skip_downgrade_logins` whose skip comment also carries a
genuine CRITICAL/IMPORTANT marker (but no imperative blocking text) was
downgraded to `material` as `review_skip` instead of staying `blocking`,
because `skip_downgrade()` rechecked only `has_blocking_text` and the
older text regex omits the structured severity markers this PR introduced.
Add the sibling `not has_blocking_severity(remainder)` guard already used
on the `approval_downgrade` path, so a live high-severity finding under a
skipped review keeps blocking. Regression test covers the CRITICAL and
IMPORTANT cases and confirms a negated severity conclusion still downgrades.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Eu6ZSEy8ptQjCXbhbjBKpo
@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 2m 17s —— View job


PR Review — fix(source-control): Approve-with-nits classification (post-sync pass)

  • Gather context: latest commit and changed files
  • Read babysit_feedback.py — skip-downgrade severity guard
  • Verify Codex P2 finding (skip path + severity) is addressed
  • Check for any new issues introduced
  • Post findings

This pass covers the latest commit 69a3560 ("guard babysit skip-downgrade against live severity markers"). All prior findings have been resolved or formally deferred; I'm focused on what's new here.

Codex P2 (line 380) — skip-downgrade severity guard ✅ RESOLVED

The final commit correctly addresses the Codex finding. The skip_downgrade() function previously rechecked only has_blocking_text(remainder), so a skip comment containing CRITICAL: authorization bypass. but no imperative text would pass and downgrade to material.

Fix at babysit_feedback.py:311:

# before
return not has_blocking_text(remainder)

# after
return not has_blocking_text(remainder) and not has_blocking_severity(remainder)

Correctness trace for "bugbot skipped: usage limit reached. Not approving. CRITICAL: authorization bypass.":

  1. Enters elif has_blocking_text(text) or has_blocking_severity(text) branch (line 380) — has_blocking_severity fires on CRITICAL
  2. Not dispositioned; approval_downgrade(text) → False (no approval verdict)
  3. If login in skip_downgrade_logins: skip_downgrade(text) strips NOT_APPROVING_RE and REVIEW_SKIP_RE → remainder = "CRITICAL: authorization bypass."
  4. has_blocking_severity(remainder) → True → skip_downgrade returns False
  5. Falls to blocking.append(record)

The negated case is also handled correctly: "No CRITICAL or IMPORTANT issues seen before the cutoff." in the remainder hits NEGATED_SEVERITY_MARKER_RE inside has_blocking_severity, CRITICAL/IMPORTANT are redacted, the function returns False → skip_downgrade still returns True (still downgrades) ✓

The fix is minimal, mirrors the pattern already used in approval_downgrade, and is symmetric with the collect_feedback entry condition (line 380) that also checks has_blocking_severity.

Tests

Three regression tests in test_babysit_feedback.py cover the right scenarios:

Scenario Asserts
Skip + CRITICAL: authorization bypass. assertFalse(skip_downgrade(...)) — stays blocking
Skip + IMPORTANT: unbounded retry loop. assertFalse(skip_downgrade(...)) — stays blocking
Skip + No CRITICAL or IMPORTANT issues seen… assertTrue(skip_downgrade(...)) — negated conclusion still downgrades

Coverage is complete for the finding.

Minor documentation gap (nit, not blocking)

The [0.12.0] CHANGELOG entry (last updated in 07961c5) documents the approval-downgrade fix comprehensively but does not mention the skip-downgrade severity guard from 69a3560. The omission doesn't affect correctness; it's a CHANGELOG documentation nit — worth adding a sentence like "A skip-downgrade comment carrying a live CRITICAL/IMPORTANT marker now stays blocking rather than being downgraded to material." Deferred to the author. Fix this →

Status of all prior findings

Finding Status
Codex P2 (line 274): negated CRITICAL/IMPORTANT → false blocker ✅ Fixed in c86fc59
Codex P2 (line 380): skip + severity-only → wrongly downgraded ✅ Fixed in 69a3560
Codex P2 (line 411): advisory P2 under approval → ignored 🔵 Deferred → #594
Configured-login plain clean approval never reaches material 🔵 Deferred → #595; docs narrowed to match
Integration test ignored bucket assertion (#578) 🔵 Deferred; not actionable at that layer

No blocking issues. The Codex P2 skip-downgrade finding is cleanly resolved. The fix is a one-line change that correctly mirrors the approval_downgrade path, is well-tested with three scenarios, and introduces no new issues. The CHANGELOG gap is a documentation nit, not a correctness issue. Ready to merge.

@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: 69a35602e5

ℹ️ 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".

…ehavior; defer APPROVED-state scan (#621)

The CHANGELOG and approval_downgrade docstring stated an unqualified
guarantee that CRITICAL/IMPORTANT findings stay blocking. A formal
APPROVED/DISMISSED-state review short-circuits to `ignored` before the
severity check (pre-existing behavior, unchanged by this PR), so the
guarantee overstated the code for that case. Narrow both to the enforced
scope — a comment or a non-APPROVED-state review — and cite the follow-up
design issue (#621) that owns whether formal APPROVED-state reviews should
be severity-scanned (bot and human paths). No classifier logic change.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Eu6ZSEy8ptQjCXbhbjBKpo
@claude

claude Bot commented Jul 20, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 1m 41s —— View job


PR Review — fix(source-control): Approve-with-nits classification (post-sync pass)

  • Gather context: latest commit and PR state
  • Read babysit_feedback.py — review current state
  • Assess Codex P1 finding (APPROVED-state review with severity markers)
  • Review latest commit (348d3fa) — docs scoping change
  • Post findings

This pass covers the latest commit 348d3fa ("docs: scope blocking-severity guarantee to enforced behavior; defer APPROVED-state scan"). All prior findings from the multi-round review have been resolved or formally tracked. Focused review below.

Latest commit (348d3fa) — docs-only scoping for APPROVED-state gap ✅

The Codex P1 finding ("Do not ignore severity findings in APPROVED reviews") is addressed by narrowing the documented guarantee rather than changing classifier logic — the correct call given what the code actually does.

Code path verified at babysit_feedback.py:382–383:

elif state in {"APPROVED", "DISMISSED"}:
    ignored.append(record)

A formal APPROVED/DISMISSED-state review routes to ignored before the has_blocking_text/has_blocking_severity branch at line 384. This short-circuit is pre-existing behavior, unchanged by this PR. The Codex P1 finding surfaced a real gap — a bot can formally approve with a live CRITICAL in the body — but it is not a regression introduced here.

What 348d3fa does: narrows the approval_downgrade docstring and the [0.12.0] CHANGELOG entry to scope the blocking-severity guarantee to "a comment or a non-APPROVED-state review", then cites follow-up #621 as the tracking issue for whether formal APPROVED-state reviews should be severity-scanned. Both surfaces now accurately describe the enforced behavior. No classifier logic changes.

Accuracy check:

The docs-narrowing resolution is the right approach here. Silently dropping formal APPROVED reviews with CRITICAL/IMPORTANT bodies is a policy gap that predates this PR; unilaterally closing it in a patch fixing Approve-with-nits classification would expand scope and introduce an undesigned behavior for human reviewers (same state in {"APPROVED", "DISMISSED"} branch covers both). #621 is the correct place to decide that.

Full finding status

Finding Source Status
Core fix: Approve-with-nits → ignored PR ✅ Fixed
Negated CRITICAL/IMPORTANT → false blocker Codex P2 (line 274) ✅ Fixed in c86fc59
Skip-downgrade severity guard Codex P2 (line 384) ✅ Fixed in 69a3560
APPROVED-state review bypasses severity check Codex P1 (line 384) ✅ Docs narrowed; deferred → #621
Advisory P2 under approval → ignored Codex P2 (line 415) 🔵 Deferred → #594
Configured-login plain clean approval never reaches material Codex P2 (line 384) 🔵 Docs narrowed; deferred → #595
Integration test ignored bucket assertion (#578) Bot reviews 🔵 Not actionable at integration layer

No new issues introduced

The CHANGELOG line A negated severity conclusion — a clean approval stating 'No CRITICAL or IMPORTANT is long but is prose in a CHANGELOG entry, not code — not actionable.


No blocking issues. The latest commit correctly scopes the documented guarantee to match the enforced behavior, and the APPROVED-state scan decision is properly deferred to #621. All correctness issues raised across six review rounds have been fixed or formally tracked with follow-up issues. Ready to merge.

@kyle-sexton
kyle-sexton merged commit f49719d into main Jul 20, 2026
18 checks passed
@kyle-sexton
kyle-sexton deleted the fix/499-babysit-snapshot-approve-nits-classification branch July 20, 2026 06:03
kyle-sexton added a commit that referenced this pull request Jul 20, 2026
…val classifier + golden fixtures (#534) (#634)

## Summary

Authorship (self/bot/human), finding (severity + lifetime-vs-open
state), and approval-verdict detection were hand-rolled independently
across the babysit **snapshot** (`babysit_delta`/`babysit_feedback`),
the **merge gate** (`babysit_merge`), the **resolve-thread reporter**
(`babysit_resolve_thread`), and the **readiness gate**
(`babysit-readiness-gate.sh`). The surfaces disagreed on identical input
— the six-issue misclassification class this umbrella closes (the
smoking gun: #499, where the gate returned `findings=0` while the
snapshot called the same review "1 blocking bot finding").

This extracts **one shared classifier** and migrates every surface to
it, so the surfaces cannot diverge, and folds each member issue in as a
golden fixture — regression-proof by construction. Implements the
operator-**ACCEPTED Option A** from the decision brief verbatim.

## Fix

- **New leaf module `babysit_classify.py`** (depends only on
`babysit_util`) owns the three concern areas: authorship (`is_bot`,
`actor_kind`, self-login normalization/membership, dependency-author
test), finding (blocking/severity heuristics + the readiness gate's
finding/classification **counting**), and approval-verdict
(approval/skip downgrades). The primitives moved out of
`babysit_feedback`, which re-exports them so existing consumers are
unchanged.
- **Migrations (import the shared classifier directly):**
`babysit_delta` and `babysit_merge` replace hand-rolled `self_logins`
casefold-compare (delta ×3 + merge) with the shared
`normalize_self_logins`/`is_self_login`; `babysit_feedback` orchestrates
the shared primitives; `babysit_resolve_thread` shares the same
`is_bot`. **`babysit_merge` remains the merge-ready boolean owner** — it
just stops carrying its own private authorship copy.
- **Gate shells out (ratified, non-defaultable call):**
`babysit-readiness-gate.sh` shells out to `babysit_findings.py` for
finding-counting (mirroring the existing `source-control-babysit-merge`
bash→Python wrapper) instead of re-implementing the severity vocabulary
in bash grep. The **bash counting is retained only as the Python-free
safe-tier degrade** (`reference/loop.md` is that path and it runs this
gate; hard-requiring Python would regress that documented tier). A
convergence test pins the two counts together on thread-state-free
input.
- **#465 (lifetime P-badges over-counted → false `READINESS_BLOCKED`):**
the shared counter discounts a severity marker carried in a review
thread GitHub reports **resolved or outdated**, counting currently-open
findings only. **Scope:** this is the mechanical resolved/outdated
discount the brief specs ("severity occurrence + lifetime-vs-open
**state**"). De-duplicating the *same concern restated across re-review
rounds within still-open threads* is deliberately **out of scope** —
there is no reliable mechanical "same concern" signal — so restatements
still count.
- **#512 (`humanThreadsActed` reported for a Bot-authored thread):**
count only threads whose **opening author** is human (via the shared
`is_bot`), not `botOnly` (which mislabeled a bot-opened thread carrying
a later human reply).
- **orchestration doc:** there was no explicit "cross-check live thread
state each cycle" workaround to delete — `orchestration.md` already
mandates trusting the deterministic engine (the brief notes the
re-derivation was "real but implicit"). Strengthened that trust
instruction to name the classification as now one shared, fixture-locked
classifier, so "don't re-derive by eye" has teeth.
- Every surface stays a **pure predicate with no writes** (#440
`report-only` default preserved).

## Verification

All commands run in the worktree with Python 3.14.6, ruff 0.15.20, jq
1.8.2.

**Engine suite (unittest + ruff + guarded wrappers) — 256 tests (was
236; +17 classifier + 3 resolve-thread golden fixtures):**
```
== unittest suite ==
Ran 256 tests in 1.500s
OK
== ruff ==
All checks passed!
== guarded-wrapper behavior ==
PASS: merge wrapper rejects --allow-unpinned-head
PASS: merge wrapper reaches fail-closed CLI (no allowlist)
PASS: resolve wrapper reaches fail-closed CLI (no allowlist)
```

**Readiness-gate bash suite — 53 cases, exit 0.** The 47 pre-existing
fixtures still pass (now through the Python counter). New: the #465
lifetime discount end-to-end, plus an explicit **dual-path convergence**
test — `BABYSIT_READINESS_BASH_ONLY=1` forces the degrade so both counts
are observed in one run and asserted equal, so the severity vocabulary
cannot drift between Python and bash silently:
```
PASS: [48] #465 lifetime discount -> findings=1 (only open)
PASS: [49] #465 lifetime discount -> READINESS_OK
PASS: [50] convergence [severity-words]: python == bash degrade (findings=3 classified=1)
PASS: [51] convergence [codex-badges]: python == bash degrade (findings=2 classified=1)
PASS: [52] convergence [plain-p-markers]: python == bash degrade (findings=2 classified=1)
PASS: [53] convergence [self-row-exclusion]: python == bash degrade (findings=1 classified=1)
```

**#465 end-to-end (3 lifetime severity markers — 2 resolved/outdated, 1
open — fully classified):**
```
$ python babysit_findings.py --comments-json lifetime.json --self me[bot]
findings=1 classified=1
$ bash babysit-readiness-gate.sh 377 --comments-json lifetime.json --self me[bot]
READINESS_OK findings=1 classified=1 checklist=n/a   (exit 0)
```
The bash degrade counts all 3 lifetime markers (old behavior, preserved
for Python-free); the Python path discounts the 2 resolved/outdated →
the false `READINESS_BLOCKED` is gone.

**#512 regression (`tests/test_babysit_resolve_thread.py`):** bot-opened
thread with a human reply, acted under `--include-human` →
`humanThreadsActed: 0`; human-opened → `1`; mixed → `1`. Passing.

**Lint:** `shellcheck --rcfile .shellcheckrc` clean on both gate
scripts; `shfmt -d` (editorconfig-driven) clean; plugin manifest valid
JSON at `0.13.0`.

Closes #534

## Related

Member issues folded as golden fixtures / regression proofs: **#512**
(bot thread counted as human — fixed here), **#499** (Approve-with-nits,
already fixed by #567 — regression fixture), **#465** (lifetime
over-count — fixed here), **#497** (single-`--pr` `self_logins` empty,
already fixed on main — covered by `SinglePrScopeSelfLoginTests`),
**#473** (CLOSED, self-reply — fixture-only). **#455** is a *different*
classifier (auto-mode retry semantics, not authorship/finding/approval)
and is referenced only, per the brief.
- #637 — post-green review finding (`babysit_resolve_thread.py` two
`is_bot` call sites omit `extra_bot_logins`), pre-existing gap surfaced
but not introduced by this PR, deferred out of scope

Deferred out of this PR's scope (arrived as research/triage notes, not
operator-ACCEPTED like Option A; none trivially covered by the
classifier built here):
- **#618** advisory-thread fast-path — tag Codex `COMMENTED` threads as
ADVISORY for a fast-path resolve ([#534 comment
5019177827](#534 (comment))).
A natural follow-up: it would slot into this same classifier.
- **#594 / #595** — P2-under-Approve material-vs-ignored routing and
configured-login clean-approval opt-in reachability (`babysit_feedback`
classification gaps found on PR #567). Same classifier home, non-trivial
policy decisions of their own.
- **#465 (b)** dedup-by-concern across re-review rounds — no reliable
mechanical signal (see Fix).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

https://claude.ai/code/session_01KyLj6oaFVTE6xFuoYCC2KC

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

automated Opened by automation.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

source-control:babysit-prs: snapshot classifies an Approve-with-nits claude[bot] review as blocking bot feedback (gate correctly returns findings=0)

1 participant