Skip to content

fix(source-control): invoke babysit mutation wrappers by bin/ path, not bare name (#484)#840

Merged
kyle-sexton merged 10 commits into
mainfrom
fix/484-babysit-resolve-thread-path
Jul 21, 2026
Merged

fix(source-control): invoke babysit mutation wrappers by bin/ path, not bare name (#484)#840
kyle-sexton merged 10 commits into
mainfrom
fix/484-babysit-resolve-thread-path

Conversation

@kyle-sexton

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

Copy link
Copy Markdown
Contributor

Summary

The source-control:babysit-prs worker/autopilot contract told workers to run the merge gate and resolve review threads by invoking the guarded mutation wrappers as bare command names (source-control-babysit-merge …, source-control-babysit-resolve-thread …). Those bare names are not on the Bash tool's PATH, so every such invocation failed command not found (exit 127) — reproduced first-hand in this environment. Workers fell back to raw gh api graphql resolveReviewThread calls, losing the wrapper's --allowed-owners guardrail and the JSON action receipt the contract tells them to parse, and re-implementing the safety logic ad hoc per worker.

Fix

Invoke each wrapper by its bundled pathbash "${CLAUDE_PLUGIN_ROOT}/bin/<wrapper>" … — exactly the form the read-only sibling scripts under ${CLAUDE_PLUGIN_ROOT}/scripts/ (fetch-all-pr-comments.sh, babysit-readiness-gate.sh) already use. Updated surfaces:

  • skills/babysit-prs/reference/safety.md — the single home for the exact commands. The prior Guarded Mutation Wrappers posture said the wrappers are invoked "only by their pinned bare wrapper names … never through an interpreter-prefixed path," which the bare-name PATH gap made unfollowable. It now prescribes the bin/-path form and narrows the prohibition to what actually protects the guards: raw-Python invocation (python … babysit_merge.py, which bypasses the wrapper's own guards such as the merge wrapper's --allow-unpinned-head rejection) and piping a wrapper into an interpreter. All fenced command blocks and the Pinned-Command Degradation operator handoffs use the path form.
  • skills/babysit-prs/SKILL.md — corrected the "invoked ONLY by their bare wrapper names" statement; points at safety.md as the single home and states that every source-control-babysit-<x> … command in this skill and its references is launched by the bin/-path form.
  • skills/babysit-prs/reference/orchestration.md — the worker prompt template (the literal contract handed to a dispatched worker) and the file's two other runnable references now use the path form, so the file is self-consistent for a standalone worker.
  • reference/review-discipline.md — a one-line pointer in D7.5 noting the babysit tiers resolve through the guarded wrapper (which adds --allowed-owners, bot-vs-human classification, and a JSON receipt). D7.5's general raw-GraphQL policy is unchanged — it stays the mechanism for /pull-request, which is not a babysit-tier consumer; repointing it at the babysit-tier wrapper would wrongly couple /pull-request to a babysit tool.

Why the path form and not shipping the bare name onto PATH: the wrappers are designed to be bare commands on PATH, and Claude Code's plugin reference documents a plugin's bin/ as added to the Bash tool's PATH "while the plugin is enabled." Empirically that is not happening in this environment — an upstream/harness matter this repo cannot fix. The ${CLAUDE_PLUGIN_ROOT}/bin/-path form is the deterministic, repo-level invocation that works today and matches the established sibling-script convention.

Verification

  • Reproduced the gap in this worktree's Bash tool (plugin enabled, Windows/Git-Bash): which source-control-babysit-resolve-thread → not found (exit 1); $CLAUDE_PLUGIN_ROOT is empty in the shell; no plugin bin/ on PATH.
  • Confirmed the path form keeps every guard (so the posture refinement is safe): invoking a wrapper by path still runs the wrapper —
    • bash "…/bin/source-control-babysit-merge" owner/repo#1 --allow-unpinned-head → still rejected, exit 2 (--allow-unpinned-head is not permitted through the wrapper).
    • bash "…/bin/source-control-babysit-resolve-thread" owner/repo#1 --resolve (no --allowed-owners) → still fail-closed, exit 3, with the JSON refusal receipt.
  • Lint: markdownlint-cli2 clean (0 errors) on all four edited docs.
  • Doc-only change (.md + plugin.json version); no scripts or executables modified.

Closes #484

Related

kyle-sexton and others added 2 commits July 21, 2026 05:35
…ot bare name (#484)

The babysit-prs worker/autopilot contract told workers to invoke the guarded
mutation wrappers (source-control-babysit-merge,
source-control-babysit-resolve-thread) by their bare command names, but those
names are not on the Bash tool's PATH — every bare invocation failed
command not found (exit 127), forcing workers to hand-roll raw gh api graphql
resolveReviewThread calls and lose the wrapper's --allowed-owners guardrail and
JSON action receipt.

Invoke each wrapper as bash "${CLAUDE_PLUGIN_ROOT}/bin/<wrapper>" — the same
form the read-only sibling scripts under ${CLAUDE_PLUGIN_ROOT}/scripts/ already
use — across SKILL.md, reference/orchestration.md (incl. the worker prompt
template), and reference/safety.md. Refine safety.md's Guarded Mutation Wrappers
posture accordingly: launching a wrapper by path runs the wrapper with every
guard intact, so the only forbidden re-spelling is the raw Python behind them
(which bypasses the guards) plus piping a wrapper into an interpreter. Add a
one-line pointer in reference/review-discipline.md noting the babysit tiers
resolve through the wrapper, while D7.5 keeps its general raw-GraphQL policy for
/pull-request.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
… -> 0.15.9)

A sibling source-control PR (the #475 readiness-gate fix) is open concurrently
and already claims 0.15.8. Per the coordinate-and-serialize protocol for
same-plugin PRs, move this PR one increment past it to 0.15.9 so the two never
collide on version, and keep this PR do-not-merge until the sibling lands.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
@kyle-sexton kyle-sexton added do-not-merge Hard merge gate: do not merge while applied. automated Opened by automation. labels Jul 21, 2026
@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

Claude finished @kyle-sexton's task in 4m 56s —— View job


PR Review: fix(source-control): invoke babysit mutation wrappers by bin/ path (#484)

  • Gather context (PR description, changed files)
  • Read PR diff
  • Review plugin.json version bump
  • Review CHANGELOG.md
  • Review skills/babysit-prs/reference/safety.md
  • Review skills/babysit-prs/SKILL.md
  • Review skills/babysit-prs/reference/orchestration.md
  • Review reference/review-discipline.md
  • Check consistency across all changed docs

Overall: The fix is correct, minimal, and well-reasoned. The bin/ directory and both wrapper files exist, the guards all survive the path form, and the CHANGELOG documents both the fix and its residuals clearly. One moderate ordering issue in SKILL.md is worth addressing before the do-not-merge hold is lifted.


Correctness

Core fix is sound. bash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-merge" is exactly the form the read-only sibling scripts under ${CLAUDE_PLUGIN_ROOT}/scripts/ already use, and both wrapper files (source-control-babysit-merge, source-control-babysit-resolve-thread) are confirmed present in bin/. The verification in the PR description (exit 2 for --allow-unpinned-head, exit 3 on missing --allowed-owners) confirms every guard survives the path form — this is not a guard-bypass re-spelling.

Posture refinement in safety.md is well-scoped. Replacing "never through an interpreter-prefixed path" (overly broad — would have excluded bash "...") with "never through the raw Python behind them … and never pipe a wrapper into an interpreter" is the right narrowing. The prior phrasing was actually in tension with the fix, and correcting it here is correct. See safety.md lines 117–136.

orchestration.md coverage is complete. All three executable invocation points are updated:

  • Fan-out gate inline reference (line 36)
  • Worker Contract auto-resolve rule (line 510)
  • Worker Prompt Template (line 576)

Issues

Moderate — SKILL.md autopilot step 2 uses bare-name shorthand before the convention is declared

SKILL.md line 146 (autopilot step 2):

resolves it via `source-control-babysit-resolve-thread owner/repo#N --allowed-owners <watched-owners>
--resolve --include-human`

The convention that covers this shorthand — "every command written as source-control-babysit-<x> … in this skill … is launched by that bin/-path form" — is declared at line 186, after the usage at line 146. An autopilot agent reading the numbered steps (1, 2, 3) for what to do per PR will encounter line 146 before it reaches the convention declaration, and may issue a bare invocation for exactly the autopilot thread-resolve action this PR is meant to fix.

The worker contract in orchestration.md and the worker prompt template are correct, but the autopilot's own description is not. The simplest fix is to either (a) add a parenthetical (via the path form in \safety.md`)` at line 146, or (b) move the convention statement above the autopilot steps, or (c) update line 146 to use the full path form directly. Fix this →

Minor — Pinned-Command Degradation handoffs use ${CLAUDE_PLUGIN_ROOT}/bin/...

safety.md lines 284, 299, 305 — the degraded commands surfaced to operators now require CLAUDE_PLUGIN_ROOT to be set. In an agent session this is fine. In a bare operator shell (someone copy-pasting the command from a report), the variable will be unexpanded and the path won't resolve. The PR's CHANGELOG already notes this residual. Worth a follow-up note in the Pinned-Command Degradation section telling operators to expand the path to their plugin cache before running (or provide the absolute path). Not blocking.

Observation — Worker Prompt Template command is in prose, not a code block

orchestration.md lines 576–578 — the long bash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-resolve-thread" owner/repo#42 … command wraps across three prose lines without a code fence. A worker reading this will understand it from context, but the lack of a fence makes it slightly harder to distinguish the command boundary. This predates the PR (the bare name had the same issue); not a regression, and noted for a follow-up only.


Security

No security concerns introduced. The invocation form change is strictly a PATH-resolution fix; the wrapper's guards (fail-closed without --allowed-owners, rejection of --allow-unpinned-head, ban on raw Python invocation, ban on piping to an interpreter) are all intact. The bin/ directory contents are unchanged.


Version & CHANGELOG

plugin.json is at 0.15.9 (serialized past sibling PR #839's 0.15.8) and the CHANGELOG's top entry is correctly ## [0.15.9], documenting both the fix and the two residuals (allow-rule mismatch and the upstream PATH gap). Consistent.


Summary: Approve the core fix. Recommend addressing the SKILL.md autopilot step 2 ordering issue (moderate) before dropping do-not-merge, as it leaves the very action this PR is fixing (autopilot thread resolution) described with a bare invocation that an agent will encounter before reaching the convention statement.

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


Security Review

  • Gather PR diff and read changed files
  • Analyze changed files for security vulnerabilities
  • Post security review findings

Scope: Documentation-only change (.md + plugin.json version bump). No scripts or executables modified. Review focuses on whether the documentation changes introduce new attack surfaces or weaken existing guardrails.

No CRITICAL or IMPORTANT findings. Two SUGGESTION-level observations follow.


Finding 1 — SUGGESTION · PLAUSIBLE

Autopilot step 2 in SKILL.md still spells the resolve command as a bare name

SKILL.md lines 146–148 still instructs the agent to invoke the resolve wrapper via its bare name (source-control-babysit-resolve-thread owner/repo#N ...). The PR addresses this indirectly via a general statement added to the "Guarded mutations" section at lines 188–191:

every command written as source-control-babysit-<x> … in this skill and its reference files is launched by that bin/-path form

The risk is that an LLM agent following the numbered autopilot steps sequentially may not apply the general statement retroactively to override the bare-name form at step 2, reproducing the exact runtime failure (command not found, exit 127) this PR is fixing — and triggering the documented fallback of bypassing the wrapper guards entirely. Step 3 was explicitly updated to point at safety.md for "the exact command"; step 2 was not given the same treatment. The failure mode is the same one confirmed in the PR description's own reproduction.

The "single home" approach chosen here is a valid design tradeoff (avoids merge conflicts with concurrent PRs), but the asymmetry between step 2 (still bare-name) and step 3 (explicit safety.md reference) leaves a gap.


Finding 2 — SUGGESTION · PLAUSIBLE

CLAUDE_PLUGIN_ROOT empty-string resolves to /bin/<wrapper> with a misleading error signal

All updated commands now take the form:

bash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-merge" …
bash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-resolve-thread" …

The PR itself confirms that CLAUDE_PLUGIN_ROOT is empirically empty in this environment ("an upstream/harness matter this repo cannot fix"). When empty, double-quoted expansion still occurs: bash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-merge" expands to bash "/bin/source-control-babysit-merge" — an absolute path into /bin/, not the plugin directory.

The failure is safe (no such file in /bin/), but:

  1. The error message is "no such file or directory: /bin/source-control-babysit-merge" rather than "CLAUDE_PLUGIN_ROOT is unset", which is harder to diagnose and could be mistaken for a missing binary.
  2. If something named source-control-babysit-merge were ever present in /bin/ (e.g. via a misconfigured system or PATH pollution that added /bin/ wrappers), it would be executed silently.

This risk is theoretical given the naming convention, but a guard such as [[ -n "${CLAUDE_PLUGIN_ROOT}" ]] || { echo "CLAUDE_PLUGIN_ROOT is unset"; exit 1; } in the wrapper scripts themselves would make the failure deterministic and unambiguous. Those scripts weren't changed by this PR, so this is a residual gap in the overall safety posture rather than a regression introduced here.


No findings related to:

  • The wrapper guards themselves (unchanged scripts, guards confirmed intact by PR verification)
  • Prompt injection (worker prompt template's BEGIN QUOTED PR DATA fence unchanged)
  • Token/credential handling (pre-existing GH_TOKEN= inline form; no change in this PR)
  • Permission widening (per-call prompts now replace broad allow-rule match — a security improvement, noted in the PR)
  • The plugin.json manifest (version bump only)

…quality cap

The guarded-mutations invocation correction pushed SKILL.md to 502 lines, over
the skill-quality-gate hard cap of 500. Trim the note to defer the full
invocation rationale to reference/safety.md (its single home), restoring 499.

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

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


PR Review: fix(source-control): invoke babysit mutation wrappers by bin/ path (#484)

  • Gather context (PR description, changed files)
  • Read changed files
  • Review plugin.json version bump
  • Review CHANGELOG.md
  • Review skills/babysit-prs/reference/safety.md
  • Review skills/babysit-prs/SKILL.md
  • Review skills/babysit-prs/reference/orchestration.md
  • Review reference/review-discipline.md
  • Check consistency across all changed docs

Overall: The core fix is correct and safe. Both wrapper files are confirmed present in bin/, the guards all survive path-form invocation, and the posture refinement in safety.md is well-reasoned. One moderate ordering issue in SKILL.md — also raised by the two prior automated reviews — remains the blocker before dropping do-not-merge.


Correctness

Core fix verified. Both source-control-babysit-merge and source-control-babysit-resolve-thread exist in plugins/source-control/bin/. The bash "${CLAUDE_PLUGIN_ROOT}/bin/<wrapper>" invocation form matches what the sibling scripts under ${CLAUDE_PLUGIN_ROOT}/scripts/ already use, and the PR's own verification (exit 2 for --allow-unpinned-head, exit 3 for missing --allowed-owners) confirms every guard survives the path form.

All three executable invocation points in orchestration.md are updated:

  • Fan-out gate inline reference (line 36) — bash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-merge" owner/repo#42 …
  • Worker Contract auto-resolve rule (line 510) — bash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-resolve-thread" owner/repo#42 …
  • Worker Prompt Template (line 576) — bash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-resolve-thread" owner/repo#42 …

safety.md posture refinement is well-scoped. Replacing "never through an interpreter-prefixed path" (which wrongly excluded bash "...") with "never through the raw Python behind them … never pipe a wrapper into an interpreter" is the correct narrowing. The Pinned-Command Degradation handoffs (lines 284, 299, 305) consistently use the bin/-path form.

review-discipline.md D7.5 change is surgical. The parenthetical at lines 202–206 correctly notes that babysit tiers use the guarded wrapper without changing the D7.5 raw-GraphQL policy for /pull-request. The scoping is accurate.


Issues

Moderate — SKILL.md autopilot step 2 still describes bare-name invocation without a path-form pointer

SKILL.md line 146 (autopilot step 2):

resolves it via `source-control-babysit-resolve-thread owner/repo#N --allowed-owners <watched-owners>
--resolve --include-human`

The convention that covers this — "every command written as source-control-babysit-<x> … in this skill … is launched by that bin/-path form" — is declared at line 186, 40 lines later. An autopilot agent reading steps 1→2→3 top-down reaches the thread-resolve instruction at step 2 before encountering either the convention or the safety.md pointer.

By contrast, step 3 at line 155 — also bare-name — explicitly defers to safety.md for "the exact command." Step 2 is missing that same deferral, creating an asymmetry between the two autopilot mutation steps: a step-3 agent checks safety.md and gets the path form; a step-2 agent may issue a bare invocation and reproduce the exact exit 127 this PR is fixing.

Simplest fix: mirror step 3's treatment — add (the exact command is in [\safety.md`](reference/safety.md))after the backtick-quoted command at line 147–148. Alternatively, use the fullbash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-resolve-thread"` form directly at step 2 to make it self-contained without relying on reading ahead to line 186. Fix this →

Minor — Pinned-Command Degradation handoffs require CLAUDE_PLUGIN_ROOT to be set in the operator's shell

safety.md lines 284, 299, 305 — the degraded commands surfaced to operators now embed ${CLAUDE_PLUGIN_ROOT}/bin/…. When an operator copies the command to a bare shell, the variable will be unexpanded (empty), resolving to /bin/<wrapper> — a safe failure (no such file), but with a misleading error message. The PR's CHANGELOG already notes this residual. Worth a follow-up note at the top of the Pinned-Command Degradation section instructing operators to substitute the absolute path before running. Not blocking.


Security

No security concerns introduced. The path-form invocation is not a guard bypass — the wrapper still runs with every guard intact. The narrowed prohibition in safety.md (raw Python invocation and interpreter-piping) correctly captures only the forms that actually dodge the guards. No permission widening; per-call prompts now replace the broad bare-name allow rule, which is if anything a stricter outcome.


Version & CHANGELOG

plugin.json is at 0.15.9 (serialized past sibling PR #839's 0.15.8). The CHANGELOG correctly skips 0.15.8 here — that entry belongs to #839 and will land when #839 merges first. The 0.15.9 entry accurately describes both the fix and the two residuals (allow-rule mismatch and the upstream PATH gap).


Recommendation: The core fix is sound. The SKILL.md step 2 ordering issue is the one remaining item to address before dropping do-not-merge — it leaves the primary fix target (autopilot thread resolution) described without a path-form pointer in exactly the position an agent reads first. All prior observations from the two automated reviews above are confirmed.

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


Security Review — PR #840 (fix/source-control: invoke babysit mutation wrappers by bin/ path)

  • Gather PR diff (all 3 commits)
  • Read changed files
  • Analyze for security vulnerabilities
  • Post findings

Scope: Documentation-only change (.md + plugin.json version bump). No scripts or executables were modified. The wrapper scripts (bin/source-control-babysit-merge, bin/source-control-babysit-resolve-thread) are confirmed unchanged — their self-locating readlink -f "$0" mechanism works correctly when invoked by absolute path, so the path-form change is a safe re-spelling and every guard survives.


IMPORTANT — Confirmed: Autopilot step 2 retains bare-name invocation before the bin/-path convention is declared

File: plugins/source-control/skills/babysit-prs/SKILL.md, lines 145–146

Autopilot step 2 spells the resolve command as a bare name:

resolves it via `source-control-babysit-resolve-thread owner/repo#N --allowed-owners <watched-owners>
--resolve --include-human`

The bin/-path convention covering this is declared at line 186, 40 lines later. An autopilot agent following the numbered steps (1 → 2 → 3) encounters the bare name at step 2 before it reaches the convention. When the bare invocation fails command not found (exit 127), the documented failure mode — confirmed first-hand in this environment and called out in the PR description — is that workers fall back to raw gh api graphql resolveReviewThread. That fallback bypasses:

  • --allowed-owners enforcement — the authorization gate that restricts resolution to watched-owner repos. A raw-GraphQL fallback can resolve threads on any PR the agent can see.
  • Thread-pin pair rule — the TOCTOU protection (--thread-id + --expected-comment-count + --expected-last-updated) that prevents resolving threads added or edited after the snapshot was taken. Raw GraphQL accepts an unpinned threadId alone.
  • JSON receipt — the structured action confirmation ("action": "resolved") that lets the autopilot verify exactly what was mutated.

This is the exact failure mode the PR is fixing in orchestration.md and safety.md, but step 2 — the primary autopilot path — was not updated. Step 3's merge reference (line 155) correctly points at safety.md as the single home; step 2's resolve reference does not.

Confidence: Confirmed. Line 145 is unchanged (bare name), line 186 declares the convention after it, and the PR description itself describes the fallback path as a first-hand reproduction.

Remediation: One of: (a) add (via the path form in \safety.md`)after the inline command at line 146, (b) replace line 146's bare name withbash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-resolve-thread"`, or (c) move the convention statement above step 2. Fix this →


SUGGESTION — Plausible: CLAUDE_PLUGIN_ROOT unset expands degradation handoff commands to /bin/<wrapper>

Files: safety.md lines 284, 299, 305 (Pinned-Command Degradation); orchestration.md lines 510, 576

The PR confirms that CLAUDE_PLUGIN_ROOT is empirically empty in this environment. When empty, bash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-merge" expands under double-quoting to bash "/bin/source-control-babysit-merge" — an absolute path into /bin/, not the plugin directory.

This primarily affects the Pinned-Command Degradation handoff: a degradation report now surfaces commands containing ${CLAUDE_PLUGIN_ROOT} that an operator may copy-paste into a shell where the variable is unset. In a normal shell, the command silently resolves to /bin/source-control-babysit-merge, which does not exist and fails safely — but the error "No such file: /bin/source-control-babysit-merge" obscures the real root cause (unset variable) and could send an operator on a wrong diagnostic path. A secondary concern: if anything named source-control-babysit-merge were ever placed in /bin/ (a root-privileged path, so this requires system compromise or supply-chain presence), it would execute with the full pinned argument list — including --merge, --expected-head, and --allowed-owners values from a real PR — without any wrapper guard.

Confidence: Plausible. The empty-variable expansion is deterministic; the /bin/ placement prerequisite is implausible under normal conditions.

Remediation: Add an explicit [[ -n "${CLAUDE_PLUGIN_ROOT}" ]] || { echo "CLAUDE_PLUGIN_ROOT is unset"; exit 1; } guard in the wrapper scripts (deferred per the PR's own follow-up note), or add an operator note in the Pinned-Command Degradation section instructing them to expand CLAUDE_PLUGIN_ROOT to an absolute path before running.


No findings for

  • Wrapper guards: Both wrappers confirmed unchanged, guards intact (--allow-unpinned-head rejected at line 16–20; fail-closed on missing --allowed-owners). Self-locating via readlink -f "$0" — not dependent on CLAUDE_PLUGIN_ROOT.
  • GitHub Actions: No workflow files modified.
  • Credential/token handling: No changes to GH_TOKEN handling or secret surfaces.
  • Permission widening: The PR's effect on allow rules is a narrowing (per-call prompts replace broad bare-name allow rules), not a widening.
  • Prompt injection from PR data: BEGIN QUOTED PR DATA fence in the worker prompt template is unchanged. The command spelling change in prose (orchestration.md lines 576–578) predates this PR's structure.
  • Path traversal in CLAUDE_PLUGIN_ROOT: The variable is set by the Claude Code harness, not user-supplied input. Shell metacharacters in the value would be unusual and are out of this PR's scope.
  • review-discipline.md addition: Informational cross-reference only; no new execution surface.

Summary: One IMPORTANT finding (confirmed): autopilot step 2 in SKILL.md retains the bare-name invocation that this PR is fixing elsewhere, leaving the exact fallback path (bare → command not found → raw GraphQL without --allowed-owners or thread-pin rule) reachable from the primary autopilot description. One SUGGESTION (plausible): CLAUDE_PLUGIN_ROOT empty expansion in degradation handoffs. Both findings were also raised in the concurrent code review comment above; no additional findings beyond those.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Post-green review classification (work lane):

Finding Source Classification Disposition
SKILL.md autopilot step 2 retains bare-name invocation before the bin/-path convention is declared (line 146 vs line 186) — reproduces the exact command not found → raw-GraphQL fallback this PR fixes, bypassing --allowed-owners + thread-pin TOCTOU rule 2× PR review + 1× security review (IMPORTANT, Confirmed) DEFER-FORBIDDEN — security-posture / fail-open gate bypass reachable from the primary autopilot path Fixing on-branch now, not deferring. Worker resumed.
CLAUDE_PLUGIN_ROOT empty-string expansion resolves degradation-handoff commands to /bin/<wrapper> (safe failure, misleading error message) Security review (SUGGESTION, Plausible) Non-blocking — already scoped as a residual in this PR's CHANGELOG/Related; the wrapper scripts themselves are unchanged/out of diff Left alone
Pinned-Command Degradation prose lacks a code fence (orchestration.md) PR review (Observation) Pre-existing, not a regression Left alone

No inline review threads to resolve (all comments are PR-level).

Holding do-not-merge — unchanged, still correctly serialized behind sibling #839 which has not yet merged. Will re-check once the IMPORTANT fix lands and again once #839 merges (rebase + re-bump both needed before flipping ready).

…step 2 (#484)

Autopilot step 2 spelled the resolve command as a bare wrapper name with args,
40 lines before the bin/-path invocation convention is declared. An autopilot
agent reading steps top-down hit the bare name first and reproduced the exact
command not found (exit 127) -> raw-GraphQL fallback this PR exists to prevent,
bypassing --allowed-owners enforcement and the thread-pin pair rule. Make step 2
point at reference/safety.md for the exact command, symmetric with step 3's
merge-gate treatment, so no runnable bare command precedes the convention.

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

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


Security Review — PR #840 (HEAD: b34208c)

  • Gather context (PR description, prior reviews, event trigger)
  • Get full PR diff (git diff 1d78d70...b34208c -- plugins/source-control/)
  • Read changed files
  • Read wrapper scripts (bin/source-control-babysit-merge, bin/source-control-babysit-resolve-thread)
  • Analyze for security vulnerabilities
  • Post findings

Scope: Documentation-only change (.md + plugin.json version bump across 4 commits on this branch). No scripts or executables modified. Both wrapper files (bin/source-control-babysit-merge, bin/source-control-babysit-resolve-thread) are confirmed unchanged and present.


Prior IMPORTANT finding: RESOLVED by latest commit (b34208c)

The three prior automated security reviews each flagged — one at IMPORTANT/Confirmed severity — that SKILL.md autopilot step 2 retained a bare-name invocation (source-control-babysit-resolve-thread owner/repo#N …) before the bin/-path convention was declared at line 186, creating a command not found → raw-GraphQL fallback path that bypassed --allowed-owners enforcement and the thread-pin TOCTOU rule.

The latest commit resolves this: step 2 now reads:

resolves it through the guarded resolve-thread wrapper (--resolve --include-human — bot, AI-review, and human threads alike); the exact command is the single home in reference/safety.md.

No runnable bare name precedes the convention. Step 2 and step 3 are now symmetric — both defer to safety.md for the exact command. The IMPORTANT finding is closed.


No CRITICAL or IMPORTANT findings

Wrapper guard verification (unchanged scripts): Both wrapper scripts self-locate via readlink -f "$0", so they resolve correctly regardless of whether they are invoked by bare name, absolute path, or the bin/-path form. The merge wrapper's --allow-unpinned-head rejection (line 15–22) and both wrappers' fail-closed behavior on missing --allowed-owners are intact. The path-form change is not a guard-bypass re-spelling.

No shell injection surface introduced: The documented commands use "${CLAUDE_PLUGIN_ROOT}/bin/<wrapper>" with double-quotes. Arguments are passed via "$@" in the wrapper, not through shell interpolation. PR data (owner/repo, PR number, --allowed-owners values) enters as distinct positional arguments that an agent supplies, not as inline string substitution into a shell command — the documentation describes what the agent should invoke, not a shell heredoc or eval path.

Worker Prompt Template (orchestration.md lines 573–578): The BEGIN QUOTED PR DATA fence around injected PR content is unchanged. The command spelled in prose within the template is a description of what the worker should run, not a directly-executed string. No prompt-injection surface is widened by changing the command spelling from bare-name to path form.

No GitHub Actions changes: No workflow files modified. No pull_request_target, workflow_run, script injection, or permission-widening patterns to review.

No credential/token handling changes: GH_TOKEN handling is unchanged. No new secret surfaces.


SUGGESTION (pre-existing, not a regression) — CLAUDE_PLUGIN_ROOT empty-string in degradation handoffs

Confidence: Plausible

safety.md lines 284, 299, 305 — The Pinned-Command Degradation handoffs now surface commands containing ${CLAUDE_PLUGIN_ROOT} that an operator may copy-paste into a shell where the variable is unset. The PR confirms CLAUDE_PLUGIN_ROOT is empirically empty in this environment; double-quoted expansion produces bash "/bin/source-control-babysit-merge" — a safe failure (no such file in /bin/), but with a misleading error message that obscures the real root cause and could send an operator on a wrong diagnostic path. A [[ -n "${CLAUDE_PLUGIN_ROOT}" ]] || { echo "CLAUDE_PLUGIN_ROOT is unset; expand to the absolute plugin path"; exit 1; } guard in the wrapper scripts (unchanged here) would make the failure unambiguous. Documented in the CHANGELOG as a known residual; not blocking.


Summary

No CRITICAL or IMPORTANT findings. The prior IMPORTANT finding (SKILL.md step 2 bare-name before convention declaration, confirmed in all three prior automated reviews) is resolved by the latest commit. One pre-existing SUGGESTION remains (operator copy-paste of ${CLAUDE_PLUGIN_ROOT} degradation commands into an unset-variable shell), already scoped in the CHANGELOG. This PR is clear from a security standpoint.

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


PR Review: post-fix pass (commit b34208c)

  • Identify trigger: PR synchronize — reviewing new commit b34208c ("remove bare-name resolve command from autopilot step 2")
  • Read updated SKILL.md to verify the blocker is resolved
  • Check consistency across all changed files
  • Post final review findings

Verdict: the blocker is resolved. All three prior automated reviews (2× code review + 1× security review, one classified IMPORTANT/Confirmed) flagged SKILL.md autopilot step 2's bare-name invocation as the only item blocking do-not-merge. Commit b34208c addresses it correctly and minimally.


What changed in b34208c

SKILL.md lines 144–151 — step 2 previously read:

resolves it via `source-control-babysit-resolve-thread owner/repo#N --allowed-owners <watched-owners>
--resolve --include-human` — bot, AI-review, and human threads alike.

Now reads:

resolves it through the guarded resolve-thread wrapper
(`--resolve --include-human` — bot, AI-review, and human threads alike); the exact command is
the single home in [reference/safety.md](reference/safety.md).

The bare runnable command is gone. The approach chosen is option (a) from the review suggestions — point at safety.md as the single home, symmetric with step 3's treatment of the merge gate. This is the right call over embedding the full bash "${CLAUDE_PLUGIN_ROOT}/bin/…" path directly: if the invocation form changes again, there is one place to update.

Correctness

Symmetry with step 3 is now complete. Step 2 and step 3 both defer to safety.md for the exact command. An autopilot agent reading steps 1 → 2 → 3 top-down can no longer issue a bare invocation before encountering the convention, reproducing the exit 127 → raw-GraphQL fallback this PR fixes.

Net line count is 0. Commit 0a8acd1 trimmed SKILL.md to 499 lines (under the 500-line quality cap). This commit swaps 3 lines for 3 lines — no cap impact.

CHANGELOG does not need updating. This is still issue #484 / version 0.15.9, and the existing 0.15.9 entry describes the overall fix correctly. The step-2 correction is part of the same fix, not a separate change.

Remaining open items (unchanged from prior reviews)

Item Status
do-not-merge label Correctly held — serialized behind #839 which has not yet landed. Drop after #839 merges and version is confirmed still leading main.
Pinned-Command Degradation handoffs embed ${CLAUDE_PLUGIN_ROOT} (operator shell may not have it set) Non-blocking residual, documented in CHANGELOG
Worker Prompt Template command in orchestration.md in prose not a code block Pre-existing, not a regression
Root cause — plugin bin/ not on Bash tool's PATH Upstream/harness gap, tracked as #843

No new concerns

The fix is strictly subtractive at the risk surface: it removes a runnable bare command and replaces it with a documentation pointer. No new execution path is introduced.


Summary: The IMPORTANT/Confirmed finding is resolved. The PR is clean and consistent. The do-not-merge hold should be dropped once sibling #839 lands and the version is reconfirmed.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Confirmed: commit `b34208c` resolves the IMPORTANT/DEFER-FORBIDDEN finding — post-fix review pass explicitly verifies step 2/step 3 symmetry is now complete and the bare-name invocation is gone. Line count unaffected (499/500, 3-for-3 line swap). Security-review re-run still in progress on this commit; will do a final check once it lands before considering this PR's review pass fully closed. `do-not-merge` remains correctly held pending #839.

@kyle-sexton

Copy link
Copy Markdown
Contributor Author

Post-fix review pass — confirmed closed. Both the re-run PR review and security-review on commit `b34208c` independently verify the IMPORTANT/DEFER-FORBIDDEN finding is resolved (step 2/step 3 now symmetric, no bare-name invocation precedes the convention, no new concerns introduced). No further findings.

This PR's review pass is done. Only the intentional `do-not-merge` hold remains — pending sibling #839 (still open, not yet merged). Will rebase + re-bump past #839's version once it lands, then flip to ready. Handing to babysit lane for ongoing watch in the meantime.

kyle-sexton added a commit that referenced this pull request Jul 21, 2026
…de-ops, work-items

Re-checked open PRs touching these plugins' plugin.json right before the version-bump
collision protocol requires it: source-control carries #839 (0.15.8) and #840 (0.15.9),
claude-ops carries #844 (0.17.2), work-items carries #826 (0.18.2) — all still open.
Bumps this PR's claims one past each plugin's current highest open-PR claim
(source-control 0.15.10, claude-ops 0.17.3, work-items 0.18.3) so this PR does not
collide at merge time regardless of which sibling lands first.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
kyle-sexton added a commit that referenced this pull request Jul 21, 2026
Resolves conflicts on source-control and work-items plugin.json/CHANGELOG.md:
main advanced source-control to 0.15.8 (#839, merged) and work-items to 0.18.2
(#826, merged) since this branch was last rebased. Also re-checked live open
PRs at merge time and found work-items now carries a new open claim, #857 at
0.19.0 (Jira adapter) — re-bumped this branch's work-items claim from 0.18.3
to 0.19.1 to stay ahead of it. source-control's 0.15.10 and claude-ops's
0.17.3 remain valid (still one past #840/#860's 0.15.9 and #844/#860's 0.17.2
open claims, respectively).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
Integrate #839 (source-control 0.15.8, babysit-readiness-gate diagnostic).
Resolved plugin.json to 0.15.9 (one past main) and stacked the 0.15.9
CHANGELOG entry above the merged-in 0.15.8 entry. SKILL.md auto-merged
(distinct regions); babysit-prs skill-quality gate green at 499/500 lines.
@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


PR Review: fix(source-control): invoke babysit mutation wrappers by bin/ path (#484)

  • Gather context (PR diff since merge with main, HEAD dc6c1de)
  • Read changed files at HEAD
  • Review plugin.json version and CHANGELOG.md
  • Review SKILL.md (step 2/3 symmetry, post-fix state)
  • Review safety.md (posture refinement)
  • Review orchestration.md (invocation points)
  • Review review-discipline.md
  • Post findings

Trigger: PR synchronize — reviewing HEAD dc6c1de (merge of origin/main into branch).

Verdict: Clean pass. The do-not-merge condition has been met.


What changed in dc6c1de

The latest commit is a merge-with-main only. It brought in two things from origin/main:

  1. The ## [0.15.8] CHANGELOG entry (from sibling fix(source-control): actionable owner/repo diagnostic on readiness-gate fetch failure #839, which has now merged).
  2. scripts/babysit-readiness-gate.sh diagnostic improvements (also from fix(source-control): actionable owner/repo diagnostic on readiness-gate fetch failure #839).

No conflicts. No change to any of the six files this PR modified. The merge is clean.


Version & CHANGELOG — do-not-merge condition satisfied

The PR body states the hold condition: "once #839 lands, drop do-not-merge and confirm the version still leads main."

origin/main is now at 0.15.8 (from #839). This branch is at 0.15.9. The CHANGELOG correctly orders ## [0.15.9] above ## [0.15.8]. The version leads main. The serialization condition is met.

See CHANGELOG.md lines 6–49 — ordering is correct.


Prior findings: all resolved or scoped

Finding Prior classification Status
SKILL.md step 2 bare-name before convention (line 146 vs line 186) IMPORTANT / Confirmed (3× reviews) Resolved in commit b34208c: step 2 now defers to safety.md for the exact command, symmetric with step 3. Verified at HEAD.
CLAUDE_PLUGIN_ROOT empty-string → /bin/<wrapper> in degradation handoffs SUGGESTION / Plausible Pre-existing residual; documented in CHANGELOG. Not blocking.
Worker Prompt Template command in prose, not a code block Observation Pre-existing; not a regression. Not blocking.

Correctness at HEAD

SKILL.md step 2/3 symmetry (lines 145–159): both steps now defer to safety.md for the exact command. No runnable bare name appears before the convention declaration.

safety.md convention coverage (lines 123–136): declares the bin/-path form above all inline bare-name shorthand references in the file, so the "every command spelled below as source-control-babysit-<x> …" convention covers them correctly in reading order.

orchestration.md invocation points (line 36, line 510, line 576): all three runnable invocation points use bash "${CLAUDE_PLUGIN_ROOT}/bin/...". Confirmed at HEAD.

SKILL.md line count: 499 (under the 500-line cap). ✓

review-discipline.md D7.5 (lines 202–206): the parenthetical correctly notes babysit tiers route through the guarded wrapper without altering the D7.5 raw-GraphQL policy for /pull-request. Scoping is accurate.


Security

No security concerns. The path-form change is confirmed not a guard-bypass re-spelling: the wrapper still self-locates via readlink -f "$0" and every guard (fail-closed without --allowed-owners, rejection of --allow-unpinned-head, thread-pin pair rule) is intact. The narrowed prohibition in safety.md (raw Python invocation and interpreter-piping, not bash "...") correctly captures only the forms that dodge the guards.


Summary

The merge with main is clean, the version serialization condition is satisfied (0.15.9 leads main's 0.15.8, #839 has landed), all prior IMPORTANT findings are resolved, and no new concerns are introduced. Ready to drop do-not-merge and proceed to merge.

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


Security Review — PR #840 (HEAD: dc6c1de)

  • Gather PR diff
  • Read changed files
  • Read wrapper scripts (bin/source-control-babysit-merge, bin/source-control-babysit-resolve-thread)
  • Analyze for security vulnerabilities
  • Post findings

Scope: Documentation-only change (.md + plugin.json version bump). No scripts or executables were modified. This pass covers the HEAD at dc6c1de, which incorporates commit b34208c (the step-2 bare-name fix) merged into the branch.


Prior IMPORTANT finding: CONFIRMED RESOLVED

The four prior automated security reviews each flagged — one at IMPORTANT/Confirmed severity — that SKILL.md autopilot step 2 retained a bare-name invocation (source-control-babysit-resolve-thread owner/repo#N …) before the bin/-path convention was declared at line 186. Commit b34208c resolved it: step 2 now reads:

resolves it through the guarded resolve-thread wrapper (--resolve --include-human — bot, AI-review, and human threads alike); the exact command is the single home in reference/safety.md.

No runnable bare name precedes the convention declaration. Steps 2 and 3 are now symmetric — both defer to safety.md for the exact command. The command not found → raw-GraphQL fallback path (which bypassed --allowed-owners enforcement and the thread-pin TOCTOU rule) is no longer reachable from the autopilot description.


No CRITICAL or IMPORTANT findings

Wrapper guards are intact. Both bin/source-control-babysit-merge and bin/source-control-babysit-resolve-thread are unchanged. Both self-locate via readlink -f "$0" (line 12 of each), which resolves correctly regardless of whether invoked by bare name, absolute path, or the bash "${CLAUDE_PLUGIN_ROOT}/bin/…" path form. The --allow-unpinned-head guard (merge wrapper lines 15–22) is in the wrapper body, not path-dependent. Both wrappers pass "$@" to the Python engine — no argument interpolation or shell expansion from PR data.

Path-form invocation is not a guard bypass. The merge wrapper's --allow-unpinned-head rejection, the fail-closed --allowed-owners check, and the babysit_merge.py deterministic engine are all inside the wrapper script, executed identically via bare-name or path invocation. The posture refinement in safety.md (narrowing the prohibition from "never through an interpreter-prefixed path" to "never the raw Python behind them") correctly captures the only re-spellings that actually dodge the guards.

No shell injection surface introduced. Arguments to the wrapper arrive as "$@" in babysit-python.sh's exec python3 "$@" — they are not interpolated into a shell string. The "${CLAUDE_PLUGIN_ROOT}/bin/…" form is double-quoted in all documented invocations, so whitespace in the path (unusual for a plugin cache path but possible) does not introduce word-splitting. PR data enters only after BEGIN QUOTED PR DATA in the worker prompt template — the command change at orchestration.md lines 576–578 is in the non-data section of the template, not inside the fence.

No GitHub Actions changes. No workflow files modified.

No credential or token handling changes. GH_TOKEN handling is unchanged.

Permission effect is a net narrowing. The path form does not match the pre-approved Bash(source-control-babysit-resolve-thread:*) allow rule, so per-call prompts now gate these invocations instead of the broad allow rule. This is strictly more restrictive than before, not less.


SUGGESTION (pre-existing, not a regression) — CLAUDE_PLUGIN_ROOT empty-string expands degradation-handoff commands to /bin/<wrapper>

Confidence: Plausible

safety.md lines 284, 299, 305 — The Pinned-Command Degradation handoffs now embed ${CLAUDE_PLUGIN_ROOT} in the commands surfaced to operators. The PR itself confirms that CLAUDE_PLUGIN_ROOT is empirically empty in this environment. Under double-quoted expansion, bash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-merge"bash "/bin/source-control-babysit-merge" — an absolute path into /bin/. The failure is safe (no such file), but:

  1. The error No such file: /bin/source-control-babysit-merge obscures the real root cause (unset variable), which could send an operator on a wrong diagnostic path.
  2. If a file named source-control-babysit-merge were ever present in /bin/ (requires system compromise or supply-chain introduction into a root-owned directory), it would execute with the full pinned argument list, including --allowed-owners values from a real PR, without any wrapper guard.

This is a pre-existing pattern (the sibling ${CLAUDE_PLUGIN_ROOT}/scripts/ invocations carry the same property); this PR adds more uses of the same form. The CHANGELOG already scopes this as a known residual. A [[ -n "${CLAUDE_PLUGIN_ROOT}" ]] || { echo "CLAUDE_PLUGIN_ROOT is unset"; exit 1; } guard in the wrapper scripts (not modified here) would make the failure unambiguous and eliminate the /bin/ path ambiguity.


Summary

No CRITICAL or IMPORTANT findings. The prior IMPORTANT/Confirmed finding (SKILL.md step 2 bare-name before convention declaration) is resolved in this HEAD. The remaining SUGGESTION is a pre-existing residual in the sibling scripts, already documented in the CHANGELOG. This PR is clear from a security standpoint.

@kyle-sexton kyle-sexton removed the do-not-merge Hard merge gate: do not merge while applied. label Jul 21, 2026
@kyle-sexton
kyle-sexton marked this pull request as ready for review July 21, 2026 16:33
@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


PR Review: fix(source-control): invoke babysit mutation wrappers by bin/ path (#484)

  • Gather context (PR state, prior reviews, trigger)
  • Read changed files at HEAD (dc6c1de)
  • Verify version & CHANGELOG ordering
  • Confirm prior findings resolved
  • Post final review

Trigger: PR marked ready_for_review — final review pass at HEAD dc6c1de (merge of origin/main into branch).

Verdict: Clean. Ready to merge.


What was verified

Core fix — All three runnable invocation points in orchestration.md use the bash "${CLAUDE_PLUGIN_ROOT}/bin/..." path form:

  • Fan-out gate (line 36): bash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-merge" owner/repo#42 …
  • Worker Contract auto-resolve (line 510): bash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-resolve-thread" …
  • Worker Prompt Template (line 576): bash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-resolve-thread" …

Prior IMPORTANT finding — resolved. SKILL.md autopilot step 2 (lines 145–151) no longer contains a bare-name invocation. It now defers to safety.md for the exact command, symmetric with step 3. The command not found → raw-GraphQL fallback path (which bypassed --allowed-owners and the thread-pin TOCTOU rule) is closed. ✓

safety.md posture (lines 115–140) — Correctly prescribes the bin/-path form, explains that bare names are absent from PATH, and narrows the prohibition to raw Python invocation and interpreter-piping only. ✓

review-discipline.md D7.5 (lines 202–206) — Parenthetical correctly notes babysit tiers use the guarded wrapper without altering the D7.5 raw-GraphQL policy for /pull-request. ✓

Version & CHANGELOGplugin.json at 0.15.9; CHANGELOG.md orders ## [0.15.9] above ## [0.15.8] (from #839, which has landed). The 0.15.9 entry accurately describes both the fix and the two known residuals. Serialization condition satisfied. ✓


Security

No concerns. The path-form invocation is confirmed not a guard bypass: the merge wrapper still self-locates via readlink -f "$0", still rejects --allow-unpinned-head (exit 2), and both wrappers still fail closed without --allowed-owners (exit 3). The prohibition narrowing in safety.md (from "never through an interpreter-prefixed path" to "never raw Python, never pipe to an interpreter") correctly captures only the re-spellings that actually dodge the guards.


Remaining non-blocking items (unchanged from prior reviews)

Item Status
CLAUDE_PLUGIN_ROOT empty-string expands degradation-handoff commands to /bin/<wrapper> (safe failure, misleading error) Pre-existing residual; documented in CHANGELOG. Tracked as #843.
Worker Prompt Template command in orchestration.md in prose, not a fenced block Pre-existing; not a regression.
Bare-name allow-rule mismatch (per-call prompts now replace broad allow-rule match) Documented in CHANGELOG. Resolves when upstream PATH gap is fixed (#843).

Summary: All five prior automated review passes (2× PR review, 2× security review, 1× final-pass re-run) are consistent. The core fix is sound, the IMPORTANT/Confirmed finding is resolved, and the serialization condition is met. This PR is clean.

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


Security Review — PR #840 (HEAD: dc6c1de)

  • Gather PR diff and identify changed files
  • Read changed files at HEAD
  • Read wrapper scripts (unchanged, for guard verification)
  • Analyze for security vulnerabilities
  • Post findings

Scope: Documentation-only change (.md + plugin.json version bump). No scripts or executables were modified. This pass covers HEAD dc6c1de, which includes commit b34208c (SKILL.md step 2 fix) merged with origin/main.


No CRITICAL or IMPORTANT findings

Prior IMPORTANT finding: CONFIRMED RESOLVED

All prior automated security reviews flagged — one at IMPORTANT/Confirmed severity — that SKILL.md autopilot step 2 retained a bare-name invocation before the bin/-path convention was declared, leaving the command not found → raw-GraphQL fallback path (which bypasses --allowed-owners enforcement and the thread-pin TOCTOU rule) reachable from the primary autopilot description.

Commit b34208c resolves this. SKILL.md lines 145–147 now reads:

resolves it through the guarded resolve-thread wrapper (--resolve --include-human — bot, AI-review, and human threads alike); the exact command is the single home in reference/safety.md.

No runnable bare name precedes the convention. Steps 2 and 3 are now symmetric — both defer to safety.md. The finding is closed.

Path-form invocation: not a guard bypass

Both bin/source-control-babysit-merge and bin/source-control-babysit-resolve-thread are unchanged and self-locate via readlink -f "$0" (line 12 of each) — their operation is independent of CLAUDE_PLUGIN_ROOT and identical whether invoked by bare name or absolute path. The merge wrapper's --allow-unpinned-head rejection (lines 15–22) is in the wrapper body, not path-dependent. Arguments reach the Python engine via "$@" in babysit_python, not through shell interpolation. The path-form change is a safe re-spelling.

No shell injection surface introduced

All updated invocations in orchestration.md and safety.md use "${CLAUDE_PLUGIN_ROOT}/bin/<wrapper>" with double-quotes, protecting against word-splitting on path whitespace. CLAUDE_PLUGIN_ROOT is harness-set, not user-supplied input. The Worker Prompt Template's BEGIN QUOTED PR DATA / END QUOTED PR DATA fence is unchanged — the updated command at orchestration.md lines 576–578 appears in the instructions section, above and outside the data fence, so PR content cannot inject into it.

No other security surfaces touched

  • No GitHub Actions files modified.
  • No credential/token handling changed.
  • Permission effect is a net narrowing: per-call prompts now gate these invocations instead of the broad bare-name allow rule.

SUGGESTION (pre-existing, not a regression) — CLAUDE_PLUGIN_ROOT empty-string in degradation-handoff commands

Confidence: Plausible

safety.md lines 284, 299, 305 — Pinned-Command Degradation handoffs now embed ${CLAUDE_PLUGIN_ROOT} in commands an operator may copy-paste into a shell. The PR confirms CLAUDE_PLUGIN_ROOT is empirically empty in this environment. Under double-quoted shell expansion, bash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-merge"bash "/bin/source-control-babysit-merge" — a safe failure (no such file), but with a misleading error (No such file or directory: /bin/source-control-babysit-merge) that obscures the real cause (unset variable). This is a pre-existing pattern shared by the sibling ${CLAUDE_PLUGIN_ROOT}/scripts/ invocations (unchanged), already scoped as a known residual in the CHANGELOG. Not a regression introduced by this PR.


Summary

No CRITICAL or IMPORTANT findings. The sole IMPORTANT finding from all prior automated security reviews (SKILL.md step 2 bare-name before convention) is confirmed resolved at this HEAD. One pre-existing SUGGESTION remains (CLAUDE_PLUGIN_ROOT empty-string expansion in degradation handoffs), already documented in the CHANGELOG. This PR is clear from a security standpoint.

kyle-sexton added a commit that referenced this pull request Jul 21, 2026
Re-syncs onto main after #860 (fleet-wide `shell: bash` declaration sweep)
merged, which bumped source-control to 0.15.9, claude-ops to 0.17.2, and
repo-hygiene to 0.4.5. Resolved conflicts on those three plugins' plugin.json
and CHANGELOG.md; re-derived each held version against the new main plus a
fresh live-open-PR check rather than assuming the prior numbers still held:

- source-control 0.15.10: still one past main's 0.15.9 and #840's live 0.15.9
  claim.
- claude-ops 0.17.3: still one past main's 0.17.2 and #844's live 0.17.2
  claim.
- repo-hygiene 0.4.6: still one past main's new 0.4.5 (#860, now merged); no
  open-PR collision remains for this plugin.
- work-items 0.19.1 and guardrails 0.9.6 were untouched by this merge (no
  conflict) and remain correct against #857/#861 (both still 0.19.0, open)
  and no open collision, respectively.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
Integrate #860 (fleet-wide `shell: bash` skill-frontmatter chore, which merged
source-control to 0.15.9). Re-derived this PR's bump to 0.15.10 (one past current
main) and re-labeled the 0.15.9 CHANGELOG entry to 0.15.10, stacked above the
merged-in 0.15.9 (shell: bash) entry. SKILL.md auto-merged (my body edits +
#860's frontmatter, distinct regions); babysit-prs skill-quality gate green at
499/500 lines.
@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


PR Review: fix(source-control): invoke babysit mutation wrappers by bin/ path (#484)

  • Gather context (PR synchronize trigger, prior review history)
  • Check what came in via the latest origin/main merge (93a0b82 vs dc6c1de)
  • Verify version still leads main
  • Confirm no regressions in the 6 changed files
  • Post final verdict

Trigger: PR synchronize — reviewing HEAD 93a0b82 (another merge of origin/main into branch).

Verdict: Clean pass. No regressions.


What came in via 93a0b82

Three commits from origin/main arrived in this merge:

Commit Description Source-control plugin impact
7688ae7 chore: declare shell: bash on every skill with dynamic-context injections Adds shell: bash to babysit-prs/SKILL.md frontmatter; bumps version 0.15.9 → 0.15.10; adds ## [0.15.9] CHANGELOG entry
560d7ac docs(topics): boris-video-absorption Phase 8 flip None
21b1b86 feat(naming): retro tournament lessons into name-it-better None

The only material change to the source-control plugin is the shell: bash addition, which is a safe cross-cutting change from main with no interaction with this PR's fix.


Version & CHANGELOG — serialization re-satisfied

origin/main is now at 0.15.9 (for the shell: bash chore). This branch is correctly at 0.15.10. CHANGELOG ordering is:

## [0.15.10]  ← this PR's fix (babysit bin/-path invocation)
## [0.15.9]   ← shell: bash on all skills (came from main)
## [0.15.8]   ← #839 babysit-readiness-gate diagnostic

The version leads main (0.15.10 > 0.15.9). The serialization condition from the prior hold is re-satisfied after this merge.


Core fix — no regressions

All five prior review passes verified the core fix; confirmed again at this HEAD:

SKILL.md step 2/3 symmetry — both steps defer to safety.md for the exact command. No runnable bare name appears before the convention declaration. The command not found → raw-GraphQL fallback path remains closed. ✓

orchestration.md invocation points — all three use the bash "${CLAUDE_PLUGIN_ROOT}/bin/..." path form:

safety.md posturebin/-path convention declared above all inline bare-name shorthand references; prohibition correctly narrowed to raw-Python invocation and interpreter-piping only. ✓

review-discipline.md D7.5 — parenthetical intact; D7.5 raw-GraphQL policy for /pull-request unchanged. ✓


Security

No new concerns. The shell: bash frontmatter addition is inert from a security standpoint — it constrains the precompute host, it does not widen any execution surface. All wrapper guards confirmed intact (unchanged scripts throughout).


Remaining non-blocking items (unchanged from prior reviews)

Item Status
CLAUDE_PLUGIN_ROOT empty-string expands degradation-handoff commands to /bin/<wrapper> (safe failure, misleading error) Pre-existing residual; documented in CHANGELOG.
Worker Prompt Template command in orchestration.md in prose, not a fenced block Pre-existing; not a regression.
Bare-name allow-rule mismatch (per-call prompts now replace broad allow-rule match) Documented in CHANGELOG. Resolves when upstream PATH gap is fixed (#843).

Summary: The merge with origin/main is clean. The version correctly re-serializes to 0.15.10 (leading main's 0.15.9). The core fix is intact with no regressions across all six changed files. All prior IMPORTANT findings remain resolved. This PR is ready to merge once the do-not-merge label is dropped.

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


Security Review — PR #840 (HEAD: 93a0b82)

  • Gather PR diff
  • Read changed files at HEAD
  • Read wrapper scripts (guard verification)
  • Analyze for security vulnerabilities
  • Post findings

Scope: Documentation-only change (.md + plugin.json version bump). No scripts or executables were modified. HEAD 93a0b82 is a merge-with-main commit; the authored content is from dc6c1de and earlier commits on this branch. Both wrapper scripts (bin/source-control-babysit-merge, bin/source-control-babysit-resolve-thread) are confirmed unchanged.


No CRITICAL or IMPORTANT findings

Prior IMPORTANT finding: CONFIRMED RESOLVED

All prior automated security reviews flagged — one at IMPORTANT/Confirmed severity — that SKILL.md autopilot step 2 retained a bare-name invocation before the bin/-path convention was declared, leaving the command not found → raw-GraphQL fallback path (bypassing --allowed-owners enforcement and the thread-pin TOCTOU rule) reachable from the primary autopilot description. That finding is resolved. SKILL.md lines 146–148 now reads:

resolves it through the guarded resolve-thread wrapper (--resolve --include-human — bot, AI-review, and human threads alike); the exact command is the single home in reference/safety.md.

No runnable bare name precedes the convention declaration. Steps 2 and 3 are symmetric.

Path-form invocation is not a guard bypass

Both bin/source-control-babysit-merge and bin/source-control-babysit-resolve-thread are confirmed unchanged and self-locate via readlink -f "$0" (line 12 of each), so their guards execute identically whether invoked by bare name or absolute path. The merge wrapper's --allow-unpinned-head rejection (lines 15–22) is entirely inside the wrapper body. Both pass "$@" to the Python engine — no argument interpolation from PR data.

Worker Prompt Template injection fence intact

The BEGIN QUOTED PR DATA / END QUOTED PR DATA fence in orchestration.md lines 555–565 is unchanged. The updated resolve command at lines 576–579 appears in the instruction prose section, above and outside the data fence — PR-derived content cannot inject into it.

Permission effect is a net tightening

The bin/-path form does not match the pre-approved Bash(source-control-babysit-merge:*) / Bash(source-control-babysit-resolve-thread:*) allow rules, so per-call prompts now gate these invocations instead of the broad allow rules. This is documented in the CHANGELOG as a known residual. It is strictly more restrictive than before — a security improvement, not a widening.

No GitHub Actions, credential-handling, or permission-widening changes

No workflow files modified. No changes to GH_TOKEN handling or any secret surface. The plugin.json change is a version bump only.

Merge commit (93a0b82) is clean

The latest commit brings in 0.15.8 CHANGELOG entry and scripts/babysit-readiness-gate.sh diagnostic improvements from sibling #839. No conflicts, no new execution surfaces introduced by the merge.


SUGGESTION (pre-existing, not a regression) — CLAUDE_PLUGIN_ROOT empty-string in degradation-handoff commands

Confidence: Plausible

safety.md lines 284, 299, 305 — Pinned-Command Degradation handoffs embed ${CLAUDE_PLUGIN_ROOT} in commands an operator may copy-paste into a shell. The PR confirms CLAUDE_PLUGIN_ROOT is empirically empty in this environment. Under double-quoted shell expansion, bash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-merge"bash "/bin/source-control-babysit-merge" — a safe failure (no such file), but with a misleading error (No such file or directory: /bin/source-control-babysit-merge) that obscures the real root cause (unset variable). A [[ -n "${CLAUDE_PLUGIN_ROOT}" ]] || { echo "CLAUDE_PLUGIN_ROOT is unset"; exit 1; } guard in the wrapper scripts (not modified here) would make the failure unambiguous. Pre-existing pattern shared by sibling ${CLAUDE_PLUGIN_ROOT}/scripts/ invocations; already scoped as a known residual in the CHANGELOG. Not a regression.


Summary

No CRITICAL or IMPORTANT findings. The sole IMPORTANT finding from all prior automated security reviews (SKILL.md step 2 bare-name before convention declaration) is confirmed resolved at this HEAD. The PR is clean from a security standpoint.

@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: 93a0b82cf2

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread plugins/source-control/reference/review-discipline.md Outdated
kyle-sexton added a commit that referenced this pull request Jul 21, 2026
#840 rebased since this branch's last check and re-derived its own claim
from 0.15.9 to 0.15.10, so this PR's prior 0.15.10 now collides exactly.
Bump to 0.15.11 (one past #840's current live claim). claude-ops (0.17.3),
work-items (0.19.1), repo-hygiene (0.4.6), and guardrails (0.9.6) were
independently re-verified against current main and all live open PRs and
remain correct — no change needed for those four.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
kyle-sexton added a commit that referenced this pull request Jul 21, 2026
#857 (Jira Cloud adapter) merged into main since this branch's last rebase,
bumping work-items to 0.19.0. Resolved the resulting conflict, keeping this
branch's 0.19.1 (already one past both the new main and #861's still-open
0.19.0 claim, so no further bump needed). Re-verified all 5 plugins fresh
against current main and every live open PR (#840 0.15.10, #844 0.17.2, #861
0.19.0): source-control 0.15.11, claude-ops 0.17.3, repo-hygiene 0.4.6, and
guardrails 0.9.6 all remain correct with no change required.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
kyle-sexton added a commit that referenced this pull request Jul 21, 2026
An unrelated claude-ops docs PR (#877, closing #864) merged since this
branch's last rebase and landed at exactly 0.17.3 — the same version this
branch was claiming. Re-bumped claude-ops to 0.17.4 to clear the exact
collision (plugin.json auto-merged to an identical-looking "0.17.3" string
without flagging a conflict, since both sides happened to write the same
literal text for unrelated reasons; caught by diffing main's version history
directly, not by trusting a clean auto-merge). CHANGELOG.md split into two
sections (0.17.3 for #877's actual change, 0.17.4 for this PR's).

Also swept for new open PRs against all 5 plugins post-merge: #870
(feat/800-audit-instructions) newly touches repo-hygiene (claims 0.4.5) and
claude-ops (claims 0.17.2) — both below this branch's current claims
(0.4.6 / 0.17.4), no collision. source-control (0.15.11), work-items
(0.19.1), and guardrails (0.9.6) re-confirmed against current main and every
live open PR (#840 0.15.10, #844 0.17.2, #861 0.19.0) — unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
kyle-sexton added a commit that referenced this pull request Jul 21, 2026
claude-ops: #844 rebased and re-derived its claim from 0.17.2 to 0.17.4,
colliding exactly with this branch's prior 0.17.4. Bump to 0.17.5.

source-control: a new PR, #882 (fix/511-babysit-self-identity-decouple),
claims 0.16.0 — a minor bump above this branch's prior 0.15.11 and above
#840's 0.15.10. Bump to 0.16.1 to stay ahead of both.

repo-hygiene (0.4.6), guardrails (0.9.6), and work-items (0.19.1) were
re-verified fresh against current main and every live open PR
(main: repo-hygiene 0.4.5, guardrails 0.9.5, work-items 0.19.0; #861 0.19.0)
and remain correct, no change needed.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
kyle-sexton added a commit that referenced this pull request Jul 21, 2026
#844 (claude-ops) merged into main at exactly 0.17.4, which this branch's
prior 0.17.5 was already one past — kept as-is, no re-bump needed for the
number itself, just resolved the resulting plugin.json/CHANGELOG.md conflict.

Full fresh collision sweep after the merge found claude-ops now has NO open-PR
collision at all. It also found #861 (work-items) re-derived its own claim
from 0.19.0 to 0.20.0 since the last check (following #857's Jira-adapter
minor bump into main) — colliding with this branch's prior 0.19.1. Re-bumped
work-items to 0.20.1.

source-control (0.16.1, held behind #882's 0.16.0 and #840's 0.15.10),
repo-hygiene (0.4.6, no collision), and guardrails (0.9.6, no collision)
re-verified against current main and all live open PRs — unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
…lve scope

The parenthetical added to D7.5 claimed the bot-only, never-human
author-conditional rule applied as the babysit "tier discipline" for both
the worker and autopilot tiers. The autopilot tier deliberately resolves
addressed human threads (`--resolve --include-human`; SKILL.md, safety.md),
so that blanket claim contradicts the autopilot contract — a worker taking
it literally would leave addressed human threads unresolved and keep the PR
blocked on conversation resolution.

Keep the wrapper-vs-raw-GraphQL pointer, drop the over-generalization, and
state each tier's own resolve author-scope: worker matches this bot-only
rule; autopilot extends to addressed human threads, per that skill's
reference/safety.md.

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


PR Review: fix(source-control): invoke babysit mutation wrappers by bin/ path (#484)

  • Gather context (trigger: PR synchronize, HEAD 716915c)
  • Read the changed file (reference/review-discipline.md) at HEAD
  • Verify the D7.5 per-tier scope fix addresses the Codex finding
  • Check consistency with SKILL.md and safety.md
  • Post final verdict

Trigger: PR synchronize — reviewing HEAD 716915c ("correct D7.5 babysit-tier note to per-tier resolve scope").

Verdict: Clean. No new concerns. This PR is ready to merge.


What changed in 716915c

review-discipline.md lines 202–207 — The parenthetical that introduced the wrapper-vs-raw-GraphQL pointer in the base PR previously read:

…which adds an --allowed-owners allowlist, bot-vs-human classification, and a JSON receipt — with this author-conditional rule applied as that skill's tier discipline, rather than the raw-GraphQL form below.

Now reads:

…which adds an --allowed-owners allowlist, bot-vs-human classification, and a JSON receipt — rather than the raw-GraphQL form below. Each tier sets its own resolve author-scope: the worker tier matches this bot-only rule; the autopilot tier deliberately extends to addressed human threads (--include-human), as documented in that skill's reference/safety.md.


Codex finding: confirmed resolved

The Codex P2 finding was valid: the old blanket claim ("this author-conditional rule applied as that skill's tier discipline") covered both worker and autopilot tiers, but autopilot deliberately resolves addressed human threads — which directly contradicts D7.5's "NEVER resolve HUMAN-authored threads" policy. A worker following the old text literally would leave addressed human threads unresolved and keep the PR blocked on conversation resolution.

The correction is well-supported by the cited sources:

  • safety.md lines 160–162: explicitly distinguishes --autonomous --resolve (worker, bot threads only) from --resolve --include-human (autopilot's addressed-thread widening)
  • SKILL.md line 40: autopilot "resolve every thread it has addressed (bot, AI-review, and human)"
  • SKILL.md line 147: autopilot step 2 explicitly uses --resolve --include-human — bot, AI-review, and human threads alike

The fix is minimal (4 lines in the parenthetical), accurate, and defers to reference/safety.md as the authority for the autopilot scope rather than re-stating it inline. No CHANGELOG update is needed — this is a within-PR accuracy correction to the pointer added by this PR, not a separate version bump.


All prior findings: status unchanged

Finding Status
SKILL.md step 2 bare-name before convention (IMPORTANT, Confirmed — 3× reviews) Resolved — commit b34208c. Verified at HEAD.
D7.5 parenthetical over-generalizes bot-only rule to autopilot tier (Codex P2) Resolved — commit 716915c. Verified at HEAD.
CLAUDE_PLUGIN_ROOT empty-string in degradation-handoff commands (SUGGESTION) Pre-existing residual; documented in CHANGELOG. Not blocking.
Worker Prompt Template command in orchestration.md in prose, not a fenced block Pre-existing; not a regression. Not blocking.
Bare-name allow-rule mismatch (per-call prompts replace broad allow-rule match) Documented in CHANGELOG. Resolves with upstream PATH gap fix (#843).

Security

No security concerns. The D7.5 correction is documentation-only; no execution surface changed. All wrapper guards confirmed intact through prior passes (unchanged scripts). No GitHub Actions, credential handling, or permission-widening changes.


Version & CHANGELOG

Plugin at 0.15.10, correctly leading origin/main's 0.15.9. The 0.15.10 CHANGELOG entry at CHANGELOG.md lines 7–30 accurately describes both the primary fix and its known residuals. The D7.5 per-tier correction is an intra-PR accuracy fix and does not require an additional version increment.


Summary: Both blocking findings (SKILL.md step 2 bare-name, Codex P2 D7.5 over-generalization) are resolved. No new concerns introduced by 716915c. This PR is clean and ready to merge.
| Branch

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


Security Review — PR #840 (HEAD: 716915c)

  • Gather PR diff (commit 716915c — latest push)
  • Read changed files at HEAD
  • Read wrapper scripts (guard verification)
  • Analyze for security vulnerabilities
  • Post findings

Scope: Documentation-only change (.md + plugin.json version bump). No scripts or executables were modified. This pass covers HEAD 716915c, which adds one new commit on top of the previously reviewed 93a0b82.


New commit: 716915c — D7.5 per-tier resolve scope correction

The only file changed in the latest commit is review-discipline.md lines 202–207. The prior wording stated the bot-only author-conditional rule applied "as that skill's tier discipline" to both the worker and autopilot tiers. The new wording:

  • Keeps the wrapper-vs-raw-GraphQL pointer intact (both tiers route through the guarded wrapper, not raw gh api graphql resolveReviewThread)
  • Correctly states the worker tier matches the bot-only rule at D7.5
  • Correctly states the autopilot tier extends to addressed human threads (--include-human), pointing at safety.md as the authoritative source for that scope

Security assessment of this change: No concerns. The prior over-claim did not widen the actual execution surface — the wrappers enforced their own scoping regardless of what the documentation said. The correction is accurate: autopilot's --include-human is governed by the same --allowed-owners guardrail and thread-pin pair rule as every other resolve invocation; the wrapper still fails closed without --allowed-owners and still requires --expected-comment-count + --expected-last-updated pins. The authorization layer is in the wrapper script (unchanged), not in this prose.


Prior IMPORTANT finding: CONFIRMED STILL RESOLVED

All prior automated security reviews flagged — one at IMPORTANT/Confirmed severity — that SKILL.md autopilot step 2 retained a bare-name invocation before the bin/-path convention was declared, leaving the command not found → raw-GraphQL fallback path reachable from the primary autopilot description. That finding was resolved in commit b34208c and is unaffected by the new commit. SKILL.md lines 146–148 still reads:

resolves it through the guarded resolve-thread wrapper (--resolve --include-human — bot, AI-review, and human threads alike); the exact command is the single home in reference/safety.md.

No runnable bare name, no regression.


No CRITICAL or IMPORTANT findings

Wrapper guards are intact and unchanged. Both bin/source-control-babysit-resolve-thread and bin/source-control-babysit-merge are confirmed unchanged throughout this PR. Both self-locate via readlink -f "$0" (line 10/12 respectively) — their guards execute identically regardless of invocation form. The --allow-unpinned-head rejection, fail-closed --allowed-owners check, and "$@" argument passthrough (no shell interpolation of PR data) are all intact.

review-discipline.md D7.5 wrapper pointer is intact. The new parenthetical still says both babysit tiers run through the guarded wrapper "rather than the raw-GraphQL form below." The security-relevant pointer (wrapper vs. raw GraphQL) is preserved; only the tier-specific author-scope description was corrected.

No shell injection surface. The updated prose describes what agents should invoke, not a directly-executed string. No eval paths, no heredocs, no user-controlled input in command construction.

Permission effect is unchanged from prior pass. The bin/-path form still does not match pre-approved Bash(source-control-babysit-resolve-thread:*) allow rules; per-call prompts continue to gate these invocations.


SUGGESTION (pre-existing, not a regression) — CLAUDE_PLUGIN_ROOT empty-string in degradation-handoff commands

Confidence: Plausible

safety.md lines 284, 299, 305 — Pinned-Command Degradation handoffs embed ${CLAUDE_PLUGIN_ROOT} in commands an operator may copy-paste. The PR confirms CLAUDE_PLUGIN_ROOT is empirically empty in this environment; double-quoted expansion produces bash "/bin/source-control-babysit-merge" — a safe failure (no such file), but with a misleading error. Already documented in the CHANGELOG as a known residual. Not a regression from any commit in this PR; not blocking.


Summary

No CRITICAL or IMPORTANT findings. The latest commit (716915c) is a documentation correction that accurately aligns the D7.5 per-tier resolve scope description with the actual autopilot contract. No execution surface is changed; no guardrails are weakened. All prior IMPORTANT findings remain resolved. The pre-existing SUGGESTION (CLAUDE_PLUGIN_ROOT empty-string expansion in degradation handoffs) is unchanged.

kyle-sexton added a commit that referenced this pull request Jul 21, 2026
#882 (source-control) merged into main at 0.16.0, which this branch's prior
0.16.1 was already one past — resolved the resulting plugin.json/CHANGELOG.md
conflict, kept as-is.

Full fresh collision sweep after the merge found source-control now carries
TWO additional open PRs beyond #840: #895 (fix/548-babysit-worktree-head-safety,
claims 0.15.10) and #898 (feat/399-shared-worktree-helper, claims 0.17.0 — a
minor bump above this branch's prior 0.16.1). Re-bumped source-control to
0.17.1 to stay ahead of all three (#898's 0.17.0, #895's and #840's 0.15.10).

claude-ops (0.17.5, no collision), work-items (0.20.1, held behind #861's
0.20.0), repo-hygiene (0.4.6, no collision), and guardrails (0.9.6, no
collision) re-verified against current main and all live open PRs — unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K1V3gkrfSf75isB8MiDy3o
Integrate the latest main (source-control advanced to 0.16.0 via #511's
pr_queue_snapshot self-identity work, plus #882). Re-derived this PR's bump to
0.16.1 (one past current main) and re-labeled the CHANGELOG entry from 0.15.10 to
0.16.1, stacked above the merged-in 0.16.0 entry. safety.md, orchestration.md,
review-discipline.md, and SKILL.md auto-merged (my bin/-path edits and the
upstream changes touch distinct regions); babysit-prs skill-quality gate green at
499/500 lines.
…ath' into fix/484-babysit-resolve-thread-path
@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


PR Review: fix(source-control): invoke babysit mutation wrappers by bin/ path (#484)

  • Gather context (trigger: PR synchronize, HEAD 78fb5e2)
  • Check what came in via latest origin/main merge
  • Verify version still leads main
  • Confirm no regressions in the 6 changed files
  • Post final verdict

Trigger: PR synchronize — reviewing HEAD 78fb5e2 (re-merge to reconcile remote branch after 716915c was pushed).

Verdict: Clean pass. No regressions.


What came in via 78fb5e2 and 51770af

Two merge commits arrived since the prior review at 716915c:

Commit Description Source-control plugin impact
51770af Merge origin/main (source-control advanced to 0.16.0 via #511 pr_queue_snapshot self-identity work) Re-derives this PR's bump to 0.16.1 (one past current main); CHANGELOG re-labeled from 0.15.100.16.1
78fb5e2 Re-merge remote branch into local (picking up 716915c D7.5 fix) review-discipline.md — the per-tier scope correction (+4/-3 lines)

The ce1c50c test commit from main (test(claude-config): add step-list-culling eval case) arrived in 51770af with no source-control plugin impact.


Version & CHANGELOG — serialization satisfied

origin/main is now at 0.16.0. This branch is at 0.16.1. The CHANGELOG orders ## [0.16.1] above ## [0.16.0]. The version leads main. ✓


Core fix — no regressions

SKILL.md step 2/3 symmetry (lines 147–163) — both steps defer to safety.md for the exact command. No runnable bare name appears before the convention declaration. The command not found → raw-GraphQL fallback path (bypassing --allowed-owners and the thread-pin TOCTOU rule) remains closed. ✓

orchestration.md invocation points — all three runnable references use the bash "${CLAUDE_PLUGIN_ROOT}/bin/..." path form:

safety.md posturebin/-path convention declared above all inline bare-name shorthand references; prohibition correctly narrowed to raw-Python invocation and interpreter-piping only. ✓

review-discipline.md D7.5 (lines 202–211) — the parenthetical correctly states each tier's own resolve author-scope (worker = bot-only matching D7.5; autopilot = deliberately extends to addressed human threads via --include-human), pointing at safety.md as authority. The Codex P2 over-generalization is resolved. ✓

SKILL.md line count: 499/500 (under the quality cap). ✓


Security

No concerns. Both wrapper scripts (bin/source-control-babysit-merge, bin/source-control-babysit-resolve-thread) are confirmed unchanged throughout this PR. All guards survive the path-form invocation — the merge wrapper still self-locates via readlink -f "$0", still rejects --allow-unpinned-head (exit 2), and both wrappers still fail closed without --allowed-owners (exit 3).


Remaining non-blocking items (unchanged from prior reviews)

Item Status
CLAUDE_PLUGIN_ROOT empty-string expands degradation-handoff commands to /bin/<wrapper> (safe failure, misleading error) Pre-existing residual; documented in CHANGELOG. Tracked as #843.
Worker Prompt Template command in orchestration.md in prose, not a fenced block Pre-existing; not a regression.
Bare-name allow-rule mismatch (per-call prompts now replace broad allow-rule match) Documented in CHANGELOG. Resolves when upstream PATH gap is fixed (#843).

Summary: The re-merge is clean. Version correctly advances to 0.16.1 (leading main's 0.16.0). All prior IMPORTANT/Confirmed findings — the SKILL.md step 2 bare-name ordering issue and the Codex P2 D7.5 over-generalization — are resolved and unaffected by the merge. No new concerns. This PR is ready to merge.
| Branch

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


Security Review — PR #840 (HEAD: 78fb5e2)

  • Gather PR diff (gh pr diff)
  • Read changed files at HEAD
  • Read wrapper scripts (guard verification)
  • Analyze for security vulnerabilities
  • Post findings

Scope: Documentation-only change (.md + plugin.json version bump). No scripts or executables were modified. HEAD 78fb5e2 is a merge-with-remote-tracking-branch commit; the only file it changed is review-discipline.md (absorbing the 716915c Codex P2 fix). Both wrapper scripts (bin/source-control-babysit-merge, bin/source-control-babysit-resolve-thread) are confirmed unchanged throughout this PR.


No CRITICAL or IMPORTANT findings

Prior IMPORTANT finding: CONFIRMED STILL RESOLVED

All prior automated security reviews flagged — one at IMPORTANT/Confirmed severity — that SKILL.md autopilot step 2 retained a bare-name invocation before the bin/-path convention was declared, leaving the command not found → raw-GraphQL fallback path (bypassing --allowed-owners enforcement and the thread-pin TOCTOU rule) reachable from the primary autopilot description. Commit b34208c resolved it, and it remains resolved at this HEAD.

SKILL.md lines 146–148 reads:

resolves it through the guarded resolve-thread wrapper (--resolve --include-human — bot, AI-review, and human threads alike); the exact command is the single home in reference/safety.md.

No runnable bare name precedes the convention declaration. Steps 2 and 3 are symmetric.

What came in via 78fb5e2 — D7.5 per-tier scope correction is safe

The merge brought in 716915c's review-discipline.md change: the D7.5 parenthetical now correctly states each tier's own resolve author-scope (worker = bot-only; autopilot = extends to addressed human threads via --include-human), instead of the prior over-claim that the bot-only rule applied as the blanket tier discipline for both. This is security-correct: --include-human widens which thread types the autopilot resolves, but does not widen the authorization guardrails — the --allowed-owners check, the thread-pin pair rule (--thread-id, --expected-comment-count, --expected-last-updated), and the fail-closed exit 3 on missing --allowed-owners are all enforced in the wrapper itself (unchanged scripts), not in this prose.

Path-form invocation: not a guard bypass

Both bin/source-control-babysit-merge and bin/source-control-babysit-resolve-thread self-locate via readlink -f "$0" (line 12 of each) and operate identically whether invoked by bare name or absolute path. The merge wrapper's --allow-unpinned-head rejection (lines 15–22) is in the wrapper body, not path-dependent. Both pass "$@" to the Python engine — no argument interpolation of PR data.

Worker Prompt Template injection fence intact

The BEGIN QUOTED PR DATA / END QUOTED PR DATA fence in orchestration.md is unchanged. The updated command at lines 576–579 appears in the instructions section, above and outside the data fence — PR-derived content cannot inject into it.

No shell injection surface introduced

All updated invocations use "${CLAUDE_PLUGIN_ROOT}/bin/<wrapper>" with double-quotes, protecting against word-splitting on path whitespace. CLAUDE_PLUGIN_ROOT is harness-set, not user-supplied input.

Permission effect is a net tightening

The bin/-path form does not match pre-approved Bash(source-control-babysit-merge:*) / Bash(source-control-babysit-resolve-thread:*) allow rules; per-call prompts now gate these invocations instead of broad allow rules. Documented in the CHANGELOG. Strictly more restrictive than before.

No GitHub Actions, credential-handling, or permission-widening changes

No workflow files modified. No changes to GH_TOKEN handling. plugin.json change is version bump only (0.16.1, leading main's 0.16.0).


SUGGESTION (pre-existing, not a regression) — CLAUDE_PLUGIN_ROOT empty-string in degradation-handoff commands

Confidence: Plausible

safety.md lines 287, 303, 309 — Pinned-Command Degradation handoffs embed ${CLAUDE_PLUGIN_ROOT} in commands an operator may copy-paste into a shell. The PR confirms CLAUDE_PLUGIN_ROOT is empirically empty in this environment. Under double-quoted shell expansion, bash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-merge"bash "/bin/source-control-babysit-merge" — a safe failure (no such file in /bin/), but with a misleading error message (No such file or directory: /bin/source-control-babysit-merge) that obscures the real root cause (unset variable). A guard such as [[ -n "${CLAUDE_PLUGIN_ROOT}" ]] || { echo "CLAUDE_PLUGIN_ROOT is unset"; exit 1; } in the wrapper scripts (not modified here) would make the failure unambiguous. Pre-existing pattern shared by the sibling ${CLAUDE_PLUGIN_ROOT}/scripts/ invocations; already documented in the CHANGELOG as a known residual. Not a regression.


Summary

No CRITICAL or IMPORTANT findings. The sole IMPORTANT/Confirmed finding from all prior automated security reviews (SKILL.md step 2 bare-name before convention declaration) remains resolved at this HEAD. The new content in 78fb5e2 (D7.5 per-tier scope correction from 716915c) introduces no security concerns — it corrects documentation accuracy without changing any execution surface or widening any authorization guardrail. One pre-existing SUGGESTION remains (CLAUDE_PLUGIN_ROOT empty-string expansion in degradation handoffs), already scoped in the CHANGELOG. This PR is clear from a security standpoint.
| Branch

@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: 78fb5e2373

ℹ️ About Codex in GitHub

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

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

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

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

Comment thread plugins/source-control/skills/babysit-prs/SKILL.md Outdated
…mutation section

The Guarded mutations declaration prohibited the bare command name, yet the
bullets immediately below spelled each wrapper bare, so a reader copying them
hits the command-not-found path this change exists to close. State once that
each `source-control-babysit-<x>` spelling in those bullets is the wrapper
launched by its `bin/`-path form, mirroring the convention safety.md already
declares over its own shorthand — no per-bullet command duplication.
@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


PR Review: fix(source-control): invoke babysit mutation wrappers by bin/ path (#484)

  • Gather context (trigger: PR synchronize, HEAD 01c6fb2)
  • Get the PR diff for the new commit
  • Read changed files at HEAD
  • Verify the Codex P2 finding is addressed
  • Check consistency across all changed files
  • Post final verdict

Trigger: PR synchronize — reviewing HEAD 01c6fb2 ("declare bin/-path shorthand in babysit guarded-mutation section").

Verdict: Clean. The Codex P2 finding is resolved. This PR is ready to merge.


What changed in 01c6fb2

SKILL.md lines 187–189 — the "Guarded mutations" section preamble previously read:

The two mutation gates are invoked ONLY by their bare wrapper names (they resolve their own interpreter and fail with a clear message when Python is absent). Both fail closed without --allowed-owners.

Now reads:

The two mutation gates are invoked ONLY through their wrapper scripts, by the bundled bin/-path form — never the bare command name nor the raw Python behind them. Each source-control-babysit-<x> … spelled in the bullets below is that wrapper launched by its bin/-path form; the exact form is the single home in reference/safety.md. Both fail closed without --allowed-owners.


Codex P2 finding: confirmed resolved

The Codex finding was: the section now says wrappers must never be invoked by bare command name, but the copyable bullets immediately below still start with source-control-babysit-merge ... and source-control-babysit-resolve-thread ....

The fix adds a section-level convention declaration that every source-control-babysit-<x> … spelling in the bullets below is launched by the bin/-path form. An agent reading the section top-down now encounters the convention before the bullets. This is the same approach safety.md already uses at line 136 ("Every command spelled below as source-control-babysit-<x> … is launched this way."), so the two files now use a consistent pattern.

The alternative — expanding every bullet to the full bash "${CLAUDE_PLUGIN_ROOT}/bin/…" path — would work but would make the already-dense bullets harder to read and require updates in more places if the form changes again. The section-declaration approach is correct here.


Correctness at HEAD

SKILL.md step 2/3 symmetry — both steps defer to safety.md for the exact command. No runnable bare name appears before the convention declaration. The command not found → raw-GraphQL fallback path remains closed. ✓

SKILL.md guarded-mutations section — convention now precedes bullets; the "single home in safety.md" pointer is present. Consistent with safety.md line 136's equivalent declaration. ✓

safety.md Guarded Mutation Wrappers section (lines 123–136) — the canonical bin/-path form is documented in fenced code blocks, with the section-level "Every command spelled below as source-control-babysit-<x> … is launched this way" declaration. All three runnable invocation points in orchestration.md use the path form. ✓

Removed detail — the old preamble said wrappers "resolve their own interpreter and fail with a clear message when Python is absent." This is no longer in SKILL.md, but the wrapper's self-locating behavior (readlink -f "$0") is intrinsic to the scripts and not consumer-facing; it doesn't need to appear in the skill overview. The replacement text accurately explains what does matter: path form keeps every guard intact, bare name fails command not found. ✓

SKILL.md line count: 499/500 (under the quality cap). ✓


All prior findings: status unchanged

Finding Prior classification Status
SKILL.md step 2 bare-name before convention (3× PR + security reviews, IMPORTANT/Confirmed) Blocking Resolved in b34208c. Intact at HEAD.
Codex P2 — SKILL.md guarded-mutations bullets use bare names before convention is declared Codex P2 Resolved in 01c6fb2. ✓
Codex P2 — D7.5 parenthetical over-generalizes bot-only rule to autopilot tier Codex P2 Resolved in 716915c. Intact at HEAD.
CLAUDE_PLUGIN_ROOT empty-string expands degradation-handoff commands to /bin/<wrapper> SUGGESTION / pre-existing Documented in CHANGELOG. Not blocking.
Worker Prompt Template command in orchestration.md in prose, not a fenced block Observation / pre-existing Not a regression.
Bare-name allow-rule mismatch (per-call prompts replace broad allow-rule match) Documented in CHANGELOG Resolves with upstream PATH gap fix (#843).

Security

No concerns. The change is additive prose in the section preamble. The bullets themselves are unchanged; the wrappers are unchanged; every guard survives. No new execution surface introduced.


Version & CHANGELOG

plugin.json at 0.16.1, correctly leading origin/main's 0.16.0. The 0.16.1 CHANGELOG entry accurately describes both the primary fix and the known residuals. No version bump is needed for this intra-PR accuracy fix. ✓


Summary: The Codex P2 finding (guarded-mutations bullets using bare names before the convention is declared) is resolved by 01c6fb2 using the same section-level convention pattern already established in safety.md. All prior blocking and Codex findings are now resolved. The PR is clean and ready to merge.
| Branch

@claude

claude Bot commented Jul 21, 2026

Copy link
Copy Markdown

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


Security Review — PR #840 (HEAD: 01c6fb2)

  • Gather PR diff and identify what changed in the latest commit
  • Read changed files at HEAD
  • Read wrapper scripts (guard verification)
  • Analyze for security vulnerabilities
  • Post findings

Scope: Documentation-only change (.md + plugin.json version bump). No scripts or executables were modified. HEAD 01c6fb2 is a single-file commit to SKILL.md only (4 lines changed), addressing the Codex P2 finding from the previous review pass. Both wrapper scripts (bin/source-control-babysit-merge, bin/source-control-babysit-resolve-thread) are confirmed unchanged throughout this PR.


New commit: 01c6fb2 — Guarded-mutations shorthand declaration

The only change in the latest commit is SKILL.md lines 186–188. The prior Guarded mutations header said:

The two mutation gates are invoked ONLY through their wrapper scripts, by the bundled bin/-path form — never the bare command name nor the raw Python behind them. The exact form is the single home in reference/safety.md.

It now adds:

Each source-control-babysit-<x> … spelled in the bullets below is that wrapper launched by its bin/-path form; the exact form is the single home in reference/safety.md.

Security assessment of this change: No concerns. The bullets below the header still spell wrapper names in shorthand (source-control-babysit-merge owner/repo#N …, source-control-babysit-resolve-thread owner/repo#N …), but the convention declaration now explicitly binds every one of those shorthand spellings to the bin/-path invocation form. This mirrors the identical convention safety.md already declares over its own shorthand references ("Every command spelled below as source-control-babysit-<x> … is launched this way"). No execution surface is introduced or widened — this is a documentation convention statement, not a runnable command.

The Codex P2 finding ("copyable bullets still start with source-control-babysit-merge …") is addressed by the same shorthand-declaration approach used in safety.md rather than per-bullet command duplication. The tradeoff is acceptable: the convention is now declared in the same section as the bullets, not 40+ lines later (the prior gap this PR's earlier commits introduced a risk around).


Prior IMPORTANT finding: CONFIRMED STILL RESOLVED

All prior automated security reviews flagged — one at IMPORTANT/Confirmed severity — that SKILL.md autopilot step 2 retained a bare-name invocation before the bin/-path convention was declared, leaving the command not found → raw-GraphQL fallback path (bypassing --allowed-owners enforcement and the thread-pin TOCTOU rule) reachable from the primary autopilot description. That finding was resolved in b34208c and is unaffected by this commit.

SKILL.md lines 146–148:

resolves it through the guarded resolve-thread wrapper (--resolve --include-human — bot, AI-review, and human threads alike); the exact command is the single home in reference/safety.md.

No runnable bare name precedes the convention. Steps 2 and 3 remain symmetric.


No CRITICAL or IMPORTANT findings

Wrapper guards are intact and unchanged. Both bin/source-control-babysit-merge and bin/source-control-babysit-resolve-thread self-locate via readlink -f "$0" (line 10/12 respectively) and operate identically whether invoked by bare name or absolute path. The merge wrapper's --allow-unpinned-head rejection (lines 16–22) is in the wrapper body, not path-dependent. Both pass "$@" to the Python engine — no argument interpolation of PR data.

No shell injection surface introduced. The SKILL.md convention statement is documentation prose, not a runnable command. All already-reviewed invocations in orchestration.md and safety.md use "${CLAUDE_PLUGIN_ROOT}/bin/<wrapper>" with double-quotes, unchanged by this commit.

No GitHub Actions, credential-handling, or permission-widening changes. No workflow files modified. No changes to GH_TOKEN handling. plugin.json change is version bump only.

D7.5 per-tier scope correction (716915c) confirmed intact at this HEAD: review-discipline.md lines 202–211 correctly states each tier's own resolve author-scope without over-generalizing the bot-only rule to the autopilot tier.


SUGGESTION (pre-existing, not a regression) — CLAUDE_PLUGIN_ROOT empty-string in degradation-handoff commands

Confidence: Plausible

safety.md lines 287, 303, 309 — Pinned-Command Degradation handoffs embed ${CLAUDE_PLUGIN_ROOT} in commands an operator may copy-paste into a shell. The PR confirms CLAUDE_PLUGIN_ROOT is empirically empty in this environment. Under double-quoted shell expansion, bash "${CLAUDE_PLUGIN_ROOT}/bin/source-control-babysit-merge"bash "/bin/source-control-babysit-merge" — a safe failure (no such file in /bin/), but with a misleading error message (No such file or directory: /bin/source-control-babysit-merge) that obscures the real root cause. Pre-existing pattern shared by the sibling ${CLAUDE_PLUGIN_ROOT}/scripts/ invocations; already scoped as a known residual in the CHANGELOG. Not a regression.


Summary

No CRITICAL or IMPORTANT findings. The sole IMPORTANT/Confirmed finding from all prior automated security reviews (SKILL.md step 2 bare-name before convention declaration) remains resolved at this HEAD. The latest commit (01c6fb2) addresses the Codex P2 finding by declaring the shorthand convention inline in the Guarded mutations section — consistent with how safety.md handles its own shorthand references, no new security surface introduced. One pre-existing SUGGESTION remains (CLAUDE_PLUGIN_ROOT empty-string expansion in degradation handoffs), already documented in the CHANGELOG. This PR is clear from a security standpoint.
| Branch

@kyle-sexton
kyle-sexton merged commit 3fc72d3 into main Jul 21, 2026
23 checks passed
@kyle-sexton
kyle-sexton deleted the fix/484-babysit-resolve-thread-path branch July 21, 2026 23:44
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: 'source-control-babysit-resolve-thread' bare wrapper not on PATH (command not found), worker must fall back to raw GraphQL

1 participant