feat(babysitter): standalone fixer that proposes a patch and never pushes
Stacked on #633 (F1). Base is babysitter/f1-feedback-filter; I'll retarget to main once #633 merges.
F2 in the Babysitter owner plan. The goal: a flow that wakes on PR feedback, loads the original session, and fixes the feedback in a sandbox. The design is Revision 1, which the lead approved.
Why propose instead of push
Cloud's PR head mode writes a contents-write token to a file the same-uid coding agent can read. A GitHub App token cannot be limited to one branch, so a prompt-injected agent could push to any branch. RULING-sandbox-push-0902 (cloud) forbids push credentials in sandboxes. The fixer therefore keeps only the #4226 read/comment token and journals a proposal. Cloud publishes it server-side: one commit with parent = bound head, then force:false on the ref update. FIXER.md is the Cloud contract (C1').
What's here
admission.ts: the admission prelude, extracted unchanged fromstandalone.tsand shared by both bodies. Every pre-existing standalone test passes unmodified, except the inline-comment rendering, which now shows#id.fix.ts:checkoutHeadsends the token as an HTTP header viaGIT_CONFIG_*, never in argv or.git/config, with hooks off. An existing checkout is fetched, reset and cleaned in place, keeping ignored dependencies. That is the hook for per-PR sandbox reuse (C2).proposeChangesbuilds a bounded binary-safe patch against the bound head, including the agent's own commits. It refuses workflow and secret paths, more than 50 files, more than 36KB of patch, or a proposal over 50KB.
fixer.ts: checkout → onebabysitter-fixagent in the checkout, with fix-mode rules → reread (declines if the head moved or the PR left scope) → proposal.- Replies only go to inline feedback that woke this run, once per thread. They carry a
<!-- babysitter:reply … -->marker, mentions are neutralised, and the origin prompt is redacted. - No push, merge, review or comment call exists in the fixer, and a test asserts it.
- Replies only go to inline feedback that woke this run, once per thread. They carry a
artifacts/babysitter-fixer.flow.ts: sha256a6cb1f4e9d16984fe6711912cd1843a9f710ab1a91085bf03625266ec6f9dd06. The standalone artifact moves to6ea6982e…because of the refactor.
⚠️ Gate edits (please review explicitly)
tests/standalone-artifact.test.tsnow loops over both artifacts. Same assertions, plus one new test: no__require(in either artifact.packages/sdk/tests/shipped-source-models.test.tsgets a call-exact waiver"babysitter-fix"for the fixer artifact, the same shape as #616's"babysitter-diagnose".- The audit first failed on my budget: 1.5M tokens / $10 broke the ≤100k tokens-per-dollar rule (
05a-…red-budget.txt). I fixed the budget to 1M / $10, not the gate.
- The audit first failed on my budget: 1.5M tokens / $10 broke the ≤100k tokens-per-dollar rule (
A bug this caught before shipping
esbuild rewrites require(...) into a __require shim. That shim does not exist once a function's source is lifted into node -e, so checkout and proposal would have crashed in production while the source tests passed. Builtins now load with import(). A test runs proposeChanges as lifted from the shipped artifact under sh -c 'node -e …'.
Evidence (examples/babysitter/evidence/f2-fixer/, unedited)
01-mutation-require-shim.txt: I reverted torequire, rebuilt, and ran the tests.the proposal script lifted from the shipped artifact runs under node -efails with__require is not defined. Restored byte-for-byte (cmp→ identical), then 15/15 pass.02-all-babysitter-green.txt: 124 tests, 117 pass, 0 fail, 7 todo (the existing deliberate gates).03-artifact-check.txt: both digests reproduce.04-flows-check.txt:flows checkpasses for both artifacts.05-sdk-shipped-source.txt: 2/2 pass.06-typecheck.txt: 0 errors outside the environment-only classes documented in #633's06-typecheck.txt. It caught one real error (reporttyped asPromise), which is fixed.
Not proven here
- The checkout never ran against GitHub.
checkoutHeadis exercised only through the mockedf.run, because it needs network and a token. The live run (Step 3) is its first real exercise. - Nothing publishes a proposal yet. That is C1' in cloud.
- The agent's
cwdmust be inside the run root. The checkout lives at<run root>/babysitter-checkout, which assumesf.runand the agent share a run root. If they don't, the step refuses and fails closed. The live run will show which.
🤖 Generated with Claude Code
[!NOTE]
Medium Risk
Introduces an agent with read/write checkout and automated patch proposals; risk is mitigated by no sandbox push, path/refusal limits, and git hardening, but Cloud must enforce the same limits when publishing.
Overview
Adds a standalone Babysitter fixer that wakes on PR feedback, checks out the bound head, runs one read/write agent with fix-mode rules, and journals a babysitter-proposal (patch + neutralised summary and inline thread replies) instead of pushing—aligned with sandbox push-credential rules and FIXER.md’s Cloud publish contract.
Shared admission moves into admission.ts for both diagnose and fix flows; origin.ts gains fix/diagnose agentTask modes and review comment #ids in prompts. fix.ts implements hardened checkout and proposal: token via GIT_CONFIG_*, reset of agent-tampered .git, refused workflow/secret paths and size limits, NUL-delimited paths with core.quotePath=false, and dynamic import() for scripts lifted into node -e. build-standalone.mjs now builds babysitter-fixer and babysitter-standalone artifacts with updated digests, plus broad fixer tests and evidence.
Merging does not enable publishing; Cloud must admit the fixer manifest SHA and implement C1'.
Reviewed by Cursor Bugbot for commit 0114937527a43dea9b2352a4a589fb6c5601a11e. Bugbot is set up for automated code reviews on this repo. Configure here.
Summary by cubic
Adds the standalone Babysitter fixer, completing the second phase of the owner plan. It wakes on PR feedback, checks out the bound head, runs one agent to fix the feedback, and journals a bounded proposal — a patch against the bound head plus thread replies — instead of pushing. The sandbox holds only the run's read and comment token, per the cloud ruling that forbids push credentials in sandboxes; Cloud publishes the proposal as a fast-forward-only commit, with FIXER.md as the contract.
- Extracts the admission prelude into
admission.ts, shared with the diagnose body; the standalone artifact's digest changes, and inline-comment rendering now shows the comment#id. - Checkout sends the token as an HTTP header via
GIT_CONFIG_*, never in argv, with hooks off; an existing checkout is fetched, reset, and cleaned in place for per-PR sandbox reuse. - Hardens checkout and proposal scripts against an agent-planted
.gitconfig: both replace.git/configwith a minimal known config and remove hooks and info/attributes before running git; the proposal diff also passes--no-ext-diffand--no-textconvso the daemon never executes agent-controlled drivers. - The proposal reads changed paths NUL-delimited with
core.quotePath=false, so a newline in a filename cannot slip a workflow path past refusal and Cloud parses back exactly the checked paths. - Proposals refuse workflow and secret paths, more than 50 files, patches over 36KB, and proposals over 50KB.
- Replies go only to the inline feedback that woke the run, once per thread, mentions neutralised and origin prompt redacted; the fixer has no push, merge, review, or comment calls, which a test asserts.
- Fixes a shipping bug where the bundler's rewritten
requireshim crashes undernode -e; builtins now load withimport(), proven by a test running the proposal function lifted from the shipped artifact. - The fixer calls
f.agentwith a literal cli/model pair per origin CLI, so the shipped-source audit passes unmodified: no waiver needed, and the fixer ignores the policy'sagentCli. - Build, artifact, and shipped-source tests cover both artifacts, with the fixer's budget set to 1M tokens / $10.
Not yet exercised
- Checkout has only run against a mocked
f.run; the live run is its first real GitHub exercise. - Nothing publishes a proposal yet — Cloud does that server-side per
FIXER.md.
Agent Relay sessions
claudesession497569fc· opened viagh pr create· last active 2026-10-09