From f6f4c94697cc8bd554a3a21ed1870fb249b3bdef Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 15 Jul 2026 12:58:15 -0700 Subject: [PATCH 01/14] fix(babysit): resolve merge conflicts against staging during the review loop --- .agents/skills/babysit/SKILL.md | 94 ++++++++++++++++++++++++++------- .claude/commands/babysit.md | 94 ++++++++++++++++++++++++++------- .cursor/commands/babysit.md | 92 +++++++++++++++++++++++++------- 3 files changed, 221 insertions(+), 59 deletions(-) diff --git a/.agents/skills/babysit/SKILL.md b/.agents/skills/babysit/SKILL.md index 4af7dbbd855..e2c6504bb77 100644 --- a/.agents/skills/babysit/SKILL.md +++ b/.agents/skills/babysit/SKILL.md @@ -1,14 +1,17 @@ --- name: babysit -description: Drive a PR to a clean review (Greptile 5/5, zero open threads) — ships if needed, triggers Greptile/Cursor Bugbot, fixes real findings, replies to and resolves every thread, and loops until clean +description: Drive a PR to a clean review (Greptile 5/5, zero open threads) — ships if needed, keeps it mergeable against staging, triggers Greptile/Cursor Bugbot, fixes real findings, replies to and resolves every thread, and loops until clean --- # Babysit PRs Owns a PR end-to-end through review: ship it, wait for the automatic review round, and if it isn't already clean, drive fix → reply → resolve → re-review cycles until Greptile reports 5/5 -and there are zero open comment threads. Designed to be run under `/loop` (no fixed interval — -let it self-pace on review latency) so it survives across multiple wakeups in the same session. +and there are zero open comment threads. Also keeps the branch mergeable against staging — a +long-running loop spans hours during which staging can drift out from under the PR and produce +a real merge conflict, not just the stray-local-commit drift `/ship`'s sync check catches. +Designed to be run under `/loop` (no fixed interval — let it self-pace on review latency) so it +survives across multiple wakeups in the same session. ## When to use @@ -33,8 +36,10 @@ round. Always check both conditions freshly after every push. ## Loop -1. **Check current state** before doing anything: +1. **Check current state** before doing anything — this includes mergeable state, not just + review state: ```bash + gh pr view --json mergeable,mergeStateStatus gh pr view --json comments -q '[.comments[] | select(.author.login=="greptile-apps")] | last | .body' gh api graphql -f query=' query { repository(owner: "", name: "") { pullRequest(number: ) { @@ -49,14 +54,50 @@ round. Always check both conditions freshly after every push. stop yet: re-run the same query with `after: ""` and keep paging until `hasNextPage` is `false` before evaluating "clean." A PR with more than 50 threads is rare but stopping on a partial page would silently miss unresolved ones past the cutoff. - If Greptile is 5/5 and every thread across all pages has `isResolved: true`, stop — report the - outcome (see "Reporting" below) and skip the rest of this list. - -2. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run + `mergeable` is `UNKNOWN` for a few seconds right after any push or base-branch move while + GitHub computes it — re-poll `gh pr view --json mergeable` every few seconds until it + settles to `MERGEABLE` or `CONFLICTING` before acting on it; don't treat `UNKNOWN` as either. + If `mergeable` is `CONFLICTING`, skip straight to step 2 (Resolve merge conflicts) — do not + evaluate review cleanliness yet, since Greptile/Cursor threads anchored to a conflicting diff + can be stale and CI can't even run to confirm anything. + If `mergeable` is `MERGEABLE`, Greptile is 5/5, and every thread across all pages has + `isResolved: true`, stop — report the outcome (see "Reporting" below) and skip the rest of + this list. + +2. **If `mergeable` is `CONFLICTING`**, resolve it before anything else this round. This can + happen even on a PR that was clean at creation, since staging moves several times a day — + recheck every iteration, don't assume it's a one-time state handled by `/ship`. + ```bash + git status # stash -u first (ship-sync-fix pattern) if anything uncommitted + git fetch origin staging + git rebase origin/staging + ``` + Resolve each conflicted file on its actual merits — open it, read both sides inside the + `<<<<<<<` / `=======` / `>>>>>>>` markers, and keep the intent of *both* changes where they're + not truly contradictory. Never resolve with a blanket `git checkout --ours`/`--theirs` across + a whole file — that silently discards one side's real change instead of merging it. `git add` + each resolved file, then `git rebase --continue`; repeat until the rebase finishes clean (a + rebase with **zero remaining conflicts** does not by itself mean the resolution is correct — + see the next point). + - A conflict resolution is a code change like any other: before pushing, run this repo's + typecheck/lint pass on the touched files (the same checks `/ship` step 6 runs) — merge + markers can resolve syntactically clean and still be semantically wrong or fail to compile. + - Push with `git push --force-with-lease` — a rebase here always rewrites already-published + history, so a plain `git push` will be rejected. + - Re-poll `gh pr view --json mergeable,mergeStateStatus` until it reports `MERGEABLE` + (not `UNKNOWN`) before moving on. If it still reports `CONFLICTING`, the rebase resolved + against a stale local `origin/staging` — `git fetch origin staging` again and redo the + rebase; don't push a second time believing it's fixed without this recheck. + - This push needs a fresh review like any other code change — go to step 8 (re-trigger + review) rather than trying to also address old review threads in the same pass, especially + if the conflict touched files a thread was anchored to (that anchor may no longer be valid + against the resolved code). Re-evaluate thread state fresh once the new round lands. + +3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / `Greptile Review`) and wait for that first round before doing anything else. -3. **If a review round has landed and it isn't clean**: for every thread where +4. **If a review round has landed and it isn't clean**: for every thread where `isResolved: false`, triage the finding on its own merits — this is the part that requires judgment, not a mechanical loop: - **Real bug**: fix it in the cleanest way available. Match the codebase's existing @@ -71,7 +112,7 @@ round. Always check both conditions freshly after every push. - **Already fixed by an earlier finding in the same round**: note that and resolve without a duplicate code change. -4. **Reply to every thread individually** before resolving it — never resolve silently: +5. **Reply to every thread individually** before resolving it — never resolve silently: ```bash gh api repos///pulls//comments//replies -f body="" ``` @@ -80,7 +121,7 @@ round. Always check both conditions freshly after every push. gh api graphql -f query='mutation { resolveReviewThread(input: {threadId: ""}) { thread { isResolved } } }' ``` -5. **Before pushing, re-run the full sync check from `/ship` step 2** — not just the log command, +6. **Before pushing, re-run the full sync check from `/ship` step 2** — not just the log command, the whole check-and-recover flow (stash WIP if needed, rebase, verify the rebase didn't just cleanly replay stray commits, cherry-pick rebuild if it did or if it conflicted). A babysit loop spanning a long session is exactly the scenario where a branch can drift, and pushing @@ -89,9 +130,13 @@ round. Always check both conditions freshly after every push. committing — not just lint/typecheck/boundary-validation, but also the conditional `/cleanup` (if this round's fix touched UI code) and `/db-migrate` (if it touched schema/migrations) gates from `/ship` steps 4 and 5. A review-fix round is still a code change and can trip - either gate just as easily as the original commit did. + either gate just as easily as the original commit did. This is the same sync check as step 2 + above but for local-drift, not for the base-branch textual conflicts step 2 handles — step 2 + already left the branch rebased onto current `origin/staging` when it ran, so this step is + normally a no-op in that case, but still run it: it also catches stray local commits that + have nothing to do with merge conflicts. -6. **Commit and push** the round's fixes as one commit — `--force-with-lease` whenever step 5's +7. **Commit and push** the round's fixes as one commit — `--force-with-lease` whenever step 6's sync check rewrote history, which includes a plain `git rebase origin/staging` that completed with no conflicts, not only the cherry-pick rebuild path; both rewrite commits already published to the remote, so a plain `git push` can be rejected either way — then run `/ship` @@ -104,29 +149,32 @@ round. Always check both conditions freshly after every push. `git log` is newest-first, so without it a positional comparison can spuriously fail on any multi-commit branch. These two lists must describe the same commits. A review loop runs many pushes across many - rounds; checking sync only before the push (step 5) and never after is how a bad push or a + rounds; checking sync only before the push (step 6) and never after is how a bad push or a PR whose commit history quietly went stale between rounds goes unnoticed. -7. **Re-trigger review** by posting `@greptile` and `@cursor review` as **two separate PR +8. **Re-trigger review** by posting `@greptile` and `@cursor review` as **two separate PR comments** — never combine them into one comment, each bot only responds to its own mention: ```bash gh pr comment --body "@greptile" gh pr comment --body "@cursor review" ``` -8. **Wait for the new round**, then go back to step 1. Pace the wait with `ScheduleWakeup` using +9. **Wait for the new round**, then go back to step 1. Pace the wait with `ScheduleWakeup` using a fallback delay of ~250–300s (Greptile/Cursor typically take 1–3 minutes) — never busy-poll in a sleep loop. Pass the same `/loop babysit PR ` prompt on each wakeup so the loop resumes correctly. -9. **Stop conditions**: clean state reached (see above), or the same unresolved finding survives - two consecutive rounds with no new information (surface it to the user instead of looping - forever), or the user interrupts. +10. **Stop conditions**: clean state reached (see above), or the same unresolved finding + survives two consecutive rounds with no new information, or the same merge conflict recurs + every round with no new information (e.g. a semantic conflict you can't confidently resolve + without changing intent), or the user interrupts — surface any of these to the user instead + of looping forever. ## Reporting When the loop ends, summarize: how many rounds it took, what was actually fixed (one line each), -what was pushed back on as a false positive and why, and the final Greptile score / thread count. +what was pushed back on as a false positive and why, how many merge conflicts came up and against +which staging commits (if any), and the final Greptile score / thread count / mergeable state. ## Hard rules @@ -136,3 +184,9 @@ what was pushed back on as a false positive and why, and the final Greptile scor pattern elsewhere in the codebase solving the same class of problem and match it. - Never silently drop a finding — every thread gets either a code fix or a reasoned reply. - Always re-run the `/ship`-style sync check before every push in the loop, not just the first. +- Never resolve a merge conflict with a blanket `--ours`/`--theirs` across a whole file — read + both sides and preserve the real intent of each; a wrong resolution ships silently since + nothing else catches it. +- Never treat `mergeable: UNKNOWN` as either `MERGEABLE` or `CONFLICTING` — poll until it settles. +- Never evaluate review-thread cleanliness while `mergeable` is `CONFLICTING` — resolve the + conflict first, since threads anchored to a conflicting diff can be stale. diff --git a/.claude/commands/babysit.md b/.claude/commands/babysit.md index 2c21ad6adc5..e7c00f5e8ac 100644 --- a/.claude/commands/babysit.md +++ b/.claude/commands/babysit.md @@ -1,13 +1,16 @@ --- -description: Drive a PR to a clean review (Greptile 5/5, zero open threads) — ships if needed, triggers Greptile/Cursor Bugbot, fixes real findings, replies to and resolves every thread, and loops until clean +description: Drive a PR to a clean review (Greptile 5/5, zero open threads) — ships if needed, keeps it mergeable against staging, triggers Greptile/Cursor Bugbot, fixes real findings, replies to and resolves every thread, and loops until clean --- # Babysit PRs Owns a PR end-to-end through review: ship it, wait for the automatic review round, and if it isn't already clean, drive fix → reply → resolve → re-review cycles until Greptile reports 5/5 -and there are zero open comment threads. Designed to be run under `/loop` (no fixed interval — -let it self-pace on review latency) so it survives across multiple wakeups in the same session. +and there are zero open comment threads. Also keeps the branch mergeable against staging — a +long-running loop spans hours during which staging can drift out from under the PR and produce +a real merge conflict, not just the stray-local-commit drift `/ship`'s sync check catches. +Designed to be run under `/loop` (no fixed interval — let it self-pace on review latency) so it +survives across multiple wakeups in the same session. ## When to use @@ -32,8 +35,10 @@ round. Always check both conditions freshly after every push. ## Loop -1. **Check current state** before doing anything: +1. **Check current state** before doing anything — this includes mergeable state, not just + review state: ```bash + gh pr view --json mergeable,mergeStateStatus gh pr view --json comments -q '[.comments[] | select(.author.login=="greptile-apps")] | last | .body' gh api graphql -f query=' query { repository(owner: "", name: "") { pullRequest(number: ) { @@ -48,14 +53,50 @@ round. Always check both conditions freshly after every push. stop yet: re-run the same query with `after: ""` and keep paging until `hasNextPage` is `false` before evaluating "clean." A PR with more than 50 threads is rare but stopping on a partial page would silently miss unresolved ones past the cutoff. - If Greptile is 5/5 and every thread across all pages has `isResolved: true`, stop — report the - outcome (see "Reporting" below) and skip the rest of this list. - -2. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run + `mergeable` is `UNKNOWN` for a few seconds right after any push or base-branch move while + GitHub computes it — re-poll `gh pr view --json mergeable` every few seconds until it + settles to `MERGEABLE` or `CONFLICTING` before acting on it; don't treat `UNKNOWN` as either. + If `mergeable` is `CONFLICTING`, skip straight to step 2 (Resolve merge conflicts) — do not + evaluate review cleanliness yet, since Greptile/Cursor threads anchored to a conflicting diff + can be stale and CI can't even run to confirm anything. + If `mergeable` is `MERGEABLE`, Greptile is 5/5, and every thread across all pages has + `isResolved: true`, stop — report the outcome (see "Reporting" below) and skip the rest of + this list. + +2. **If `mergeable` is `CONFLICTING`**, resolve it before anything else this round. This can + happen even on a PR that was clean at creation, since staging moves several times a day — + recheck every iteration, don't assume it's a one-time state handled by `/ship`. + ```bash + git status # stash -u first (ship-sync-fix pattern) if anything uncommitted + git fetch origin staging + git rebase origin/staging + ``` + Resolve each conflicted file on its actual merits — open it, read both sides inside the + `<<<<<<<` / `=======` / `>>>>>>>` markers, and keep the intent of *both* changes where they're + not truly contradictory. Never resolve with a blanket `git checkout --ours`/`--theirs` across + a whole file — that silently discards one side's real change instead of merging it. `git add` + each resolved file, then `git rebase --continue`; repeat until the rebase finishes clean (a + rebase with **zero remaining conflicts** does not by itself mean the resolution is correct — + see the next point). + - A conflict resolution is a code change like any other: before pushing, run this repo's + typecheck/lint pass on the touched files (the same checks `/ship` step 6 runs) — merge + markers can resolve syntactically clean and still be semantically wrong or fail to compile. + - Push with `git push --force-with-lease` — a rebase here always rewrites already-published + history, so a plain `git push` will be rejected. + - Re-poll `gh pr view --json mergeable,mergeStateStatus` until it reports `MERGEABLE` + (not `UNKNOWN`) before moving on. If it still reports `CONFLICTING`, the rebase resolved + against a stale local `origin/staging` — `git fetch origin staging` again and redo the + rebase; don't push a second time believing it's fixed without this recheck. + - This push needs a fresh review like any other code change — go to step 8 (re-trigger + review) rather than trying to also address old review threads in the same pass, especially + if the conflict touched files a thread was anchored to (that anchor may no longer be valid + against the resolved code). Re-evaluate thread state fresh once the new round lands. + +3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / `Greptile Review`) and wait for that first round before doing anything else. -3. **If a review round has landed and it isn't clean**: for every thread where +4. **If a review round has landed and it isn't clean**: for every thread where `isResolved: false`, triage the finding on its own merits — this is the part that requires judgment, not a mechanical loop: - **Real bug**: fix it in the cleanest way available. Match the codebase's existing @@ -70,7 +111,7 @@ round. Always check both conditions freshly after every push. - **Already fixed by an earlier finding in the same round**: note that and resolve without a duplicate code change. -4. **Reply to every thread individually** before resolving it — never resolve silently: +5. **Reply to every thread individually** before resolving it — never resolve silently: ```bash gh api repos///pulls//comments//replies -f body="" ``` @@ -79,7 +120,7 @@ round. Always check both conditions freshly after every push. gh api graphql -f query='mutation { resolveReviewThread(input: {threadId: ""}) { thread { isResolved } } }' ``` -5. **Before pushing, re-run the full sync check from `/ship` step 2** — not just the log command, +6. **Before pushing, re-run the full sync check from `/ship` step 2** — not just the log command, the whole check-and-recover flow (stash WIP if needed, rebase, verify the rebase didn't just cleanly replay stray commits, cherry-pick rebuild if it did or if it conflicted). A babysit loop spanning a long session is exactly the scenario where a branch can drift, and pushing @@ -88,9 +129,13 @@ round. Always check both conditions freshly after every push. committing — not just lint/typecheck/boundary-validation, but also the conditional `/cleanup` (if this round's fix touched UI code) and `/db-migrate` (if it touched schema/migrations) gates from `/ship` steps 4 and 5. A review-fix round is still a code change and can trip - either gate just as easily as the original commit did. + either gate just as easily as the original commit did. This is the same sync check as step 2 + above but for local-drift, not for the base-branch textual conflicts step 2 handles — step 2 + already left the branch rebased onto current `origin/staging` when it ran, so this step is + normally a no-op in that case, but still run it: it also catches stray local commits that + have nothing to do with merge conflicts. -6. **Commit and push** the round's fixes as one commit — `--force-with-lease` whenever step 5's +7. **Commit and push** the round's fixes as one commit — `--force-with-lease` whenever step 6's sync check rewrote history, which includes a plain `git rebase origin/staging` that completed with no conflicts, not only the cherry-pick rebuild path; both rewrite commits already published to the remote, so a plain `git push` can be rejected either way — then run `/ship` @@ -103,29 +148,32 @@ round. Always check both conditions freshly after every push. `git log` is newest-first, so without it a positional comparison can spuriously fail on any multi-commit branch. These two lists must describe the same commits. A review loop runs many pushes across many - rounds; checking sync only before the push (step 5) and never after is how a bad push or a + rounds; checking sync only before the push (step 6) and never after is how a bad push or a PR whose commit history quietly went stale between rounds goes unnoticed. -7. **Re-trigger review** by posting `@greptile` and `@cursor review` as **two separate PR +8. **Re-trigger review** by posting `@greptile` and `@cursor review` as **two separate PR comments** — never combine them into one comment, each bot only responds to its own mention: ```bash gh pr comment --body "@greptile" gh pr comment --body "@cursor review" ``` -8. **Wait for the new round**, then go back to step 1. Pace the wait with `ScheduleWakeup` using +9. **Wait for the new round**, then go back to step 1. Pace the wait with `ScheduleWakeup` using a fallback delay of ~250–300s (Greptile/Cursor typically take 1–3 minutes) — never busy-poll in a sleep loop. Pass the same `/loop babysit PR ` prompt on each wakeup so the loop resumes correctly. -9. **Stop conditions**: clean state reached (see above), or the same unresolved finding survives - two consecutive rounds with no new information (surface it to the user instead of looping - forever), or the user interrupts. +10. **Stop conditions**: clean state reached (see above), or the same unresolved finding + survives two consecutive rounds with no new information, or the same merge conflict recurs + every round with no new information (e.g. a semantic conflict you can't confidently resolve + without changing intent), or the user interrupts — surface any of these to the user instead + of looping forever. ## Reporting When the loop ends, summarize: how many rounds it took, what was actually fixed (one line each), -what was pushed back on as a false positive and why, and the final Greptile score / thread count. +what was pushed back on as a false positive and why, how many merge conflicts came up and against +which staging commits (if any), and the final Greptile score / thread count / mergeable state. ## Hard rules @@ -135,3 +183,9 @@ what was pushed back on as a false positive and why, and the final Greptile scor pattern elsewhere in the codebase solving the same class of problem and match it. - Never silently drop a finding — every thread gets either a code fix or a reasoned reply. - Always re-run the `/ship`-style sync check before every push in the loop, not just the first. +- Never resolve a merge conflict with a blanket `--ours`/`--theirs` across a whole file — read + both sides and preserve the real intent of each; a wrong resolution ships silently since + nothing else catches it. +- Never treat `mergeable: UNKNOWN` as either `MERGEABLE` or `CONFLICTING` — poll until it settles. +- Never evaluate review-thread cleanliness while `mergeable` is `CONFLICTING` — resolve the + conflict first, since threads anchored to a conflicting diff can be stale. diff --git a/.cursor/commands/babysit.md b/.cursor/commands/babysit.md index 7386b608ac4..eb712bee247 100644 --- a/.cursor/commands/babysit.md +++ b/.cursor/commands/babysit.md @@ -2,8 +2,11 @@ Owns a PR end-to-end through review: ship it, wait for the automatic review round, and if it isn't already clean, drive fix → reply → resolve → re-review cycles until Greptile reports 5/5 -and there are zero open comment threads. Designed to be run under `/loop` (no fixed interval — -let it self-pace on review latency) so it survives across multiple wakeups in the same session. +and there are zero open comment threads. Also keeps the branch mergeable against staging — a +long-running loop spans hours during which staging can drift out from under the PR and produce +a real merge conflict, not just the stray-local-commit drift `/ship`'s sync check catches. +Designed to be run under `/loop` (no fixed interval — let it self-pace on review latency) so it +survives across multiple wakeups in the same session. ## When to use @@ -28,8 +31,10 @@ round. Always check both conditions freshly after every push. ## Loop -1. **Check current state** before doing anything: +1. **Check current state** before doing anything — this includes mergeable state, not just + review state: ```bash + gh pr view --json mergeable,mergeStateStatus gh pr view --json comments -q '[.comments[] | select(.author.login=="greptile-apps")] | last | .body' gh api graphql -f query=' query { repository(owner: "", name: "") { pullRequest(number: ) { @@ -44,14 +49,50 @@ round. Always check both conditions freshly after every push. stop yet: re-run the same query with `after: ""` and keep paging until `hasNextPage` is `false` before evaluating "clean." A PR with more than 50 threads is rare but stopping on a partial page would silently miss unresolved ones past the cutoff. - If Greptile is 5/5 and every thread across all pages has `isResolved: true`, stop — report the - outcome (see "Reporting" below) and skip the rest of this list. - -2. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run + `mergeable` is `UNKNOWN` for a few seconds right after any push or base-branch move while + GitHub computes it — re-poll `gh pr view --json mergeable` every few seconds until it + settles to `MERGEABLE` or `CONFLICTING` before acting on it; don't treat `UNKNOWN` as either. + If `mergeable` is `CONFLICTING`, skip straight to step 2 (Resolve merge conflicts) — do not + evaluate review cleanliness yet, since Greptile/Cursor threads anchored to a conflicting diff + can be stale and CI can't even run to confirm anything. + If `mergeable` is `MERGEABLE`, Greptile is 5/5, and every thread across all pages has + `isResolved: true`, stop — report the outcome (see "Reporting" below) and skip the rest of + this list. + +2. **If `mergeable` is `CONFLICTING`**, resolve it before anything else this round. This can + happen even on a PR that was clean at creation, since staging moves several times a day — + recheck every iteration, don't assume it's a one-time state handled by `/ship`. + ```bash + git status # stash -u first (ship-sync-fix pattern) if anything uncommitted + git fetch origin staging + git rebase origin/staging + ``` + Resolve each conflicted file on its actual merits — open it, read both sides inside the + `<<<<<<<` / `=======` / `>>>>>>>` markers, and keep the intent of *both* changes where they're + not truly contradictory. Never resolve with a blanket `git checkout --ours`/`--theirs` across + a whole file — that silently discards one side's real change instead of merging it. `git add` + each resolved file, then `git rebase --continue`; repeat until the rebase finishes clean (a + rebase with **zero remaining conflicts** does not by itself mean the resolution is correct — + see the next point). + - A conflict resolution is a code change like any other: before pushing, run this repo's + typecheck/lint pass on the touched files (the same checks `/ship` step 6 runs) — merge + markers can resolve syntactically clean and still be semantically wrong or fail to compile. + - Push with `git push --force-with-lease` — a rebase here always rewrites already-published + history, so a plain `git push` will be rejected. + - Re-poll `gh pr view --json mergeable,mergeStateStatus` until it reports `MERGEABLE` + (not `UNKNOWN`) before moving on. If it still reports `CONFLICTING`, the rebase resolved + against a stale local `origin/staging` — `git fetch origin staging` again and redo the + rebase; don't push a second time believing it's fixed without this recheck. + - This push needs a fresh review like any other code change — go to step 8 (re-trigger + review) rather than trying to also address old review threads in the same pass, especially + if the conflict touched files a thread was anchored to (that anchor may no longer be valid + against the resolved code). Re-evaluate thread state fresh once the new round lands. + +3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / `Greptile Review`) and wait for that first round before doing anything else. -3. **If a review round has landed and it isn't clean**: for every thread where +4. **If a review round has landed and it isn't clean**: for every thread where `isResolved: false`, triage the finding on its own merits — this is the part that requires judgment, not a mechanical loop: - **Real bug**: fix it in the cleanest way available. Match the codebase's existing @@ -66,7 +107,7 @@ round. Always check both conditions freshly after every push. - **Already fixed by an earlier finding in the same round**: note that and resolve without a duplicate code change. -4. **Reply to every thread individually** before resolving it — never resolve silently: +5. **Reply to every thread individually** before resolving it — never resolve silently: ```bash gh api repos///pulls//comments//replies -f body="" ``` @@ -75,7 +116,7 @@ round. Always check both conditions freshly after every push. gh api graphql -f query='mutation { resolveReviewThread(input: {threadId: ""}) { thread { isResolved } } }' ``` -5. **Before pushing, re-run the full sync check from `/ship` step 2** — not just the log command, +6. **Before pushing, re-run the full sync check from `/ship` step 2** — not just the log command, the whole check-and-recover flow (stash WIP if needed, rebase, verify the rebase didn't just cleanly replay stray commits, cherry-pick rebuild if it did or if it conflicted). A babysit loop spanning a long session is exactly the scenario where a branch can drift, and pushing @@ -84,9 +125,13 @@ round. Always check both conditions freshly after every push. committing — not just lint/typecheck/boundary-validation, but also the conditional `/cleanup` (if this round's fix touched UI code) and `/db-migrate` (if it touched schema/migrations) gates from `/ship` steps 4 and 5. A review-fix round is still a code change and can trip - either gate just as easily as the original commit did. + either gate just as easily as the original commit did. This is the same sync check as step 2 + above but for local-drift, not for the base-branch textual conflicts step 2 handles — step 2 + already left the branch rebased onto current `origin/staging` when it ran, so this step is + normally a no-op in that case, but still run it: it also catches stray local commits that + have nothing to do with merge conflicts. -6. **Commit and push** the round's fixes as one commit — `--force-with-lease` whenever step 5's +7. **Commit and push** the round's fixes as one commit — `--force-with-lease` whenever step 6's sync check rewrote history, which includes a plain `git rebase origin/staging` that completed with no conflicts, not only the cherry-pick rebuild path; both rewrite commits already published to the remote, so a plain `git push` can be rejected either way — then run `/ship` @@ -99,29 +144,32 @@ round. Always check both conditions freshly after every push. `git log` is newest-first, so without it a positional comparison can spuriously fail on any multi-commit branch. These two lists must describe the same commits. A review loop runs many pushes across many - rounds; checking sync only before the push (step 5) and never after is how a bad push or a + rounds; checking sync only before the push (step 6) and never after is how a bad push or a PR whose commit history quietly went stale between rounds goes unnoticed. -7. **Re-trigger review** by posting `@greptile` and `@cursor review` as **two separate PR +8. **Re-trigger review** by posting `@greptile` and `@cursor review` as **two separate PR comments** — never combine them into one comment, each bot only responds to its own mention: ```bash gh pr comment --body "@greptile" gh pr comment --body "@cursor review" ``` -8. **Wait for the new round**, then go back to step 1. Pace the wait with `ScheduleWakeup` using +9. **Wait for the new round**, then go back to step 1. Pace the wait with `ScheduleWakeup` using a fallback delay of ~250–300s (Greptile/Cursor typically take 1–3 minutes) — never busy-poll in a sleep loop. Pass the same `/loop babysit PR ` prompt on each wakeup so the loop resumes correctly. -9. **Stop conditions**: clean state reached (see above), or the same unresolved finding survives - two consecutive rounds with no new information (surface it to the user instead of looping - forever), or the user interrupts. +10. **Stop conditions**: clean state reached (see above), or the same unresolved finding + survives two consecutive rounds with no new information, or the same merge conflict recurs + every round with no new information (e.g. a semantic conflict you can't confidently resolve + without changing intent), or the user interrupts — surface any of these to the user instead + of looping forever. ## Reporting When the loop ends, summarize: how many rounds it took, what was actually fixed (one line each), -what was pushed back on as a false positive and why, and the final Greptile score / thread count. +what was pushed back on as a false positive and why, how many merge conflicts came up and against +which staging commits (if any), and the final Greptile score / thread count / mergeable state. ## Hard rules @@ -131,3 +179,9 @@ what was pushed back on as a false positive and why, and the final Greptile scor pattern elsewhere in the codebase solving the same class of problem and match it. - Never silently drop a finding — every thread gets either a code fix or a reasoned reply. - Always re-run the `/ship`-style sync check before every push in the loop, not just the first. +- Never resolve a merge conflict with a blanket `--ours`/`--theirs` across a whole file — read + both sides and preserve the real intent of each; a wrong resolution ships silently since + nothing else catches it. +- Never treat `mergeable: UNKNOWN` as either `MERGEABLE` or `CONFLICTING` — poll until it settles. +- Never evaluate review-thread cleanliness while `mergeable` is `CONFLICTING` — resolve the + conflict first, since threads anchored to a conflicting diff can be stale. From daf4217ff10ead296e5b74100e0262b328fadda4 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 15 Jul 2026 14:26:14 -0700 Subject: [PATCH 02/14] fix(babysit): trim merge-conflict handling to essentials --- .agents/skills/babysit/SKILL.md | 89 +++++++++------------------------ .claude/commands/babysit.md | 89 +++++++++------------------------ .cursor/commands/babysit.md | 89 +++++++++------------------------ 3 files changed, 72 insertions(+), 195 deletions(-) diff --git a/.agents/skills/babysit/SKILL.md b/.agents/skills/babysit/SKILL.md index e2c6504bb77..406a45ed480 100644 --- a/.agents/skills/babysit/SKILL.md +++ b/.agents/skills/babysit/SKILL.md @@ -7,11 +7,10 @@ description: Drive a PR to a clean review (Greptile 5/5, zero open threads) — Owns a PR end-to-end through review: ship it, wait for the automatic review round, and if it isn't already clean, drive fix → reply → resolve → re-review cycles until Greptile reports 5/5 -and there are zero open comment threads. Also keeps the branch mergeable against staging — a -long-running loop spans hours during which staging can drift out from under the PR and produce -a real merge conflict, not just the stray-local-commit drift `/ship`'s sync check catches. -Designed to be run under `/loop` (no fixed interval — let it self-pace on review latency) so it -survives across multiple wakeups in the same session. +and there are zero open comment threads. Also keeps the branch mergeable against staging, since +a long babysit session can outlast staging moving underneath it. Designed to be run under +`/loop` (no fixed interval — let it self-pace on review latency) so it survives across multiple +wakeups in the same session. ## When to use @@ -36,10 +35,9 @@ round. Always check both conditions freshly after every push. ## Loop -1. **Check current state** before doing anything — this includes mergeable state, not just - review state: +1. **Check current state** before doing anything, including whether the PR is still mergeable: ```bash - gh pr view --json mergeable,mergeStateStatus + gh pr view --json mergeable gh pr view --json comments -q '[.comments[] | select(.author.login=="greptile-apps")] | last | .body' gh api graphql -f query=' query { repository(owner: "", name: "") { pullRequest(number: ) { @@ -54,44 +52,16 @@ round. Always check both conditions freshly after every push. stop yet: re-run the same query with `after: ""` and keep paging until `hasNextPage` is `false` before evaluating "clean." A PR with more than 50 threads is rare but stopping on a partial page would silently miss unresolved ones past the cutoff. - `mergeable` is `UNKNOWN` for a few seconds right after any push or base-branch move while - GitHub computes it — re-poll `gh pr view --json mergeable` every few seconds until it - settles to `MERGEABLE` or `CONFLICTING` before acting on it; don't treat `UNKNOWN` as either. - If `mergeable` is `CONFLICTING`, skip straight to step 2 (Resolve merge conflicts) — do not - evaluate review cleanliness yet, since Greptile/Cursor threads anchored to a conflicting diff - can be stale and CI can't even run to confirm anything. - If `mergeable` is `MERGEABLE`, Greptile is 5/5, and every thread across all pages has - `isResolved: true`, stop — report the outcome (see "Reporting" below) and skip the rest of - this list. - -2. **If `mergeable` is `CONFLICTING`**, resolve it before anything else this round. This can - happen even on a PR that was clean at creation, since staging moves several times a day — - recheck every iteration, don't assume it's a one-time state handled by `/ship`. - ```bash - git status # stash -u first (ship-sync-fix pattern) if anything uncommitted - git fetch origin staging - git rebase origin/staging - ``` - Resolve each conflicted file on its actual merits — open it, read both sides inside the - `<<<<<<<` / `=======` / `>>>>>>>` markers, and keep the intent of *both* changes where they're - not truly contradictory. Never resolve with a blanket `git checkout --ours`/`--theirs` across - a whole file — that silently discards one side's real change instead of merging it. `git add` - each resolved file, then `git rebase --continue`; repeat until the rebase finishes clean (a - rebase with **zero remaining conflicts** does not by itself mean the resolution is correct — - see the next point). - - A conflict resolution is a code change like any other: before pushing, run this repo's - typecheck/lint pass on the touched files (the same checks `/ship` step 6 runs) — merge - markers can resolve syntactically clean and still be semantically wrong or fail to compile. - - Push with `git push --force-with-lease` — a rebase here always rewrites already-published - history, so a plain `git push` will be rejected. - - Re-poll `gh pr view --json mergeable,mergeStateStatus` until it reports `MERGEABLE` - (not `UNKNOWN`) before moving on. If it still reports `CONFLICTING`, the rebase resolved - against a stale local `origin/staging` — `git fetch origin staging` again and redo the - rebase; don't push a second time believing it's fixed without this recheck. - - This push needs a fresh review like any other code change — go to step 8 (re-trigger - review) rather than trying to also address old review threads in the same pass, especially - if the conflict touched files a thread was anchored to (that anchor may no longer be valid - against the resolved code). Re-evaluate thread state fresh once the new round lands. + If `mergeable` comes back `CONFLICTING`, go fix that first (step 2) before evaluating review + state — a conflicting PR can't run CI, and this can happen mid-loop even on a PR that was + clean at creation, since staging moves several times a day. Otherwise, if Greptile is 5/5 and + every thread across all pages has `isResolved: true`, stop — report the outcome (see + "Reporting" below) and skip the rest of this list. + +2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git rebase + origin/staging`, resolve the conflicts for real (don't just take one side blindly), then push + with `--force-with-lease`. Continue on to step 8 to trigger a fresh review of the resolved + code. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / @@ -130,11 +100,7 @@ round. Always check both conditions freshly after every push. committing — not just lint/typecheck/boundary-validation, but also the conditional `/cleanup` (if this round's fix touched UI code) and `/db-migrate` (if it touched schema/migrations) gates from `/ship` steps 4 and 5. A review-fix round is still a code change and can trip - either gate just as easily as the original commit did. This is the same sync check as step 2 - above but for local-drift, not for the base-branch textual conflicts step 2 handles — step 2 - already left the branch rebased onto current `origin/staging` when it ran, so this step is - normally a no-op in that case, but still run it: it also catches stray local commits that - have nothing to do with merge conflicts. + either gate just as easily as the original commit did. 7. **Commit and push** the round's fixes as one commit — `--force-with-lease` whenever step 6's sync check rewrote history, which includes a plain `git rebase origin/staging` that completed @@ -164,17 +130,15 @@ round. Always check both conditions freshly after every push. in a sleep loop. Pass the same `/loop babysit PR ` prompt on each wakeup so the loop resumes correctly. -10. **Stop conditions**: clean state reached (see above), or the same unresolved finding - survives two consecutive rounds with no new information, or the same merge conflict recurs - every round with no new information (e.g. a semantic conflict you can't confidently resolve - without changing intent), or the user interrupts — surface any of these to the user instead - of looping forever. +10. **Stop conditions**: clean state reached (see above), or the same unresolved finding or + merge conflict survives two consecutive rounds with no new information (surface it to the + user instead of looping forever), or the user interrupts. ## Reporting -When the loop ends, summarize: how many rounds it took, what was actually fixed (one line each), -what was pushed back on as a false positive and why, how many merge conflicts came up and against -which staging commits (if any), and the final Greptile score / thread count / mergeable state. +When the loop ends, summarize: how many rounds it took, what was actually fixed (one line each, +including any merge conflict resolved), what was pushed back on as a false positive and why, and +the final Greptile score / thread count. ## Hard rules @@ -184,9 +148,4 @@ which staging commits (if any), and the final Greptile score / thread count / me pattern elsewhere in the codebase solving the same class of problem and match it. - Never silently drop a finding — every thread gets either a code fix or a reasoned reply. - Always re-run the `/ship`-style sync check before every push in the loop, not just the first. -- Never resolve a merge conflict with a blanket `--ours`/`--theirs` across a whole file — read - both sides and preserve the real intent of each; a wrong resolution ships silently since - nothing else catches it. -- Never treat `mergeable: UNKNOWN` as either `MERGEABLE` or `CONFLICTING` — poll until it settles. -- Never evaluate review-thread cleanliness while `mergeable` is `CONFLICTING` — resolve the - conflict first, since threads anchored to a conflicting diff can be stale. +- Never resolve a merge conflict by blindly taking one side — check the actual diff. diff --git a/.claude/commands/babysit.md b/.claude/commands/babysit.md index e7c00f5e8ac..baa95b743c3 100644 --- a/.claude/commands/babysit.md +++ b/.claude/commands/babysit.md @@ -6,11 +6,10 @@ description: Drive a PR to a clean review (Greptile 5/5, zero open threads) — Owns a PR end-to-end through review: ship it, wait for the automatic review round, and if it isn't already clean, drive fix → reply → resolve → re-review cycles until Greptile reports 5/5 -and there are zero open comment threads. Also keeps the branch mergeable against staging — a -long-running loop spans hours during which staging can drift out from under the PR and produce -a real merge conflict, not just the stray-local-commit drift `/ship`'s sync check catches. -Designed to be run under `/loop` (no fixed interval — let it self-pace on review latency) so it -survives across multiple wakeups in the same session. +and there are zero open comment threads. Also keeps the branch mergeable against staging, since +a long babysit session can outlast staging moving underneath it. Designed to be run under +`/loop` (no fixed interval — let it self-pace on review latency) so it survives across multiple +wakeups in the same session. ## When to use @@ -35,10 +34,9 @@ round. Always check both conditions freshly after every push. ## Loop -1. **Check current state** before doing anything — this includes mergeable state, not just - review state: +1. **Check current state** before doing anything, including whether the PR is still mergeable: ```bash - gh pr view --json mergeable,mergeStateStatus + gh pr view --json mergeable gh pr view --json comments -q '[.comments[] | select(.author.login=="greptile-apps")] | last | .body' gh api graphql -f query=' query { repository(owner: "", name: "") { pullRequest(number: ) { @@ -53,44 +51,16 @@ round. Always check both conditions freshly after every push. stop yet: re-run the same query with `after: ""` and keep paging until `hasNextPage` is `false` before evaluating "clean." A PR with more than 50 threads is rare but stopping on a partial page would silently miss unresolved ones past the cutoff. - `mergeable` is `UNKNOWN` for a few seconds right after any push or base-branch move while - GitHub computes it — re-poll `gh pr view --json mergeable` every few seconds until it - settles to `MERGEABLE` or `CONFLICTING` before acting on it; don't treat `UNKNOWN` as either. - If `mergeable` is `CONFLICTING`, skip straight to step 2 (Resolve merge conflicts) — do not - evaluate review cleanliness yet, since Greptile/Cursor threads anchored to a conflicting diff - can be stale and CI can't even run to confirm anything. - If `mergeable` is `MERGEABLE`, Greptile is 5/5, and every thread across all pages has - `isResolved: true`, stop — report the outcome (see "Reporting" below) and skip the rest of - this list. - -2. **If `mergeable` is `CONFLICTING`**, resolve it before anything else this round. This can - happen even on a PR that was clean at creation, since staging moves several times a day — - recheck every iteration, don't assume it's a one-time state handled by `/ship`. - ```bash - git status # stash -u first (ship-sync-fix pattern) if anything uncommitted - git fetch origin staging - git rebase origin/staging - ``` - Resolve each conflicted file on its actual merits — open it, read both sides inside the - `<<<<<<<` / `=======` / `>>>>>>>` markers, and keep the intent of *both* changes where they're - not truly contradictory. Never resolve with a blanket `git checkout --ours`/`--theirs` across - a whole file — that silently discards one side's real change instead of merging it. `git add` - each resolved file, then `git rebase --continue`; repeat until the rebase finishes clean (a - rebase with **zero remaining conflicts** does not by itself mean the resolution is correct — - see the next point). - - A conflict resolution is a code change like any other: before pushing, run this repo's - typecheck/lint pass on the touched files (the same checks `/ship` step 6 runs) — merge - markers can resolve syntactically clean and still be semantically wrong or fail to compile. - - Push with `git push --force-with-lease` — a rebase here always rewrites already-published - history, so a plain `git push` will be rejected. - - Re-poll `gh pr view --json mergeable,mergeStateStatus` until it reports `MERGEABLE` - (not `UNKNOWN`) before moving on. If it still reports `CONFLICTING`, the rebase resolved - against a stale local `origin/staging` — `git fetch origin staging` again and redo the - rebase; don't push a second time believing it's fixed without this recheck. - - This push needs a fresh review like any other code change — go to step 8 (re-trigger - review) rather than trying to also address old review threads in the same pass, especially - if the conflict touched files a thread was anchored to (that anchor may no longer be valid - against the resolved code). Re-evaluate thread state fresh once the new round lands. + If `mergeable` comes back `CONFLICTING`, go fix that first (step 2) before evaluating review + state — a conflicting PR can't run CI, and this can happen mid-loop even on a PR that was + clean at creation, since staging moves several times a day. Otherwise, if Greptile is 5/5 and + every thread across all pages has `isResolved: true`, stop — report the outcome (see + "Reporting" below) and skip the rest of this list. + +2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git rebase + origin/staging`, resolve the conflicts for real (don't just take one side blindly), then push + with `--force-with-lease`. Continue on to step 8 to trigger a fresh review of the resolved + code. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / @@ -129,11 +99,7 @@ round. Always check both conditions freshly after every push. committing — not just lint/typecheck/boundary-validation, but also the conditional `/cleanup` (if this round's fix touched UI code) and `/db-migrate` (if it touched schema/migrations) gates from `/ship` steps 4 and 5. A review-fix round is still a code change and can trip - either gate just as easily as the original commit did. This is the same sync check as step 2 - above but for local-drift, not for the base-branch textual conflicts step 2 handles — step 2 - already left the branch rebased onto current `origin/staging` when it ran, so this step is - normally a no-op in that case, but still run it: it also catches stray local commits that - have nothing to do with merge conflicts. + either gate just as easily as the original commit did. 7. **Commit and push** the round's fixes as one commit — `--force-with-lease` whenever step 6's sync check rewrote history, which includes a plain `git rebase origin/staging` that completed @@ -163,17 +129,15 @@ round. Always check both conditions freshly after every push. in a sleep loop. Pass the same `/loop babysit PR ` prompt on each wakeup so the loop resumes correctly. -10. **Stop conditions**: clean state reached (see above), or the same unresolved finding - survives two consecutive rounds with no new information, or the same merge conflict recurs - every round with no new information (e.g. a semantic conflict you can't confidently resolve - without changing intent), or the user interrupts — surface any of these to the user instead - of looping forever. +10. **Stop conditions**: clean state reached (see above), or the same unresolved finding or + merge conflict survives two consecutive rounds with no new information (surface it to the + user instead of looping forever), or the user interrupts. ## Reporting -When the loop ends, summarize: how many rounds it took, what was actually fixed (one line each), -what was pushed back on as a false positive and why, how many merge conflicts came up and against -which staging commits (if any), and the final Greptile score / thread count / mergeable state. +When the loop ends, summarize: how many rounds it took, what was actually fixed (one line each, +including any merge conflict resolved), what was pushed back on as a false positive and why, and +the final Greptile score / thread count. ## Hard rules @@ -183,9 +147,4 @@ which staging commits (if any), and the final Greptile score / thread count / me pattern elsewhere in the codebase solving the same class of problem and match it. - Never silently drop a finding — every thread gets either a code fix or a reasoned reply. - Always re-run the `/ship`-style sync check before every push in the loop, not just the first. -- Never resolve a merge conflict with a blanket `--ours`/`--theirs` across a whole file — read - both sides and preserve the real intent of each; a wrong resolution ships silently since - nothing else catches it. -- Never treat `mergeable: UNKNOWN` as either `MERGEABLE` or `CONFLICTING` — poll until it settles. -- Never evaluate review-thread cleanliness while `mergeable` is `CONFLICTING` — resolve the - conflict first, since threads anchored to a conflicting diff can be stale. +- Never resolve a merge conflict by blindly taking one side — check the actual diff. diff --git a/.cursor/commands/babysit.md b/.cursor/commands/babysit.md index eb712bee247..f2ab68945cf 100644 --- a/.cursor/commands/babysit.md +++ b/.cursor/commands/babysit.md @@ -2,11 +2,10 @@ Owns a PR end-to-end through review: ship it, wait for the automatic review round, and if it isn't already clean, drive fix → reply → resolve → re-review cycles until Greptile reports 5/5 -and there are zero open comment threads. Also keeps the branch mergeable against staging — a -long-running loop spans hours during which staging can drift out from under the PR and produce -a real merge conflict, not just the stray-local-commit drift `/ship`'s sync check catches. -Designed to be run under `/loop` (no fixed interval — let it self-pace on review latency) so it -survives across multiple wakeups in the same session. +and there are zero open comment threads. Also keeps the branch mergeable against staging, since +a long babysit session can outlast staging moving underneath it. Designed to be run under +`/loop` (no fixed interval — let it self-pace on review latency) so it survives across multiple +wakeups in the same session. ## When to use @@ -31,10 +30,9 @@ round. Always check both conditions freshly after every push. ## Loop -1. **Check current state** before doing anything — this includes mergeable state, not just - review state: +1. **Check current state** before doing anything, including whether the PR is still mergeable: ```bash - gh pr view --json mergeable,mergeStateStatus + gh pr view --json mergeable gh pr view --json comments -q '[.comments[] | select(.author.login=="greptile-apps")] | last | .body' gh api graphql -f query=' query { repository(owner: "", name: "") { pullRequest(number: ) { @@ -49,44 +47,16 @@ round. Always check both conditions freshly after every push. stop yet: re-run the same query with `after: ""` and keep paging until `hasNextPage` is `false` before evaluating "clean." A PR with more than 50 threads is rare but stopping on a partial page would silently miss unresolved ones past the cutoff. - `mergeable` is `UNKNOWN` for a few seconds right after any push or base-branch move while - GitHub computes it — re-poll `gh pr view --json mergeable` every few seconds until it - settles to `MERGEABLE` or `CONFLICTING` before acting on it; don't treat `UNKNOWN` as either. - If `mergeable` is `CONFLICTING`, skip straight to step 2 (Resolve merge conflicts) — do not - evaluate review cleanliness yet, since Greptile/Cursor threads anchored to a conflicting diff - can be stale and CI can't even run to confirm anything. - If `mergeable` is `MERGEABLE`, Greptile is 5/5, and every thread across all pages has - `isResolved: true`, stop — report the outcome (see "Reporting" below) and skip the rest of - this list. - -2. **If `mergeable` is `CONFLICTING`**, resolve it before anything else this round. This can - happen even on a PR that was clean at creation, since staging moves several times a day — - recheck every iteration, don't assume it's a one-time state handled by `/ship`. - ```bash - git status # stash -u first (ship-sync-fix pattern) if anything uncommitted - git fetch origin staging - git rebase origin/staging - ``` - Resolve each conflicted file on its actual merits — open it, read both sides inside the - `<<<<<<<` / `=======` / `>>>>>>>` markers, and keep the intent of *both* changes where they're - not truly contradictory. Never resolve with a blanket `git checkout --ours`/`--theirs` across - a whole file — that silently discards one side's real change instead of merging it. `git add` - each resolved file, then `git rebase --continue`; repeat until the rebase finishes clean (a - rebase with **zero remaining conflicts** does not by itself mean the resolution is correct — - see the next point). - - A conflict resolution is a code change like any other: before pushing, run this repo's - typecheck/lint pass on the touched files (the same checks `/ship` step 6 runs) — merge - markers can resolve syntactically clean and still be semantically wrong or fail to compile. - - Push with `git push --force-with-lease` — a rebase here always rewrites already-published - history, so a plain `git push` will be rejected. - - Re-poll `gh pr view --json mergeable,mergeStateStatus` until it reports `MERGEABLE` - (not `UNKNOWN`) before moving on. If it still reports `CONFLICTING`, the rebase resolved - against a stale local `origin/staging` — `git fetch origin staging` again and redo the - rebase; don't push a second time believing it's fixed without this recheck. - - This push needs a fresh review like any other code change — go to step 8 (re-trigger - review) rather than trying to also address old review threads in the same pass, especially - if the conflict touched files a thread was anchored to (that anchor may no longer be valid - against the resolved code). Re-evaluate thread state fresh once the new round lands. + If `mergeable` comes back `CONFLICTING`, go fix that first (step 2) before evaluating review + state — a conflicting PR can't run CI, and this can happen mid-loop even on a PR that was + clean at creation, since staging moves several times a day. Otherwise, if Greptile is 5/5 and + every thread across all pages has `isResolved: true`, stop — report the outcome (see + "Reporting" below) and skip the rest of this list. + +2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git rebase + origin/staging`, resolve the conflicts for real (don't just take one side blindly), then push + with `--force-with-lease`. Continue on to step 8 to trigger a fresh review of the resolved + code. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / @@ -125,11 +95,7 @@ round. Always check both conditions freshly after every push. committing — not just lint/typecheck/boundary-validation, but also the conditional `/cleanup` (if this round's fix touched UI code) and `/db-migrate` (if it touched schema/migrations) gates from `/ship` steps 4 and 5. A review-fix round is still a code change and can trip - either gate just as easily as the original commit did. This is the same sync check as step 2 - above but for local-drift, not for the base-branch textual conflicts step 2 handles — step 2 - already left the branch rebased onto current `origin/staging` when it ran, so this step is - normally a no-op in that case, but still run it: it also catches stray local commits that - have nothing to do with merge conflicts. + either gate just as easily as the original commit did. 7. **Commit and push** the round's fixes as one commit — `--force-with-lease` whenever step 6's sync check rewrote history, which includes a plain `git rebase origin/staging` that completed @@ -159,17 +125,15 @@ round. Always check both conditions freshly after every push. in a sleep loop. Pass the same `/loop babysit PR ` prompt on each wakeup so the loop resumes correctly. -10. **Stop conditions**: clean state reached (see above), or the same unresolved finding - survives two consecutive rounds with no new information, or the same merge conflict recurs - every round with no new information (e.g. a semantic conflict you can't confidently resolve - without changing intent), or the user interrupts — surface any of these to the user instead - of looping forever. +10. **Stop conditions**: clean state reached (see above), or the same unresolved finding or + merge conflict survives two consecutive rounds with no new information (surface it to the + user instead of looping forever), or the user interrupts. ## Reporting -When the loop ends, summarize: how many rounds it took, what was actually fixed (one line each), -what was pushed back on as a false positive and why, how many merge conflicts came up and against -which staging commits (if any), and the final Greptile score / thread count / mergeable state. +When the loop ends, summarize: how many rounds it took, what was actually fixed (one line each, +including any merge conflict resolved), what was pushed back on as a false positive and why, and +the final Greptile score / thread count. ## Hard rules @@ -179,9 +143,4 @@ which staging commits (if any), and the final Greptile score / thread count / me pattern elsewhere in the codebase solving the same class of problem and match it. - Never silently drop a finding — every thread gets either a code fix or a reasoned reply. - Always re-run the `/ship`-style sync check before every push in the loop, not just the first. -- Never resolve a merge conflict with a blanket `--ours`/`--theirs` across a whole file — read - both sides and preserve the real intent of each; a wrong resolution ships silently since - nothing else catches it. -- Never treat `mergeable: UNKNOWN` as either `MERGEABLE` or `CONFLICTING` — poll until it settles. -- Never evaluate review-thread cleanliness while `mergeable` is `CONFLICTING` — resolve the - conflict first, since threads anchored to a conflicting diff can be stale. +- Never resolve a merge conflict by blindly taking one side — check the actual diff. From 62dde5be49492ede89aa7425b71ed4ce58eb5737 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 15 Jul 2026 15:04:48 -0700 Subject: [PATCH 03/14] fix(babysit): gate conflict-resolution pushes and handle UNKNOWN mergeable --- .agents/skills/babysit/SKILL.md | 16 ++++++++++------ .claude/commands/babysit.md | 16 ++++++++++------ .cursor/commands/babysit.md | 16 ++++++++++------ 3 files changed, 30 insertions(+), 18 deletions(-) diff --git a/.agents/skills/babysit/SKILL.md b/.agents/skills/babysit/SKILL.md index 406a45ed480..603d4bbc2bc 100644 --- a/.agents/skills/babysit/SKILL.md +++ b/.agents/skills/babysit/SKILL.md @@ -54,14 +54,18 @@ round. Always check both conditions freshly after every push. stopping on a partial page would silently miss unresolved ones past the cutoff. If `mergeable` comes back `CONFLICTING`, go fix that first (step 2) before evaluating review state — a conflicting PR can't run CI, and this can happen mid-loop even on a PR that was - clean at creation, since staging moves several times a day. Otherwise, if Greptile is 5/5 and - every thread across all pages has `isResolved: true`, stop — report the outcome (see - "Reporting" below) and skip the rest of this list. + clean at creation, since staging moves several times a day. If `mergeable` is `UNKNOWN` + (GitHub still computing it), don't stop and don't treat it as either state — just wait for + the next round (step 9) and recheck then. Otherwise, if `mergeable` is `MERGEABLE`, Greptile + is 5/5, and every thread across all pages has `isResolved: true`, stop — report the outcome + (see "Reporting" below) and skip the rest of this list. 2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git rebase - origin/staging`, resolve the conflicts for real (don't just take one side blindly), then push - with `--force-with-lease`. Continue on to step 8 to trigger a fresh review of the resolved - code. + origin/staging`, resolve the conflicts for real (don't just take one side blindly), then run + the same pre-push checks as step 6 (lint, boundary validation, and the conditional + cleanup/db-migrate gates) before pushing with `--force-with-lease`, and verify the push + landed the same way step 7 does. Skip steps 3–7 and go straight to step 8 to trigger a fresh + review of the resolved code. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / diff --git a/.claude/commands/babysit.md b/.claude/commands/babysit.md index baa95b743c3..1b505feec92 100644 --- a/.claude/commands/babysit.md +++ b/.claude/commands/babysit.md @@ -53,14 +53,18 @@ round. Always check both conditions freshly after every push. stopping on a partial page would silently miss unresolved ones past the cutoff. If `mergeable` comes back `CONFLICTING`, go fix that first (step 2) before evaluating review state — a conflicting PR can't run CI, and this can happen mid-loop even on a PR that was - clean at creation, since staging moves several times a day. Otherwise, if Greptile is 5/5 and - every thread across all pages has `isResolved: true`, stop — report the outcome (see - "Reporting" below) and skip the rest of this list. + clean at creation, since staging moves several times a day. If `mergeable` is `UNKNOWN` + (GitHub still computing it), don't stop and don't treat it as either state — just wait for + the next round (step 9) and recheck then. Otherwise, if `mergeable` is `MERGEABLE`, Greptile + is 5/5, and every thread across all pages has `isResolved: true`, stop — report the outcome + (see "Reporting" below) and skip the rest of this list. 2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git rebase - origin/staging`, resolve the conflicts for real (don't just take one side blindly), then push - with `--force-with-lease`. Continue on to step 8 to trigger a fresh review of the resolved - code. + origin/staging`, resolve the conflicts for real (don't just take one side blindly), then run + the same pre-push checks as step 6 (lint, boundary validation, and the conditional + cleanup/db-migrate gates) before pushing with `--force-with-lease`, and verify the push + landed the same way step 7 does. Skip steps 3–7 and go straight to step 8 to trigger a fresh + review of the resolved code. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / diff --git a/.cursor/commands/babysit.md b/.cursor/commands/babysit.md index f2ab68945cf..4bb592b3919 100644 --- a/.cursor/commands/babysit.md +++ b/.cursor/commands/babysit.md @@ -49,14 +49,18 @@ round. Always check both conditions freshly after every push. stopping on a partial page would silently miss unresolved ones past the cutoff. If `mergeable` comes back `CONFLICTING`, go fix that first (step 2) before evaluating review state — a conflicting PR can't run CI, and this can happen mid-loop even on a PR that was - clean at creation, since staging moves several times a day. Otherwise, if Greptile is 5/5 and - every thread across all pages has `isResolved: true`, stop — report the outcome (see - "Reporting" below) and skip the rest of this list. + clean at creation, since staging moves several times a day. If `mergeable` is `UNKNOWN` + (GitHub still computing it), don't stop and don't treat it as either state — just wait for + the next round (step 9) and recheck then. Otherwise, if `mergeable` is `MERGEABLE`, Greptile + is 5/5, and every thread across all pages has `isResolved: true`, stop — report the outcome + (see "Reporting" below) and skip the rest of this list. 2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git rebase - origin/staging`, resolve the conflicts for real (don't just take one side blindly), then push - with `--force-with-lease`. Continue on to step 8 to trigger a fresh review of the resolved - code. + origin/staging`, resolve the conflicts for real (don't just take one side blindly), then run + the same pre-push checks as step 6 (lint, boundary validation, and the conditional + cleanup/db-migrate gates) before pushing with `--force-with-lease`, and verify the push + landed the same way step 7 does. Skip steps 3–7 and go straight to step 8 to trigger a fresh + review of the resolved code. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / From 3744df57e0156523f9eff455844457e8ee947fd1 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 15 Jul 2026 15:11:36 -0700 Subject: [PATCH 04/14] fix(babysit): bound persistent UNKNOWN mergeable state and clarify step skip --- .agents/skills/babysit/SKILL.md | 15 ++++++++------- .claude/commands/babysit.md | 15 ++++++++------- .cursor/commands/babysit.md | 15 ++++++++------- 3 files changed, 24 insertions(+), 21 deletions(-) diff --git a/.agents/skills/babysit/SKILL.md b/.agents/skills/babysit/SKILL.md index 603d4bbc2bc..e3f7a630628 100644 --- a/.agents/skills/babysit/SKILL.md +++ b/.agents/skills/babysit/SKILL.md @@ -55,10 +55,11 @@ round. Always check both conditions freshly after every push. If `mergeable` comes back `CONFLICTING`, go fix that first (step 2) before evaluating review state — a conflicting PR can't run CI, and this can happen mid-loop even on a PR that was clean at creation, since staging moves several times a day. If `mergeable` is `UNKNOWN` - (GitHub still computing it), don't stop and don't treat it as either state — just wait for - the next round (step 9) and recheck then. Otherwise, if `mergeable` is `MERGEABLE`, Greptile - is 5/5, and every thread across all pages has `isResolved: true`, stop — report the outcome - (see "Reporting" below) and skip the rest of this list. + (GitHub still computing it), don't treat it as either state — skip the rest of this list and + go straight to step 9 to wait and recheck next round (see step 10 for when to stop waiting on + a persistently `UNKNOWN` result). Otherwise, if `mergeable` is `MERGEABLE`, Greptile is 5/5, + and every thread across all pages has `isResolved: true`, stop — report the outcome (see + "Reporting" below) and skip the rest of this list. 2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git rebase origin/staging`, resolve the conflicts for real (don't just take one side blindly), then run @@ -134,9 +135,9 @@ round. Always check both conditions freshly after every push. in a sleep loop. Pass the same `/loop babysit PR ` prompt on each wakeup so the loop resumes correctly. -10. **Stop conditions**: clean state reached (see above), or the same unresolved finding or - merge conflict survives two consecutive rounds with no new information (surface it to the - user instead of looping forever), or the user interrupts. +10. **Stop conditions**: clean state reached (see above), or the same unresolved finding, merge + conflict, or `UNKNOWN` mergeable result survives two consecutive rounds with no new + information (surface it to the user instead of looping forever), or the user interrupts. ## Reporting diff --git a/.claude/commands/babysit.md b/.claude/commands/babysit.md index 1b505feec92..e903cac9788 100644 --- a/.claude/commands/babysit.md +++ b/.claude/commands/babysit.md @@ -54,10 +54,11 @@ round. Always check both conditions freshly after every push. If `mergeable` comes back `CONFLICTING`, go fix that first (step 2) before evaluating review state — a conflicting PR can't run CI, and this can happen mid-loop even on a PR that was clean at creation, since staging moves several times a day. If `mergeable` is `UNKNOWN` - (GitHub still computing it), don't stop and don't treat it as either state — just wait for - the next round (step 9) and recheck then. Otherwise, if `mergeable` is `MERGEABLE`, Greptile - is 5/5, and every thread across all pages has `isResolved: true`, stop — report the outcome - (see "Reporting" below) and skip the rest of this list. + (GitHub still computing it), don't treat it as either state — skip the rest of this list and + go straight to step 9 to wait and recheck next round (see step 10 for when to stop waiting on + a persistently `UNKNOWN` result). Otherwise, if `mergeable` is `MERGEABLE`, Greptile is 5/5, + and every thread across all pages has `isResolved: true`, stop — report the outcome (see + "Reporting" below) and skip the rest of this list. 2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git rebase origin/staging`, resolve the conflicts for real (don't just take one side blindly), then run @@ -133,9 +134,9 @@ round. Always check both conditions freshly after every push. in a sleep loop. Pass the same `/loop babysit PR ` prompt on each wakeup so the loop resumes correctly. -10. **Stop conditions**: clean state reached (see above), or the same unresolved finding or - merge conflict survives two consecutive rounds with no new information (surface it to the - user instead of looping forever), or the user interrupts. +10. **Stop conditions**: clean state reached (see above), or the same unresolved finding, merge + conflict, or `UNKNOWN` mergeable result survives two consecutive rounds with no new + information (surface it to the user instead of looping forever), or the user interrupts. ## Reporting diff --git a/.cursor/commands/babysit.md b/.cursor/commands/babysit.md index 4bb592b3919..5593ac28140 100644 --- a/.cursor/commands/babysit.md +++ b/.cursor/commands/babysit.md @@ -50,10 +50,11 @@ round. Always check both conditions freshly after every push. If `mergeable` comes back `CONFLICTING`, go fix that first (step 2) before evaluating review state — a conflicting PR can't run CI, and this can happen mid-loop even on a PR that was clean at creation, since staging moves several times a day. If `mergeable` is `UNKNOWN` - (GitHub still computing it), don't stop and don't treat it as either state — just wait for - the next round (step 9) and recheck then. Otherwise, if `mergeable` is `MERGEABLE`, Greptile - is 5/5, and every thread across all pages has `isResolved: true`, stop — report the outcome - (see "Reporting" below) and skip the rest of this list. + (GitHub still computing it), don't treat it as either state — skip the rest of this list and + go straight to step 9 to wait and recheck next round (see step 10 for when to stop waiting on + a persistently `UNKNOWN` result). Otherwise, if `mergeable` is `MERGEABLE`, Greptile is 5/5, + and every thread across all pages has `isResolved: true`, stop — report the outcome (see + "Reporting" below) and skip the rest of this list. 2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git rebase origin/staging`, resolve the conflicts for real (don't just take one side blindly), then run @@ -129,9 +130,9 @@ round. Always check both conditions freshly after every push. in a sleep loop. Pass the same `/loop babysit PR ` prompt on each wakeup so the loop resumes correctly. -10. **Stop conditions**: clean state reached (see above), or the same unresolved finding or - merge conflict survives two consecutive rounds with no new information (surface it to the - user instead of looping forever), or the user interrupts. +10. **Stop conditions**: clean state reached (see above), or the same unresolved finding, merge + conflict, or `UNKNOWN` mergeable result survives two consecutive rounds with no new + information (surface it to the user instead of looping forever), or the user interrupts. ## Reporting From f6aea3a915a5ca95bbdfddcb13f4f9222d06d01e Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 15 Jul 2026 16:43:08 -0700 Subject: [PATCH 05/14] fix(babysit): merge instead of rebase to resolve conflicts, drop force-with-lease --- .agents/skills/babysit/SKILL.md | 5 +++-- .claude/commands/babysit.md | 5 +++-- .cursor/commands/babysit.md | 5 +++-- 3 files changed, 9 insertions(+), 6 deletions(-) diff --git a/.agents/skills/babysit/SKILL.md b/.agents/skills/babysit/SKILL.md index e3f7a630628..db1088e1a7e 100644 --- a/.agents/skills/babysit/SKILL.md +++ b/.agents/skills/babysit/SKILL.md @@ -61,10 +61,11 @@ round. Always check both conditions freshly after every push. and every thread across all pages has `isResolved: true`, stop — report the outcome (see "Reporting" below) and skip the rest of this list. -2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git rebase +2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git merge origin/staging`, resolve the conflicts for real (don't just take one side blindly), then run the same pre-push checks as step 6 (lint, boundary validation, and the conditional - cleanup/db-migrate gates) before pushing with `--force-with-lease`, and verify the push + cleanup/db-migrate gates) before a plain `git push` — a merge commit doesn't rewrite + already-published history, so this never needs `--force-with-lease` — and verify the push landed the same way step 7 does. Skip steps 3–7 and go straight to step 8 to trigger a fresh review of the resolved code. diff --git a/.claude/commands/babysit.md b/.claude/commands/babysit.md index e903cac9788..ca0ea0ddf26 100644 --- a/.claude/commands/babysit.md +++ b/.claude/commands/babysit.md @@ -60,10 +60,11 @@ round. Always check both conditions freshly after every push. and every thread across all pages has `isResolved: true`, stop — report the outcome (see "Reporting" below) and skip the rest of this list. -2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git rebase +2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git merge origin/staging`, resolve the conflicts for real (don't just take one side blindly), then run the same pre-push checks as step 6 (lint, boundary validation, and the conditional - cleanup/db-migrate gates) before pushing with `--force-with-lease`, and verify the push + cleanup/db-migrate gates) before a plain `git push` — a merge commit doesn't rewrite + already-published history, so this never needs `--force-with-lease` — and verify the push landed the same way step 7 does. Skip steps 3–7 and go straight to step 8 to trigger a fresh review of the resolved code. diff --git a/.cursor/commands/babysit.md b/.cursor/commands/babysit.md index 5593ac28140..0116e6e867c 100644 --- a/.cursor/commands/babysit.md +++ b/.cursor/commands/babysit.md @@ -56,10 +56,11 @@ round. Always check both conditions freshly after every push. and every thread across all pages has `isResolved: true`, stop — report the outcome (see "Reporting" below) and skip the rest of this list. -2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git rebase +2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git merge origin/staging`, resolve the conflicts for real (don't just take one side blindly), then run the same pre-push checks as step 6 (lint, boundary validation, and the conditional - cleanup/db-migrate gates) before pushing with `--force-with-lease`, and verify the push + cleanup/db-migrate gates) before a plain `git push` — a merge commit doesn't rewrite + already-published history, so this never needs `--force-with-lease` — and verify the push landed the same way step 7 does. Skip steps 3–7 and go straight to step 8 to trigger a fresh review of the resolved code. From e3c5efebbb7a71670f1187f40395f44acfb811bd Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 15 Jul 2026 16:46:32 -0700 Subject: [PATCH 06/14] fix(babysit): don't skip pending review findings when resolving a merge conflict --- .agents/skills/babysit/SKILL.md | 14 ++++++++------ .claude/commands/babysit.md | 14 ++++++++------ .cursor/commands/babysit.md | 14 ++++++++------ 3 files changed, 24 insertions(+), 18 deletions(-) diff --git a/.agents/skills/babysit/SKILL.md b/.agents/skills/babysit/SKILL.md index db1088e1a7e..ffdd1820283 100644 --- a/.agents/skills/babysit/SKILL.md +++ b/.agents/skills/babysit/SKILL.md @@ -62,12 +62,14 @@ round. Always check both conditions freshly after every push. "Reporting" below) and skip the rest of this list. 2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git merge - origin/staging`, resolve the conflicts for real (don't just take one side blindly), then run - the same pre-push checks as step 6 (lint, boundary validation, and the conditional - cleanup/db-migrate gates) before a plain `git push` — a merge commit doesn't rewrite - already-published history, so this never needs `--force-with-lease` — and verify the push - landed the same way step 7 does. Skip steps 3–7 and go straight to step 8 to trigger a fresh - review of the resolved code. + origin/staging`, resolve the conflicts for real (don't just take one side blindly). If step + 1 also found unresolved review threads, don't push the conflict fix alone and leave those + findings unaddressed — triage and fix them now too (step 4), replying/resolving each (step + 5), in the same pass. Then run the same pre-push checks as step 6 (lint, boundary + validation, and the conditional cleanup/db-migrate gates) before a plain `git push` — a + merge commit doesn't rewrite already-published history, so this never needs + `--force-with-lease` — and verify the push landed the same way step 7 does. Skip step 3 and + go straight to step 8 to trigger a fresh review of the resolved code. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / diff --git a/.claude/commands/babysit.md b/.claude/commands/babysit.md index ca0ea0ddf26..6ee11ee60b8 100644 --- a/.claude/commands/babysit.md +++ b/.claude/commands/babysit.md @@ -61,12 +61,14 @@ round. Always check both conditions freshly after every push. "Reporting" below) and skip the rest of this list. 2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git merge - origin/staging`, resolve the conflicts for real (don't just take one side blindly), then run - the same pre-push checks as step 6 (lint, boundary validation, and the conditional - cleanup/db-migrate gates) before a plain `git push` — a merge commit doesn't rewrite - already-published history, so this never needs `--force-with-lease` — and verify the push - landed the same way step 7 does. Skip steps 3–7 and go straight to step 8 to trigger a fresh - review of the resolved code. + origin/staging`, resolve the conflicts for real (don't just take one side blindly). If step + 1 also found unresolved review threads, don't push the conflict fix alone and leave those + findings unaddressed — triage and fix them now too (step 4), replying/resolving each (step + 5), in the same pass. Then run the same pre-push checks as step 6 (lint, boundary + validation, and the conditional cleanup/db-migrate gates) before a plain `git push` — a + merge commit doesn't rewrite already-published history, so this never needs + `--force-with-lease` — and verify the push landed the same way step 7 does. Skip step 3 and + go straight to step 8 to trigger a fresh review of the resolved code. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / diff --git a/.cursor/commands/babysit.md b/.cursor/commands/babysit.md index 0116e6e867c..2ff832b90ed 100644 --- a/.cursor/commands/babysit.md +++ b/.cursor/commands/babysit.md @@ -57,12 +57,14 @@ round. Always check both conditions freshly after every push. "Reporting" below) and skip the rest of this list. 2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git merge - origin/staging`, resolve the conflicts for real (don't just take one side blindly), then run - the same pre-push checks as step 6 (lint, boundary validation, and the conditional - cleanup/db-migrate gates) before a plain `git push` — a merge commit doesn't rewrite - already-published history, so this never needs `--force-with-lease` — and verify the push - landed the same way step 7 does. Skip steps 3–7 and go straight to step 8 to trigger a fresh - review of the resolved code. + origin/staging`, resolve the conflicts for real (don't just take one side blindly). If step + 1 also found unresolved review threads, don't push the conflict fix alone and leave those + findings unaddressed — triage and fix them now too (step 4), replying/resolving each (step + 5), in the same pass. Then run the same pre-push checks as step 6 (lint, boundary + validation, and the conditional cleanup/db-migrate gates) before a plain `git push` — a + merge commit doesn't rewrite already-published history, so this never needs + `--force-with-lease` — and verify the push landed the same way step 7 does. Skip step 3 and + go straight to step 8 to trigger a fresh review of the resolved code. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / From 435e14c253918dd51dca4e7189be3ce79e38c1d2 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 15 Jul 2026 16:52:18 -0700 Subject: [PATCH 07/14] fix(babysit): spot-check commit hygiene before a merge-conflict push --- .agents/skills/babysit/SKILL.md | 10 +++++++--- .claude/commands/babysit.md | 10 +++++++--- .cursor/commands/babysit.md | 10 +++++++--- 3 files changed, 21 insertions(+), 9 deletions(-) diff --git a/.agents/skills/babysit/SKILL.md b/.agents/skills/babysit/SKILL.md index ffdd1820283..a063d475518 100644 --- a/.agents/skills/babysit/SKILL.md +++ b/.agents/skills/babysit/SKILL.md @@ -65,9 +65,13 @@ round. Always check both conditions freshly after every push. origin/staging`, resolve the conflicts for real (don't just take one side blindly). If step 1 also found unresolved review threads, don't push the conflict fix alone and leave those findings unaddressed — triage and fix them now too (step 4), replying/resolving each (step - 5), in the same pass. Then run the same pre-push checks as step 6 (lint, boundary - validation, and the conditional cleanup/db-migrate gates) before a plain `git push` — a - merge commit doesn't rewrite already-published history, so this never needs + 5), in the same pass. The merge is this round's sync check (it already pulls in current + `origin/staging`) — no need to also run step 6's stash/rebase/cherry-pick machinery, which is + for a different problem (local stray commits) — but do spot-check + `git log --oneline --reverse origin/staging..HEAD` still shows only commits you recognize + before pushing, same as step 6 would. Then run the same pre-push checks as step 6 (lint, + boundary validation, and the conditional cleanup/db-migrate gates) before a plain `git push` + — a merge commit doesn't rewrite already-published history, so this never needs `--force-with-lease` — and verify the push landed the same way step 7 does. Skip step 3 and go straight to step 8 to trigger a fresh review of the resolved code. diff --git a/.claude/commands/babysit.md b/.claude/commands/babysit.md index 6ee11ee60b8..fe2e7f8647f 100644 --- a/.claude/commands/babysit.md +++ b/.claude/commands/babysit.md @@ -64,9 +64,13 @@ round. Always check both conditions freshly after every push. origin/staging`, resolve the conflicts for real (don't just take one side blindly). If step 1 also found unresolved review threads, don't push the conflict fix alone and leave those findings unaddressed — triage and fix them now too (step 4), replying/resolving each (step - 5), in the same pass. Then run the same pre-push checks as step 6 (lint, boundary - validation, and the conditional cleanup/db-migrate gates) before a plain `git push` — a - merge commit doesn't rewrite already-published history, so this never needs + 5), in the same pass. The merge is this round's sync check (it already pulls in current + `origin/staging`) — no need to also run step 6's stash/rebase/cherry-pick machinery, which is + for a different problem (local stray commits) — but do spot-check + `git log --oneline --reverse origin/staging..HEAD` still shows only commits you recognize + before pushing, same as step 6 would. Then run the same pre-push checks as step 6 (lint, + boundary validation, and the conditional cleanup/db-migrate gates) before a plain `git push` + — a merge commit doesn't rewrite already-published history, so this never needs `--force-with-lease` — and verify the push landed the same way step 7 does. Skip step 3 and go straight to step 8 to trigger a fresh review of the resolved code. diff --git a/.cursor/commands/babysit.md b/.cursor/commands/babysit.md index 2ff832b90ed..ae2ddbaae34 100644 --- a/.cursor/commands/babysit.md +++ b/.cursor/commands/babysit.md @@ -60,9 +60,13 @@ round. Always check both conditions freshly after every push. origin/staging`, resolve the conflicts for real (don't just take one side blindly). If step 1 also found unresolved review threads, don't push the conflict fix alone and leave those findings unaddressed — triage and fix them now too (step 4), replying/resolving each (step - 5), in the same pass. Then run the same pre-push checks as step 6 (lint, boundary - validation, and the conditional cleanup/db-migrate gates) before a plain `git push` — a - merge commit doesn't rewrite already-published history, so this never needs + 5), in the same pass. The merge is this round's sync check (it already pulls in current + `origin/staging`) — no need to also run step 6's stash/rebase/cherry-pick machinery, which is + for a different problem (local stray commits) — but do spot-check + `git log --oneline --reverse origin/staging..HEAD` still shows only commits you recognize + before pushing, same as step 6 would. Then run the same pre-push checks as step 6 (lint, + boundary validation, and the conditional cleanup/db-migrate gates) before a plain `git push` + — a merge commit doesn't rewrite already-published history, so this never needs `--force-with-lease` — and verify the push landed the same way step 7 does. Skip step 3 and go straight to step 8 to trigger a fresh review of the resolved code. From 9d7612fa4cbe09a5ae2d00454ca65b1ef433afd5 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 15 Jul 2026 16:58:29 -0700 Subject: [PATCH 08/14] fix(babysit): commit merge-conflict fixes before pushing, bound UNKNOWN before waiting --- .agents/skills/babysit/SKILL.md | 36 ++++++++++++++++++--------------- .claude/commands/babysit.md | 36 ++++++++++++++++++--------------- .cursor/commands/babysit.md | 36 ++++++++++++++++++--------------- 3 files changed, 60 insertions(+), 48 deletions(-) diff --git a/.agents/skills/babysit/SKILL.md b/.agents/skills/babysit/SKILL.md index a063d475518..a02e4e8156f 100644 --- a/.agents/skills/babysit/SKILL.md +++ b/.agents/skills/babysit/SKILL.md @@ -55,25 +55,29 @@ round. Always check both conditions freshly after every push. If `mergeable` comes back `CONFLICTING`, go fix that first (step 2) before evaluating review state — a conflicting PR can't run CI, and this can happen mid-loop even on a PR that was clean at creation, since staging moves several times a day. If `mergeable` is `UNKNOWN` - (GitHub still computing it), don't treat it as either state — skip the rest of this list and - go straight to step 9 to wait and recheck next round (see step 10 for when to stop waiting on - a persistently `UNKNOWN` result). Otherwise, if `mergeable` is `MERGEABLE`, Greptile is 5/5, - and every thread across all pages has `isResolved: true`, stop — report the outcome (see + (GitHub still computing it), don't treat it as either state — but before waiting, check step + 10's two-consecutive-rounds condition first: if it was also `UNKNOWN` on the immediately + preceding round (recall that from this session, not a fresh query), stop now and surface it + instead of scheduling another wakeup; otherwise skip the rest of this list and go straight to + step 9 to wait and recheck next round. Otherwise, if `mergeable` is `MERGEABLE`, Greptile is + 5/5, and every thread across all pages has `isResolved: true`, stop — report the outcome (see "Reporting" below) and skip the rest of this list. 2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git merge - origin/staging`, resolve the conflicts for real (don't just take one side blindly). If step - 1 also found unresolved review threads, don't push the conflict fix alone and leave those - findings unaddressed — triage and fix them now too (step 4), replying/resolving each (step - 5), in the same pass. The merge is this round's sync check (it already pulls in current - `origin/staging`) — no need to also run step 6's stash/rebase/cherry-pick machinery, which is - for a different problem (local stray commits) — but do spot-check - `git log --oneline --reverse origin/staging..HEAD` still shows only commits you recognize - before pushing, same as step 6 would. Then run the same pre-push checks as step 6 (lint, - boundary validation, and the conditional cleanup/db-migrate gates) before a plain `git push` - — a merge commit doesn't rewrite already-published history, so this never needs - `--force-with-lease` — and verify the push landed the same way step 7 does. Skip step 3 and - go straight to step 8 to trigger a fresh review of the resolved code. + origin/staging`, resolve the conflicts for real (don't just take one side blindly), `git add` + the resolved files, then `git commit` to complete the merge commit — a merge with conflicts + stays uncommitted until you do this. If step 1 also found unresolved review threads, don't + push the conflict fix alone and leave those findings unaddressed — triage and fix them now + too (step 4), replying/resolving each (step 5), then `git add`/`git commit` those as their + own commit same as step 7 would (keep it separate from the merge commit). The merge is this + round's sync check (it already pulls in current `origin/staging`) — no need to also run step + 6's stash/rebase/cherry-pick machinery, which is for a different problem (local stray + commits) — but do spot-check `git log --oneline --reverse origin/staging..HEAD` still shows + only commits you recognize before pushing, same as step 6 would. Then run the same pre-push + checks as step 6 (lint, boundary validation, and the conditional cleanup/db-migrate gates) + before a plain `git push` — a merge commit doesn't rewrite already-published history, so this + never needs `--force-with-lease` — and verify the push landed the same way step 7 does. Skip + step 3 and go straight to step 8 to trigger a fresh review of the resolved code. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / diff --git a/.claude/commands/babysit.md b/.claude/commands/babysit.md index fe2e7f8647f..d2f8c7ae0c1 100644 --- a/.claude/commands/babysit.md +++ b/.claude/commands/babysit.md @@ -54,25 +54,29 @@ round. Always check both conditions freshly after every push. If `mergeable` comes back `CONFLICTING`, go fix that first (step 2) before evaluating review state — a conflicting PR can't run CI, and this can happen mid-loop even on a PR that was clean at creation, since staging moves several times a day. If `mergeable` is `UNKNOWN` - (GitHub still computing it), don't treat it as either state — skip the rest of this list and - go straight to step 9 to wait and recheck next round (see step 10 for when to stop waiting on - a persistently `UNKNOWN` result). Otherwise, if `mergeable` is `MERGEABLE`, Greptile is 5/5, - and every thread across all pages has `isResolved: true`, stop — report the outcome (see + (GitHub still computing it), don't treat it as either state — but before waiting, check step + 10's two-consecutive-rounds condition first: if it was also `UNKNOWN` on the immediately + preceding round (recall that from this session, not a fresh query), stop now and surface it + instead of scheduling another wakeup; otherwise skip the rest of this list and go straight to + step 9 to wait and recheck next round. Otherwise, if `mergeable` is `MERGEABLE`, Greptile is + 5/5, and every thread across all pages has `isResolved: true`, stop — report the outcome (see "Reporting" below) and skip the rest of this list. 2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git merge - origin/staging`, resolve the conflicts for real (don't just take one side blindly). If step - 1 also found unresolved review threads, don't push the conflict fix alone and leave those - findings unaddressed — triage and fix them now too (step 4), replying/resolving each (step - 5), in the same pass. The merge is this round's sync check (it already pulls in current - `origin/staging`) — no need to also run step 6's stash/rebase/cherry-pick machinery, which is - for a different problem (local stray commits) — but do spot-check - `git log --oneline --reverse origin/staging..HEAD` still shows only commits you recognize - before pushing, same as step 6 would. Then run the same pre-push checks as step 6 (lint, - boundary validation, and the conditional cleanup/db-migrate gates) before a plain `git push` - — a merge commit doesn't rewrite already-published history, so this never needs - `--force-with-lease` — and verify the push landed the same way step 7 does. Skip step 3 and - go straight to step 8 to trigger a fresh review of the resolved code. + origin/staging`, resolve the conflicts for real (don't just take one side blindly), `git add` + the resolved files, then `git commit` to complete the merge commit — a merge with conflicts + stays uncommitted until you do this. If step 1 also found unresolved review threads, don't + push the conflict fix alone and leave those findings unaddressed — triage and fix them now + too (step 4), replying/resolving each (step 5), then `git add`/`git commit` those as their + own commit same as step 7 would (keep it separate from the merge commit). The merge is this + round's sync check (it already pulls in current `origin/staging`) — no need to also run step + 6's stash/rebase/cherry-pick machinery, which is for a different problem (local stray + commits) — but do spot-check `git log --oneline --reverse origin/staging..HEAD` still shows + only commits you recognize before pushing, same as step 6 would. Then run the same pre-push + checks as step 6 (lint, boundary validation, and the conditional cleanup/db-migrate gates) + before a plain `git push` — a merge commit doesn't rewrite already-published history, so this + never needs `--force-with-lease` — and verify the push landed the same way step 7 does. Skip + step 3 and go straight to step 8 to trigger a fresh review of the resolved code. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / diff --git a/.cursor/commands/babysit.md b/.cursor/commands/babysit.md index ae2ddbaae34..a9fba9fffd6 100644 --- a/.cursor/commands/babysit.md +++ b/.cursor/commands/babysit.md @@ -50,25 +50,29 @@ round. Always check both conditions freshly after every push. If `mergeable` comes back `CONFLICTING`, go fix that first (step 2) before evaluating review state — a conflicting PR can't run CI, and this can happen mid-loop even on a PR that was clean at creation, since staging moves several times a day. If `mergeable` is `UNKNOWN` - (GitHub still computing it), don't treat it as either state — skip the rest of this list and - go straight to step 9 to wait and recheck next round (see step 10 for when to stop waiting on - a persistently `UNKNOWN` result). Otherwise, if `mergeable` is `MERGEABLE`, Greptile is 5/5, - and every thread across all pages has `isResolved: true`, stop — report the outcome (see + (GitHub still computing it), don't treat it as either state — but before waiting, check step + 10's two-consecutive-rounds condition first: if it was also `UNKNOWN` on the immediately + preceding round (recall that from this session, not a fresh query), stop now and surface it + instead of scheduling another wakeup; otherwise skip the rest of this list and go straight to + step 9 to wait and recheck next round. Otherwise, if `mergeable` is `MERGEABLE`, Greptile is + 5/5, and every thread across all pages has `isResolved: true`, stop — report the outcome (see "Reporting" below) and skip the rest of this list. 2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git merge - origin/staging`, resolve the conflicts for real (don't just take one side blindly). If step - 1 also found unresolved review threads, don't push the conflict fix alone and leave those - findings unaddressed — triage and fix them now too (step 4), replying/resolving each (step - 5), in the same pass. The merge is this round's sync check (it already pulls in current - `origin/staging`) — no need to also run step 6's stash/rebase/cherry-pick machinery, which is - for a different problem (local stray commits) — but do spot-check - `git log --oneline --reverse origin/staging..HEAD` still shows only commits you recognize - before pushing, same as step 6 would. Then run the same pre-push checks as step 6 (lint, - boundary validation, and the conditional cleanup/db-migrate gates) before a plain `git push` - — a merge commit doesn't rewrite already-published history, so this never needs - `--force-with-lease` — and verify the push landed the same way step 7 does. Skip step 3 and - go straight to step 8 to trigger a fresh review of the resolved code. + origin/staging`, resolve the conflicts for real (don't just take one side blindly), `git add` + the resolved files, then `git commit` to complete the merge commit — a merge with conflicts + stays uncommitted until you do this. If step 1 also found unresolved review threads, don't + push the conflict fix alone and leave those findings unaddressed — triage and fix them now + too (step 4), replying/resolving each (step 5), then `git add`/`git commit` those as their + own commit same as step 7 would (keep it separate from the merge commit). The merge is this + round's sync check (it already pulls in current `origin/staging`) — no need to also run step + 6's stash/rebase/cherry-pick machinery, which is for a different problem (local stray + commits) — but do spot-check `git log --oneline --reverse origin/staging..HEAD` still shows + only commits you recognize before pushing, same as step 6 would. Then run the same pre-push + checks as step 6 (lint, boundary validation, and the conditional cleanup/db-migrate gates) + before a plain `git push` — a merge commit doesn't rewrite already-published history, so this + never needs `--force-with-lease` — and verify the push landed the same way step 7 does. Skip + step 3 and go straight to step 8 to trigger a fresh review of the resolved code. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / From 4a46125dc1aa387553e5cbfc65150b4d640e28e5 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 15 Jul 2026 17:04:16 -0700 Subject: [PATCH 09/14] fix(babysit): run pre-push checks before committing the merge-conflict fix --- .agents/skills/babysit/SKILL.md | 31 +++++++++++++++++-------------- .claude/commands/babysit.md | 31 +++++++++++++++++-------------- .cursor/commands/babysit.md | 31 +++++++++++++++++-------------- 3 files changed, 51 insertions(+), 42 deletions(-) diff --git a/.agents/skills/babysit/SKILL.md b/.agents/skills/babysit/SKILL.md index a02e4e8156f..cb369089caa 100644 --- a/.agents/skills/babysit/SKILL.md +++ b/.agents/skills/babysit/SKILL.md @@ -64,20 +64,23 @@ round. Always check both conditions freshly after every push. "Reporting" below) and skip the rest of this list. 2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git merge - origin/staging`, resolve the conflicts for real (don't just take one side blindly), `git add` - the resolved files, then `git commit` to complete the merge commit — a merge with conflicts - stays uncommitted until you do this. If step 1 also found unresolved review threads, don't - push the conflict fix alone and leave those findings unaddressed — triage and fix them now - too (step 4), replying/resolving each (step 5), then `git add`/`git commit` those as their - own commit same as step 7 would (keep it separate from the merge commit). The merge is this - round's sync check (it already pulls in current `origin/staging`) — no need to also run step - 6's stash/rebase/cherry-pick machinery, which is for a different problem (local stray - commits) — but do spot-check `git log --oneline --reverse origin/staging..HEAD` still shows - only commits you recognize before pushing, same as step 6 would. Then run the same pre-push - checks as step 6 (lint, boundary validation, and the conditional cleanup/db-migrate gates) - before a plain `git push` — a merge commit doesn't rewrite already-published history, so this - never needs `--force-with-lease` — and verify the push landed the same way step 7 does. Skip - step 3 and go straight to step 8 to trigger a fresh review of the resolved code. + origin/staging`, resolve the conflicts for real (don't just take one side blindly). If step 1 + also found unresolved review threads, don't leave those findings unaddressed while you're + already touching the branch — triage and fix them now too (step 4), replying/resolving each + (step 5). Before committing anything, run the same pre-push checks as step 6 (lint, boundary + validation, and the conditional cleanup/db-migrate gates) — same order step 6 → step 7 uses, + checks before commit, not after, since `lint` can auto-fix files and committing first would + leave those fixes unstaged after push. Then `git add` the resolved/fixed files and `git + commit` to complete the merge commit — a merge with conflicts stays uncommitted until you do + this — committing any review-thread fixes as their own separate commit same as step 7 would + (keep it separate from the merge commit). The merge is this round's sync check (it already + pulls in current `origin/staging`) — no need to also run step 6's stash/rebase/cherry-pick + machinery, which is for a different problem (local stray commits) — but do spot-check + `git log --oneline --reverse origin/staging..HEAD` still shows only commits you recognize + before pushing, same as step 6 would. Then a plain `git push` — a merge commit doesn't + rewrite already-published history, so this never needs `--force-with-lease` — and verify the + push landed the same way step 7 does. Skip step 3 and go straight to step 8 to trigger a + fresh review of the resolved code. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / diff --git a/.claude/commands/babysit.md b/.claude/commands/babysit.md index d2f8c7ae0c1..7f55fece1e5 100644 --- a/.claude/commands/babysit.md +++ b/.claude/commands/babysit.md @@ -63,20 +63,23 @@ round. Always check both conditions freshly after every push. "Reporting" below) and skip the rest of this list. 2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git merge - origin/staging`, resolve the conflicts for real (don't just take one side blindly), `git add` - the resolved files, then `git commit` to complete the merge commit — a merge with conflicts - stays uncommitted until you do this. If step 1 also found unresolved review threads, don't - push the conflict fix alone and leave those findings unaddressed — triage and fix them now - too (step 4), replying/resolving each (step 5), then `git add`/`git commit` those as their - own commit same as step 7 would (keep it separate from the merge commit). The merge is this - round's sync check (it already pulls in current `origin/staging`) — no need to also run step - 6's stash/rebase/cherry-pick machinery, which is for a different problem (local stray - commits) — but do spot-check `git log --oneline --reverse origin/staging..HEAD` still shows - only commits you recognize before pushing, same as step 6 would. Then run the same pre-push - checks as step 6 (lint, boundary validation, and the conditional cleanup/db-migrate gates) - before a plain `git push` — a merge commit doesn't rewrite already-published history, so this - never needs `--force-with-lease` — and verify the push landed the same way step 7 does. Skip - step 3 and go straight to step 8 to trigger a fresh review of the resolved code. + origin/staging`, resolve the conflicts for real (don't just take one side blindly). If step 1 + also found unresolved review threads, don't leave those findings unaddressed while you're + already touching the branch — triage and fix them now too (step 4), replying/resolving each + (step 5). Before committing anything, run the same pre-push checks as step 6 (lint, boundary + validation, and the conditional cleanup/db-migrate gates) — same order step 6 → step 7 uses, + checks before commit, not after, since `lint` can auto-fix files and committing first would + leave those fixes unstaged after push. Then `git add` the resolved/fixed files and `git + commit` to complete the merge commit — a merge with conflicts stays uncommitted until you do + this — committing any review-thread fixes as their own separate commit same as step 7 would + (keep it separate from the merge commit). The merge is this round's sync check (it already + pulls in current `origin/staging`) — no need to also run step 6's stash/rebase/cherry-pick + machinery, which is for a different problem (local stray commits) — but do spot-check + `git log --oneline --reverse origin/staging..HEAD` still shows only commits you recognize + before pushing, same as step 6 would. Then a plain `git push` — a merge commit doesn't + rewrite already-published history, so this never needs `--force-with-lease` — and verify the + push landed the same way step 7 does. Skip step 3 and go straight to step 8 to trigger a + fresh review of the resolved code. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / diff --git a/.cursor/commands/babysit.md b/.cursor/commands/babysit.md index a9fba9fffd6..afccf41a7fd 100644 --- a/.cursor/commands/babysit.md +++ b/.cursor/commands/babysit.md @@ -59,20 +59,23 @@ round. Always check both conditions freshly after every push. "Reporting" below) and skip the rest of this list. 2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git merge - origin/staging`, resolve the conflicts for real (don't just take one side blindly), `git add` - the resolved files, then `git commit` to complete the merge commit — a merge with conflicts - stays uncommitted until you do this. If step 1 also found unresolved review threads, don't - push the conflict fix alone and leave those findings unaddressed — triage and fix them now - too (step 4), replying/resolving each (step 5), then `git add`/`git commit` those as their - own commit same as step 7 would (keep it separate from the merge commit). The merge is this - round's sync check (it already pulls in current `origin/staging`) — no need to also run step - 6's stash/rebase/cherry-pick machinery, which is for a different problem (local stray - commits) — but do spot-check `git log --oneline --reverse origin/staging..HEAD` still shows - only commits you recognize before pushing, same as step 6 would. Then run the same pre-push - checks as step 6 (lint, boundary validation, and the conditional cleanup/db-migrate gates) - before a plain `git push` — a merge commit doesn't rewrite already-published history, so this - never needs `--force-with-lease` — and verify the push landed the same way step 7 does. Skip - step 3 and go straight to step 8 to trigger a fresh review of the resolved code. + origin/staging`, resolve the conflicts for real (don't just take one side blindly). If step 1 + also found unresolved review threads, don't leave those findings unaddressed while you're + already touching the branch — triage and fix them now too (step 4), replying/resolving each + (step 5). Before committing anything, run the same pre-push checks as step 6 (lint, boundary + validation, and the conditional cleanup/db-migrate gates) — same order step 6 → step 7 uses, + checks before commit, not after, since `lint` can auto-fix files and committing first would + leave those fixes unstaged after push. Then `git add` the resolved/fixed files and `git + commit` to complete the merge commit — a merge with conflicts stays uncommitted until you do + this — committing any review-thread fixes as their own separate commit same as step 7 would + (keep it separate from the merge commit). The merge is this round's sync check (it already + pulls in current `origin/staging`) — no need to also run step 6's stash/rebase/cherry-pick + machinery, which is for a different problem (local stray commits) — but do spot-check + `git log --oneline --reverse origin/staging..HEAD` still shows only commits you recognize + before pushing, same as step 6 would. Then a plain `git push` — a merge commit doesn't + rewrite already-published history, so this never needs `--force-with-lease` — and verify the + push landed the same way step 7 does. Skip step 3 and go straight to step 8 to trigger a + fresh review of the resolved code. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / From 4d758cd1f0a33c91dd8e080e336e756226a18669 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 15 Jul 2026 17:11:02 -0700 Subject: [PATCH 10/14] fix(babysit): bound persistent CONFLICTING state and fix pre-push check order --- .agents/skills/babysit/SKILL.md | 37 +++++++++++++++++++-------------- .claude/commands/babysit.md | 37 +++++++++++++++++++-------------- .cursor/commands/babysit.md | 37 +++++++++++++++++++-------------- 3 files changed, 63 insertions(+), 48 deletions(-) diff --git a/.agents/skills/babysit/SKILL.md b/.agents/skills/babysit/SKILL.md index cb369089caa..b1f6772b217 100644 --- a/.agents/skills/babysit/SKILL.md +++ b/.agents/skills/babysit/SKILL.md @@ -52,25 +52,29 @@ round. Always check both conditions freshly after every push. stop yet: re-run the same query with `after: ""` and keep paging until `hasNextPage` is `false` before evaluating "clean." A PR with more than 50 threads is rare but stopping on a partial page would silently miss unresolved ones past the cutoff. - If `mergeable` comes back `CONFLICTING`, go fix that first (step 2) before evaluating review + If `mergeable` comes back `CONFLICTING`, check step 10's two-consecutive-rounds condition + first — if it was also `CONFLICTING` after the immediately preceding round's attempted fix + with no new information (recall that from this session, not a fresh query), stop now and + surface it instead of trying again; otherwise go fix it (step 2) before evaluating review state — a conflicting PR can't run CI, and this can happen mid-loop even on a PR that was clean at creation, since staging moves several times a day. If `mergeable` is `UNKNOWN` - (GitHub still computing it), don't treat it as either state — but before waiting, check step - 10's two-consecutive-rounds condition first: if it was also `UNKNOWN` on the immediately - preceding round (recall that from this session, not a fresh query), stop now and surface it - instead of scheduling another wakeup; otherwise skip the rest of this list and go straight to - step 9 to wait and recheck next round. Otherwise, if `mergeable` is `MERGEABLE`, Greptile is - 5/5, and every thread across all pages has `isResolved: true`, stop — report the outcome (see - "Reporting" below) and skip the rest of this list. + (GitHub still computing it), don't treat it as either state — but before waiting, check the + same two-consecutive-rounds condition: if it was also `UNKNOWN` on the immediately preceding + round, stop now and surface it instead of scheduling another wakeup; otherwise skip the rest + of this list and go straight to step 9 to wait and recheck next round. Otherwise, if + `mergeable` is `MERGEABLE`, Greptile is 5/5, and every thread across all pages has + `isResolved: true`, stop — report the outcome (see "Reporting" below) and skip the rest of + this list. 2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git merge origin/staging`, resolve the conflicts for real (don't just take one side blindly). If step 1 also found unresolved review threads, don't leave those findings unaddressed while you're already touching the branch — triage and fix them now too (step 4), replying/resolving each - (step 5). Before committing anything, run the same pre-push checks as step 6 (lint, boundary - validation, and the conditional cleanup/db-migrate gates) — same order step 6 → step 7 uses, - checks before commit, not after, since `lint` can auto-fix files and committing first would - leave those fixes unstaged after push. Then `git add` the resolved/fixed files and `git + (step 5). Before committing anything, run the same pre-push checks as step 6, in the same + order `/ship` runs them (the conditional cleanup/db-migrate gates first, since they can + rewrite code, then lint and boundary validation last so they check the final state) — checks + before commit, not after, since `lint` can auto-fix files and committing first would leave + those fixes unstaged after push. Then `git add` the resolved/fixed files and `git commit` to complete the merge commit — a merge with conflicts stays uncommitted until you do this — committing any review-thread fixes as their own separate commit same as step 7 would (keep it separate from the merge commit). The merge is this round's sync check (it already @@ -116,10 +120,11 @@ round. Always check both conditions freshly after every push. loop spanning a long session is exactly the scenario where a branch can drift, and pushing review fixes on top of undetected drift is how an oversized PR happens even after the branch was fixed once. Then run the repo's pre-ship checks the same way `/ship` does before - committing — not just lint/typecheck/boundary-validation, but also the conditional `/cleanup` - (if this round's fix touched UI code) and `/db-migrate` (if it touched schema/migrations) - gates from `/ship` steps 4 and 5. A review-fix round is still a code change and can trip - either gate just as easily as the original commit did. + committing, in the same order — the conditional `/cleanup` (if this round's fix touched UI + code) and `/db-migrate` (if it touched schema/migrations) gates from `/ship` steps 4 and 5 + first, since they can rewrite code, then lint/typecheck/boundary-validation last so they + check the final state. A review-fix round is still a code change and can trip either gate + just as easily as the original commit did. 7. **Commit and push** the round's fixes as one commit — `--force-with-lease` whenever step 6's sync check rewrote history, which includes a plain `git rebase origin/staging` that completed diff --git a/.claude/commands/babysit.md b/.claude/commands/babysit.md index 7f55fece1e5..2fedd2b498d 100644 --- a/.claude/commands/babysit.md +++ b/.claude/commands/babysit.md @@ -51,25 +51,29 @@ round. Always check both conditions freshly after every push. stop yet: re-run the same query with `after: ""` and keep paging until `hasNextPage` is `false` before evaluating "clean." A PR with more than 50 threads is rare but stopping on a partial page would silently miss unresolved ones past the cutoff. - If `mergeable` comes back `CONFLICTING`, go fix that first (step 2) before evaluating review + If `mergeable` comes back `CONFLICTING`, check step 10's two-consecutive-rounds condition + first — if it was also `CONFLICTING` after the immediately preceding round's attempted fix + with no new information (recall that from this session, not a fresh query), stop now and + surface it instead of trying again; otherwise go fix it (step 2) before evaluating review state — a conflicting PR can't run CI, and this can happen mid-loop even on a PR that was clean at creation, since staging moves several times a day. If `mergeable` is `UNKNOWN` - (GitHub still computing it), don't treat it as either state — but before waiting, check step - 10's two-consecutive-rounds condition first: if it was also `UNKNOWN` on the immediately - preceding round (recall that from this session, not a fresh query), stop now and surface it - instead of scheduling another wakeup; otherwise skip the rest of this list and go straight to - step 9 to wait and recheck next round. Otherwise, if `mergeable` is `MERGEABLE`, Greptile is - 5/5, and every thread across all pages has `isResolved: true`, stop — report the outcome (see - "Reporting" below) and skip the rest of this list. + (GitHub still computing it), don't treat it as either state — but before waiting, check the + same two-consecutive-rounds condition: if it was also `UNKNOWN` on the immediately preceding + round, stop now and surface it instead of scheduling another wakeup; otherwise skip the rest + of this list and go straight to step 9 to wait and recheck next round. Otherwise, if + `mergeable` is `MERGEABLE`, Greptile is 5/5, and every thread across all pages has + `isResolved: true`, stop — report the outcome (see "Reporting" below) and skip the rest of + this list. 2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git merge origin/staging`, resolve the conflicts for real (don't just take one side blindly). If step 1 also found unresolved review threads, don't leave those findings unaddressed while you're already touching the branch — triage and fix them now too (step 4), replying/resolving each - (step 5). Before committing anything, run the same pre-push checks as step 6 (lint, boundary - validation, and the conditional cleanup/db-migrate gates) — same order step 6 → step 7 uses, - checks before commit, not after, since `lint` can auto-fix files and committing first would - leave those fixes unstaged after push. Then `git add` the resolved/fixed files and `git + (step 5). Before committing anything, run the same pre-push checks as step 6, in the same + order `/ship` runs them (the conditional cleanup/db-migrate gates first, since they can + rewrite code, then lint and boundary validation last so they check the final state) — checks + before commit, not after, since `lint` can auto-fix files and committing first would leave + those fixes unstaged after push. Then `git add` the resolved/fixed files and `git commit` to complete the merge commit — a merge with conflicts stays uncommitted until you do this — committing any review-thread fixes as their own separate commit same as step 7 would (keep it separate from the merge commit). The merge is this round's sync check (it already @@ -115,10 +119,11 @@ round. Always check both conditions freshly after every push. loop spanning a long session is exactly the scenario where a branch can drift, and pushing review fixes on top of undetected drift is how an oversized PR happens even after the branch was fixed once. Then run the repo's pre-ship checks the same way `/ship` does before - committing — not just lint/typecheck/boundary-validation, but also the conditional `/cleanup` - (if this round's fix touched UI code) and `/db-migrate` (if it touched schema/migrations) - gates from `/ship` steps 4 and 5. A review-fix round is still a code change and can trip - either gate just as easily as the original commit did. + committing, in the same order — the conditional `/cleanup` (if this round's fix touched UI + code) and `/db-migrate` (if it touched schema/migrations) gates from `/ship` steps 4 and 5 + first, since they can rewrite code, then lint/typecheck/boundary-validation last so they + check the final state. A review-fix round is still a code change and can trip either gate + just as easily as the original commit did. 7. **Commit and push** the round's fixes as one commit — `--force-with-lease` whenever step 6's sync check rewrote history, which includes a plain `git rebase origin/staging` that completed diff --git a/.cursor/commands/babysit.md b/.cursor/commands/babysit.md index afccf41a7fd..8e5ce7081ec 100644 --- a/.cursor/commands/babysit.md +++ b/.cursor/commands/babysit.md @@ -47,25 +47,29 @@ round. Always check both conditions freshly after every push. stop yet: re-run the same query with `after: ""` and keep paging until `hasNextPage` is `false` before evaluating "clean." A PR with more than 50 threads is rare but stopping on a partial page would silently miss unresolved ones past the cutoff. - If `mergeable` comes back `CONFLICTING`, go fix that first (step 2) before evaluating review + If `mergeable` comes back `CONFLICTING`, check step 10's two-consecutive-rounds condition + first — if it was also `CONFLICTING` after the immediately preceding round's attempted fix + with no new information (recall that from this session, not a fresh query), stop now and + surface it instead of trying again; otherwise go fix it (step 2) before evaluating review state — a conflicting PR can't run CI, and this can happen mid-loop even on a PR that was clean at creation, since staging moves several times a day. If `mergeable` is `UNKNOWN` - (GitHub still computing it), don't treat it as either state — but before waiting, check step - 10's two-consecutive-rounds condition first: if it was also `UNKNOWN` on the immediately - preceding round (recall that from this session, not a fresh query), stop now and surface it - instead of scheduling another wakeup; otherwise skip the rest of this list and go straight to - step 9 to wait and recheck next round. Otherwise, if `mergeable` is `MERGEABLE`, Greptile is - 5/5, and every thread across all pages has `isResolved: true`, stop — report the outcome (see - "Reporting" below) and skip the rest of this list. + (GitHub still computing it), don't treat it as either state — but before waiting, check the + same two-consecutive-rounds condition: if it was also `UNKNOWN` on the immediately preceding + round, stop now and surface it instead of scheduling another wakeup; otherwise skip the rest + of this list and go straight to step 9 to wait and recheck next round. Otherwise, if + `mergeable` is `MERGEABLE`, Greptile is 5/5, and every thread across all pages has + `isResolved: true`, stop — report the outcome (see "Reporting" below) and skip the rest of + this list. 2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git merge origin/staging`, resolve the conflicts for real (don't just take one side blindly). If step 1 also found unresolved review threads, don't leave those findings unaddressed while you're already touching the branch — triage and fix them now too (step 4), replying/resolving each - (step 5). Before committing anything, run the same pre-push checks as step 6 (lint, boundary - validation, and the conditional cleanup/db-migrate gates) — same order step 6 → step 7 uses, - checks before commit, not after, since `lint` can auto-fix files and committing first would - leave those fixes unstaged after push. Then `git add` the resolved/fixed files and `git + (step 5). Before committing anything, run the same pre-push checks as step 6, in the same + order `/ship` runs them (the conditional cleanup/db-migrate gates first, since they can + rewrite code, then lint and boundary validation last so they check the final state) — checks + before commit, not after, since `lint` can auto-fix files and committing first would leave + those fixes unstaged after push. Then `git add` the resolved/fixed files and `git commit` to complete the merge commit — a merge with conflicts stays uncommitted until you do this — committing any review-thread fixes as their own separate commit same as step 7 would (keep it separate from the merge commit). The merge is this round's sync check (it already @@ -111,10 +115,11 @@ round. Always check both conditions freshly after every push. loop spanning a long session is exactly the scenario where a branch can drift, and pushing review fixes on top of undetected drift is how an oversized PR happens even after the branch was fixed once. Then run the repo's pre-ship checks the same way `/ship` does before - committing — not just lint/typecheck/boundary-validation, but also the conditional `/cleanup` - (if this round's fix touched UI code) and `/db-migrate` (if it touched schema/migrations) - gates from `/ship` steps 4 and 5. A review-fix round is still a code change and can trip - either gate just as easily as the original commit did. + committing, in the same order — the conditional `/cleanup` (if this round's fix touched UI + code) and `/db-migrate` (if it touched schema/migrations) gates from `/ship` steps 4 and 5 + first, since they can rewrite code, then lint/typecheck/boundary-validation last so they + check the final state. A review-fix round is still a code change and can trip either gate + just as easily as the original commit did. 7. **Commit and push** the round's fixes as one commit — `--force-with-lease` whenever step 6's sync check rewrote history, which includes a plain `git rebase origin/staging` that completed From 71648c7e9a928817ab9d1ca0c56c9a7deda081f2 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 15 Jul 2026 17:16:57 -0700 Subject: [PATCH 11/14] fix(babysit): reconcile hard rule with step 2's merge-conflict sync exception --- .agents/skills/babysit/SKILL.md | 5 ++++- .claude/commands/babysit.md | 5 ++++- .cursor/commands/babysit.md | 5 ++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/.agents/skills/babysit/SKILL.md b/.agents/skills/babysit/SKILL.md index b1f6772b217..e8ec30a6d7d 100644 --- a/.agents/skills/babysit/SKILL.md +++ b/.agents/skills/babysit/SKILL.md @@ -171,5 +171,8 @@ the final Greptile score / thread count. - Never fix a finding with a hacky workaround — if the clean fix isn't obvious, find the sibling pattern elsewhere in the codebase solving the same class of problem and match it. - Never silently drop a finding — every thread gets either a code fix or a reasoned reply. -- Always re-run the `/ship`-style sync check before every push in the loop, not just the first. +- Always re-run the `/ship`-style sync check before every push in the loop, not just the + first — except step 2's merge-conflict push, where `git merge origin/staging` plus the + spot-check already serves as that round's sync check; don't also run step 6's + stash/rebase/cherry-pick flow on top of it. - Never resolve a merge conflict by blindly taking one side — check the actual diff. diff --git a/.claude/commands/babysit.md b/.claude/commands/babysit.md index 2fedd2b498d..94538be0c5c 100644 --- a/.claude/commands/babysit.md +++ b/.claude/commands/babysit.md @@ -170,5 +170,8 @@ the final Greptile score / thread count. - Never fix a finding with a hacky workaround — if the clean fix isn't obvious, find the sibling pattern elsewhere in the codebase solving the same class of problem and match it. - Never silently drop a finding — every thread gets either a code fix or a reasoned reply. -- Always re-run the `/ship`-style sync check before every push in the loop, not just the first. +- Always re-run the `/ship`-style sync check before every push in the loop, not just the + first — except step 2's merge-conflict push, where `git merge origin/staging` plus the + spot-check already serves as that round's sync check; don't also run step 6's + stash/rebase/cherry-pick flow on top of it. - Never resolve a merge conflict by blindly taking one side — check the actual diff. diff --git a/.cursor/commands/babysit.md b/.cursor/commands/babysit.md index 8e5ce7081ec..a5c03076d52 100644 --- a/.cursor/commands/babysit.md +++ b/.cursor/commands/babysit.md @@ -166,5 +166,8 @@ the final Greptile score / thread count. - Never fix a finding with a hacky workaround — if the clean fix isn't obvious, find the sibling pattern elsewhere in the codebase solving the same class of problem and match it. - Never silently drop a finding — every thread gets either a code fix or a reasoned reply. -- Always re-run the `/ship`-style sync check before every push in the loop, not just the first. +- Always re-run the `/ship`-style sync check before every push in the loop, not just the + first — except step 2's merge-conflict push, where `git merge origin/staging` plus the + spot-check already serves as that round's sync check; don't also run step 6's + stash/rebase/cherry-pick flow on top of it. - Never resolve a merge conflict by blindly taking one side — check the actual diff. From ff7474be9417f20af9e5e5c18c532e7370857fdb Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 15 Jul 2026 17:23:12 -0700 Subject: [PATCH 12/14] fix(babysit): page all review threads before branching on mergeable state --- .agents/skills/babysit/SKILL.md | 7 +++++-- .claude/commands/babysit.md | 7 +++++-- .cursor/commands/babysit.md | 7 +++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/.agents/skills/babysit/SKILL.md b/.agents/skills/babysit/SKILL.md index e8ec30a6d7d..6b81b3249b1 100644 --- a/.agents/skills/babysit/SKILL.md +++ b/.agents/skills/babysit/SKILL.md @@ -50,8 +50,11 @@ round. Always check both conditions freshly after every push. *comment*, so it silently misses the actual "Confidence Score: X/5" line. `reviewThreads(first: 50)` is a single page — check `pageInfo.hasNextPage`. If `true`, don't stop yet: re-run the same query with `after: ""` and keep paging until - `hasNextPage` is `false` before evaluating "clean." A PR with more than 50 threads is rare but - stopping on a partial page would silently miss unresolved ones past the cutoff. + `hasNextPage` is `false` before doing anything else with the thread list, not just before + evaluating "clean" — step 2's "did step 1 also find unresolved review threads" check depends + on the same complete, paged list, so page fully before branching on `mergeable` at all. A PR + with more than 50 threads is rare but stopping on a partial page would silently miss + unresolved ones past the cutoff. If `mergeable` comes back `CONFLICTING`, check step 10's two-consecutive-rounds condition first — if it was also `CONFLICTING` after the immediately preceding round's attempted fix with no new information (recall that from this session, not a fresh query), stop now and diff --git a/.claude/commands/babysit.md b/.claude/commands/babysit.md index 94538be0c5c..b5bc87e5b10 100644 --- a/.claude/commands/babysit.md +++ b/.claude/commands/babysit.md @@ -49,8 +49,11 @@ round. Always check both conditions freshly after every push. *comment*, so it silently misses the actual "Confidence Score: X/5" line. `reviewThreads(first: 50)` is a single page — check `pageInfo.hasNextPage`. If `true`, don't stop yet: re-run the same query with `after: ""` and keep paging until - `hasNextPage` is `false` before evaluating "clean." A PR with more than 50 threads is rare but - stopping on a partial page would silently miss unresolved ones past the cutoff. + `hasNextPage` is `false` before doing anything else with the thread list, not just before + evaluating "clean" — step 2's "did step 1 also find unresolved review threads" check depends + on the same complete, paged list, so page fully before branching on `mergeable` at all. A PR + with more than 50 threads is rare but stopping on a partial page would silently miss + unresolved ones past the cutoff. If `mergeable` comes back `CONFLICTING`, check step 10's two-consecutive-rounds condition first — if it was also `CONFLICTING` after the immediately preceding round's attempted fix with no new information (recall that from this session, not a fresh query), stop now and diff --git a/.cursor/commands/babysit.md b/.cursor/commands/babysit.md index a5c03076d52..c71957f78a7 100644 --- a/.cursor/commands/babysit.md +++ b/.cursor/commands/babysit.md @@ -45,8 +45,11 @@ round. Always check both conditions freshly after every push. *comment*, so it silently misses the actual "Confidence Score: X/5" line. `reviewThreads(first: 50)` is a single page — check `pageInfo.hasNextPage`. If `true`, don't stop yet: re-run the same query with `after: ""` and keep paging until - `hasNextPage` is `false` before evaluating "clean." A PR with more than 50 threads is rare but - stopping on a partial page would silently miss unresolved ones past the cutoff. + `hasNextPage` is `false` before doing anything else with the thread list, not just before + evaluating "clean" — step 2's "did step 1 also find unresolved review threads" check depends + on the same complete, paged list, so page fully before branching on `mergeable` at all. A PR + with more than 50 threads is rare but stopping on a partial page would silently miss + unresolved ones past the cutoff. If `mergeable` comes back `CONFLICTING`, check step 10's two-consecutive-rounds condition first — if it was also `CONFLICTING` after the immediately preceding round's attempted fix with no new information (recall that from this session, not a fresh query), stop now and From 7232f3099baec9406424bb487ea8c39cbe75f3ff Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 15 Jul 2026 17:29:34 -0700 Subject: [PATCH 13/14] fix(babysit): cut merge-conflict handling down to the essentials --- .agents/skills/babysit/SKILL.md | 77 ++++++++++----------------------- .claude/commands/babysit.md | 77 ++++++++++----------------------- .cursor/commands/babysit.md | 77 ++++++++++----------------------- 3 files changed, 72 insertions(+), 159 deletions(-) diff --git a/.agents/skills/babysit/SKILL.md b/.agents/skills/babysit/SKILL.md index 6b81b3249b1..bf596f4b78e 100644 --- a/.agents/skills/babysit/SKILL.md +++ b/.agents/skills/babysit/SKILL.md @@ -8,9 +8,10 @@ description: Drive a PR to a clean review (Greptile 5/5, zero open threads) — Owns a PR end-to-end through review: ship it, wait for the automatic review round, and if it isn't already clean, drive fix → reply → resolve → re-review cycles until Greptile reports 5/5 and there are zero open comment threads. Also keeps the branch mergeable against staging, since -a long babysit session can outlast staging moving underneath it. Designed to be run under -`/loop` (no fixed interval — let it self-pace on review latency) so it survives across multiple -wakeups in the same session. +a long babysit session can outlast staging moving underneath it — it's just a normal merge +conflict, resolve it like you would any other. Designed to be run under `/loop` (no fixed +interval — let it self-pace on review latency) so it survives across multiple wakeups in the +same session. ## When to use @@ -50,44 +51,18 @@ round. Always check both conditions freshly after every push. *comment*, so it silently misses the actual "Confidence Score: X/5" line. `reviewThreads(first: 50)` is a single page — check `pageInfo.hasNextPage`. If `true`, don't stop yet: re-run the same query with `after: ""` and keep paging until - `hasNextPage` is `false` before doing anything else with the thread list, not just before - evaluating "clean" — step 2's "did step 1 also find unresolved review threads" check depends - on the same complete, paged list, so page fully before branching on `mergeable` at all. A PR - with more than 50 threads is rare but stopping on a partial page would silently miss - unresolved ones past the cutoff. - If `mergeable` comes back `CONFLICTING`, check step 10's two-consecutive-rounds condition - first — if it was also `CONFLICTING` after the immediately preceding round's attempted fix - with no new information (recall that from this session, not a fresh query), stop now and - surface it instead of trying again; otherwise go fix it (step 2) before evaluating review - state — a conflicting PR can't run CI, and this can happen mid-loop even on a PR that was - clean at creation, since staging moves several times a day. If `mergeable` is `UNKNOWN` - (GitHub still computing it), don't treat it as either state — but before waiting, check the - same two-consecutive-rounds condition: if it was also `UNKNOWN` on the immediately preceding - round, stop now and surface it instead of scheduling another wakeup; otherwise skip the rest - of this list and go straight to step 9 to wait and recheck next round. Otherwise, if - `mergeable` is `MERGEABLE`, Greptile is 5/5, and every thread across all pages has - `isResolved: true`, stop — report the outcome (see "Reporting" below) and skip the rest of - this list. - -2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git merge - origin/staging`, resolve the conflicts for real (don't just take one side blindly). If step 1 - also found unresolved review threads, don't leave those findings unaddressed while you're - already touching the branch — triage and fix them now too (step 4), replying/resolving each - (step 5). Before committing anything, run the same pre-push checks as step 6, in the same - order `/ship` runs them (the conditional cleanup/db-migrate gates first, since they can - rewrite code, then lint and boundary validation last so they check the final state) — checks - before commit, not after, since `lint` can auto-fix files and committing first would leave - those fixes unstaged after push. Then `git add` the resolved/fixed files and `git - commit` to complete the merge commit — a merge with conflicts stays uncommitted until you do - this — committing any review-thread fixes as their own separate commit same as step 7 would - (keep it separate from the merge commit). The merge is this round's sync check (it already - pulls in current `origin/staging`) — no need to also run step 6's stash/rebase/cherry-pick - machinery, which is for a different problem (local stray commits) — but do spot-check - `git log --oneline --reverse origin/staging..HEAD` still shows only commits you recognize - before pushing, same as step 6 would. Then a plain `git push` — a merge commit doesn't - rewrite already-published history, so this never needs `--force-with-lease` — and verify the - push landed the same way step 7 does. Skip step 3 and go straight to step 8 to trigger a - fresh review of the resolved code. + `hasNextPage` is `false` before evaluating "clean." A PR with more than 50 threads is rare but + stopping on a partial page would silently miss unresolved ones past the cutoff. + If `mergeable` is `CONFLICTING`, resolve it first (step 2). If it's `UNKNOWN` (GitHub still + computing it), just wait for the next round (step 9) and recheck. Otherwise, if `mergeable` + is `MERGEABLE`, Greptile is 5/5, and every thread across all pages has `isResolved: true`, + stop — report the outcome (see "Reporting" below) and skip the rest of this list. + +2. **If the PR has a merge conflict**, resolve it: merge `origin/staging`, fix the conflicts for + real (don't just take one side), run the repo's usual checks, commit, and push — a plain + `git push` is fine since merging doesn't rewrite history. If there are also pending review + findings, fix those in the same pass rather than pushing the conflict fix alone. Then go to + step 8 to trigger a fresh review. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / @@ -123,11 +98,10 @@ round. Always check both conditions freshly after every push. loop spanning a long session is exactly the scenario where a branch can drift, and pushing review fixes on top of undetected drift is how an oversized PR happens even after the branch was fixed once. Then run the repo's pre-ship checks the same way `/ship` does before - committing, in the same order — the conditional `/cleanup` (if this round's fix touched UI - code) and `/db-migrate` (if it touched schema/migrations) gates from `/ship` steps 4 and 5 - first, since they can rewrite code, then lint/typecheck/boundary-validation last so they - check the final state. A review-fix round is still a code change and can trip either gate - just as easily as the original commit did. + committing — not just lint/typecheck/boundary-validation, but also the conditional `/cleanup` + (if this round's fix touched UI code) and `/db-migrate` (if it touched schema/migrations) + gates from `/ship` steps 4 and 5. A review-fix round is still a code change and can trip + either gate just as easily as the original commit did. 7. **Commit and push** the round's fixes as one commit — `--force-with-lease` whenever step 6's sync check rewrote history, which includes a plain `git rebase origin/staging` that completed @@ -157,9 +131,9 @@ round. Always check both conditions freshly after every push. in a sleep loop. Pass the same `/loop babysit PR ` prompt on each wakeup so the loop resumes correctly. -10. **Stop conditions**: clean state reached (see above), or the same unresolved finding, merge - conflict, or `UNKNOWN` mergeable result survives two consecutive rounds with no new - information (surface it to the user instead of looping forever), or the user interrupts. +10. **Stop conditions**: clean state reached (see above), or the same unresolved finding or + merge conflict survives two consecutive rounds with no new information (surface it to the + user instead of looping forever), or the user interrupts. ## Reporting @@ -174,8 +148,5 @@ the final Greptile score / thread count. - Never fix a finding with a hacky workaround — if the clean fix isn't obvious, find the sibling pattern elsewhere in the codebase solving the same class of problem and match it. - Never silently drop a finding — every thread gets either a code fix or a reasoned reply. -- Always re-run the `/ship`-style sync check before every push in the loop, not just the - first — except step 2's merge-conflict push, where `git merge origin/staging` plus the - spot-check already serves as that round's sync check; don't also run step 6's - stash/rebase/cherry-pick flow on top of it. +- Always re-run the `/ship`-style sync check before every push in the loop, not just the first. - Never resolve a merge conflict by blindly taking one side — check the actual diff. diff --git a/.claude/commands/babysit.md b/.claude/commands/babysit.md index b5bc87e5b10..390a8162494 100644 --- a/.claude/commands/babysit.md +++ b/.claude/commands/babysit.md @@ -7,9 +7,10 @@ description: Drive a PR to a clean review (Greptile 5/5, zero open threads) — Owns a PR end-to-end through review: ship it, wait for the automatic review round, and if it isn't already clean, drive fix → reply → resolve → re-review cycles until Greptile reports 5/5 and there are zero open comment threads. Also keeps the branch mergeable against staging, since -a long babysit session can outlast staging moving underneath it. Designed to be run under -`/loop` (no fixed interval — let it self-pace on review latency) so it survives across multiple -wakeups in the same session. +a long babysit session can outlast staging moving underneath it — it's just a normal merge +conflict, resolve it like you would any other. Designed to be run under `/loop` (no fixed +interval — let it self-pace on review latency) so it survives across multiple wakeups in the +same session. ## When to use @@ -49,44 +50,18 @@ round. Always check both conditions freshly after every push. *comment*, so it silently misses the actual "Confidence Score: X/5" line. `reviewThreads(first: 50)` is a single page — check `pageInfo.hasNextPage`. If `true`, don't stop yet: re-run the same query with `after: ""` and keep paging until - `hasNextPage` is `false` before doing anything else with the thread list, not just before - evaluating "clean" — step 2's "did step 1 also find unresolved review threads" check depends - on the same complete, paged list, so page fully before branching on `mergeable` at all. A PR - with more than 50 threads is rare but stopping on a partial page would silently miss - unresolved ones past the cutoff. - If `mergeable` comes back `CONFLICTING`, check step 10's two-consecutive-rounds condition - first — if it was also `CONFLICTING` after the immediately preceding round's attempted fix - with no new information (recall that from this session, not a fresh query), stop now and - surface it instead of trying again; otherwise go fix it (step 2) before evaluating review - state — a conflicting PR can't run CI, and this can happen mid-loop even on a PR that was - clean at creation, since staging moves several times a day. If `mergeable` is `UNKNOWN` - (GitHub still computing it), don't treat it as either state — but before waiting, check the - same two-consecutive-rounds condition: if it was also `UNKNOWN` on the immediately preceding - round, stop now and surface it instead of scheduling another wakeup; otherwise skip the rest - of this list and go straight to step 9 to wait and recheck next round. Otherwise, if - `mergeable` is `MERGEABLE`, Greptile is 5/5, and every thread across all pages has - `isResolved: true`, stop — report the outcome (see "Reporting" below) and skip the rest of - this list. - -2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git merge - origin/staging`, resolve the conflicts for real (don't just take one side blindly). If step 1 - also found unresolved review threads, don't leave those findings unaddressed while you're - already touching the branch — triage and fix them now too (step 4), replying/resolving each - (step 5). Before committing anything, run the same pre-push checks as step 6, in the same - order `/ship` runs them (the conditional cleanup/db-migrate gates first, since they can - rewrite code, then lint and boundary validation last so they check the final state) — checks - before commit, not after, since `lint` can auto-fix files and committing first would leave - those fixes unstaged after push. Then `git add` the resolved/fixed files and `git - commit` to complete the merge commit — a merge with conflicts stays uncommitted until you do - this — committing any review-thread fixes as their own separate commit same as step 7 would - (keep it separate from the merge commit). The merge is this round's sync check (it already - pulls in current `origin/staging`) — no need to also run step 6's stash/rebase/cherry-pick - machinery, which is for a different problem (local stray commits) — but do spot-check - `git log --oneline --reverse origin/staging..HEAD` still shows only commits you recognize - before pushing, same as step 6 would. Then a plain `git push` — a merge commit doesn't - rewrite already-published history, so this never needs `--force-with-lease` — and verify the - push landed the same way step 7 does. Skip step 3 and go straight to step 8 to trigger a - fresh review of the resolved code. + `hasNextPage` is `false` before evaluating "clean." A PR with more than 50 threads is rare but + stopping on a partial page would silently miss unresolved ones past the cutoff. + If `mergeable` is `CONFLICTING`, resolve it first (step 2). If it's `UNKNOWN` (GitHub still + computing it), just wait for the next round (step 9) and recheck. Otherwise, if `mergeable` + is `MERGEABLE`, Greptile is 5/5, and every thread across all pages has `isResolved: true`, + stop — report the outcome (see "Reporting" below) and skip the rest of this list. + +2. **If the PR has a merge conflict**, resolve it: merge `origin/staging`, fix the conflicts for + real (don't just take one side), run the repo's usual checks, commit, and push — a plain + `git push` is fine since merging doesn't rewrite history. If there are also pending review + findings, fix those in the same pass rather than pushing the conflict fix alone. Then go to + step 8 to trigger a fresh review. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / @@ -122,11 +97,10 @@ round. Always check both conditions freshly after every push. loop spanning a long session is exactly the scenario where a branch can drift, and pushing review fixes on top of undetected drift is how an oversized PR happens even after the branch was fixed once. Then run the repo's pre-ship checks the same way `/ship` does before - committing, in the same order — the conditional `/cleanup` (if this round's fix touched UI - code) and `/db-migrate` (if it touched schema/migrations) gates from `/ship` steps 4 and 5 - first, since they can rewrite code, then lint/typecheck/boundary-validation last so they - check the final state. A review-fix round is still a code change and can trip either gate - just as easily as the original commit did. + committing — not just lint/typecheck/boundary-validation, but also the conditional `/cleanup` + (if this round's fix touched UI code) and `/db-migrate` (if it touched schema/migrations) + gates from `/ship` steps 4 and 5. A review-fix round is still a code change and can trip + either gate just as easily as the original commit did. 7. **Commit and push** the round's fixes as one commit — `--force-with-lease` whenever step 6's sync check rewrote history, which includes a plain `git rebase origin/staging` that completed @@ -156,9 +130,9 @@ round. Always check both conditions freshly after every push. in a sleep loop. Pass the same `/loop babysit PR ` prompt on each wakeup so the loop resumes correctly. -10. **Stop conditions**: clean state reached (see above), or the same unresolved finding, merge - conflict, or `UNKNOWN` mergeable result survives two consecutive rounds with no new - information (surface it to the user instead of looping forever), or the user interrupts. +10. **Stop conditions**: clean state reached (see above), or the same unresolved finding or + merge conflict survives two consecutive rounds with no new information (surface it to the + user instead of looping forever), or the user interrupts. ## Reporting @@ -173,8 +147,5 @@ the final Greptile score / thread count. - Never fix a finding with a hacky workaround — if the clean fix isn't obvious, find the sibling pattern elsewhere in the codebase solving the same class of problem and match it. - Never silently drop a finding — every thread gets either a code fix or a reasoned reply. -- Always re-run the `/ship`-style sync check before every push in the loop, not just the - first — except step 2's merge-conflict push, where `git merge origin/staging` plus the - spot-check already serves as that round's sync check; don't also run step 6's - stash/rebase/cherry-pick flow on top of it. +- Always re-run the `/ship`-style sync check before every push in the loop, not just the first. - Never resolve a merge conflict by blindly taking one side — check the actual diff. diff --git a/.cursor/commands/babysit.md b/.cursor/commands/babysit.md index c71957f78a7..bc8f971a028 100644 --- a/.cursor/commands/babysit.md +++ b/.cursor/commands/babysit.md @@ -3,9 +3,10 @@ Owns a PR end-to-end through review: ship it, wait for the automatic review round, and if it isn't already clean, drive fix → reply → resolve → re-review cycles until Greptile reports 5/5 and there are zero open comment threads. Also keeps the branch mergeable against staging, since -a long babysit session can outlast staging moving underneath it. Designed to be run under -`/loop` (no fixed interval — let it self-pace on review latency) so it survives across multiple -wakeups in the same session. +a long babysit session can outlast staging moving underneath it — it's just a normal merge +conflict, resolve it like you would any other. Designed to be run under `/loop` (no fixed +interval — let it self-pace on review latency) so it survives across multiple wakeups in the +same session. ## When to use @@ -45,44 +46,18 @@ round. Always check both conditions freshly after every push. *comment*, so it silently misses the actual "Confidence Score: X/5" line. `reviewThreads(first: 50)` is a single page — check `pageInfo.hasNextPage`. If `true`, don't stop yet: re-run the same query with `after: ""` and keep paging until - `hasNextPage` is `false` before doing anything else with the thread list, not just before - evaluating "clean" — step 2's "did step 1 also find unresolved review threads" check depends - on the same complete, paged list, so page fully before branching on `mergeable` at all. A PR - with more than 50 threads is rare but stopping on a partial page would silently miss - unresolved ones past the cutoff. - If `mergeable` comes back `CONFLICTING`, check step 10's two-consecutive-rounds condition - first — if it was also `CONFLICTING` after the immediately preceding round's attempted fix - with no new information (recall that from this session, not a fresh query), stop now and - surface it instead of trying again; otherwise go fix it (step 2) before evaluating review - state — a conflicting PR can't run CI, and this can happen mid-loop even on a PR that was - clean at creation, since staging moves several times a day. If `mergeable` is `UNKNOWN` - (GitHub still computing it), don't treat it as either state — but before waiting, check the - same two-consecutive-rounds condition: if it was also `UNKNOWN` on the immediately preceding - round, stop now and surface it instead of scheduling another wakeup; otherwise skip the rest - of this list and go straight to step 9 to wait and recheck next round. Otherwise, if - `mergeable` is `MERGEABLE`, Greptile is 5/5, and every thread across all pages has - `isResolved: true`, stop — report the outcome (see "Reporting" below) and skip the rest of - this list. - -2. **If the PR has a merge conflict**, fix it: `git fetch origin staging`, `git merge - origin/staging`, resolve the conflicts for real (don't just take one side blindly). If step 1 - also found unresolved review threads, don't leave those findings unaddressed while you're - already touching the branch — triage and fix them now too (step 4), replying/resolving each - (step 5). Before committing anything, run the same pre-push checks as step 6, in the same - order `/ship` runs them (the conditional cleanup/db-migrate gates first, since they can - rewrite code, then lint and boundary validation last so they check the final state) — checks - before commit, not after, since `lint` can auto-fix files and committing first would leave - those fixes unstaged after push. Then `git add` the resolved/fixed files and `git - commit` to complete the merge commit — a merge with conflicts stays uncommitted until you do - this — committing any review-thread fixes as their own separate commit same as step 7 would - (keep it separate from the merge commit). The merge is this round's sync check (it already - pulls in current `origin/staging`) — no need to also run step 6's stash/rebase/cherry-pick - machinery, which is for a different problem (local stray commits) — but do spot-check - `git log --oneline --reverse origin/staging..HEAD` still shows only commits you recognize - before pushing, same as step 6 would. Then a plain `git push` — a merge commit doesn't - rewrite already-published history, so this never needs `--force-with-lease` — and verify the - push landed the same way step 7 does. Skip step 3 and go straight to step 8 to trigger a - fresh review of the resolved code. + `hasNextPage` is `false` before evaluating "clean." A PR with more than 50 threads is rare but + stopping on a partial page would silently miss unresolved ones past the cutoff. + If `mergeable` is `CONFLICTING`, resolve it first (step 2). If it's `UNKNOWN` (GitHub still + computing it), just wait for the next round (step 9) and recheck. Otherwise, if `mergeable` + is `MERGEABLE`, Greptile is 5/5, and every thread across all pages has `isResolved: true`, + stop — report the outcome (see "Reporting" below) and skip the rest of this list. + +2. **If the PR has a merge conflict**, resolve it: merge `origin/staging`, fix the conflicts for + real (don't just take one side), run the repo's usual checks, commit, and push — a plain + `git push` is fine since merging doesn't rewrite history. If there are also pending review + findings, fix those in the same pass rather than pushing the conflict fix alone. Then go to + step 8 to trigger a fresh review. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / @@ -118,11 +93,10 @@ round. Always check both conditions freshly after every push. loop spanning a long session is exactly the scenario where a branch can drift, and pushing review fixes on top of undetected drift is how an oversized PR happens even after the branch was fixed once. Then run the repo's pre-ship checks the same way `/ship` does before - committing, in the same order — the conditional `/cleanup` (if this round's fix touched UI - code) and `/db-migrate` (if it touched schema/migrations) gates from `/ship` steps 4 and 5 - first, since they can rewrite code, then lint/typecheck/boundary-validation last so they - check the final state. A review-fix round is still a code change and can trip either gate - just as easily as the original commit did. + committing — not just lint/typecheck/boundary-validation, but also the conditional `/cleanup` + (if this round's fix touched UI code) and `/db-migrate` (if it touched schema/migrations) + gates from `/ship` steps 4 and 5. A review-fix round is still a code change and can trip + either gate just as easily as the original commit did. 7. **Commit and push** the round's fixes as one commit — `--force-with-lease` whenever step 6's sync check rewrote history, which includes a plain `git rebase origin/staging` that completed @@ -152,9 +126,9 @@ round. Always check both conditions freshly after every push. in a sleep loop. Pass the same `/loop babysit PR ` prompt on each wakeup so the loop resumes correctly. -10. **Stop conditions**: clean state reached (see above), or the same unresolved finding, merge - conflict, or `UNKNOWN` mergeable result survives two consecutive rounds with no new - information (surface it to the user instead of looping forever), or the user interrupts. +10. **Stop conditions**: clean state reached (see above), or the same unresolved finding or + merge conflict survives two consecutive rounds with no new information (surface it to the + user instead of looping forever), or the user interrupts. ## Reporting @@ -169,8 +143,5 @@ the final Greptile score / thread count. - Never fix a finding with a hacky workaround — if the clean fix isn't obvious, find the sibling pattern elsewhere in the codebase solving the same class of problem and match it. - Never silently drop a finding — every thread gets either a code fix or a reasoned reply. -- Always re-run the `/ship`-style sync check before every push in the loop, not just the - first — except step 2's merge-conflict push, where `git merge origin/staging` plus the - spot-check already serves as that round's sync check; don't also run step 6's - stash/rebase/cherry-pick flow on top of it. +- Always re-run the `/ship`-style sync check before every push in the loop, not just the first. - Never resolve a merge conflict by blindly taking one side — check the actual diff. From a6216a0de724c0c7856b463268df1712021192b9 Mon Sep 17 00:00:00 2001 From: Theodore Li Date: Wed, 15 Jul 2026 19:17:12 -0700 Subject: [PATCH 14/14] fix(babysit): simplify merge-conflict handling to a minimal step --- .agents/skills/babysit/SKILL.md | 28 ++++++++++------------------ .claude/commands/babysit.md | 28 ++++++++++------------------ .cursor/commands/babysit.md | 28 ++++++++++------------------ 3 files changed, 30 insertions(+), 54 deletions(-) diff --git a/.agents/skills/babysit/SKILL.md b/.agents/skills/babysit/SKILL.md index bf596f4b78e..8f324d190b3 100644 --- a/.agents/skills/babysit/SKILL.md +++ b/.agents/skills/babysit/SKILL.md @@ -7,11 +7,9 @@ description: Drive a PR to a clean review (Greptile 5/5, zero open threads) — Owns a PR end-to-end through review: ship it, wait for the automatic review round, and if it isn't already clean, drive fix → reply → resolve → re-review cycles until Greptile reports 5/5 -and there are zero open comment threads. Also keeps the branch mergeable against staging, since -a long babysit session can outlast staging moving underneath it — it's just a normal merge -conflict, resolve it like you would any other. Designed to be run under `/loop` (no fixed -interval — let it self-pace on review latency) so it survives across multiple wakeups in the -same session. +and there are zero open comment threads, keeping the branch mergeable against staging along the +way. Designed to be run under `/loop` (no fixed interval — let it self-pace on review latency) +so it survives across multiple wakeups in the same session. ## When to use @@ -53,16 +51,12 @@ round. Always check both conditions freshly after every push. stop yet: re-run the same query with `after: ""` and keep paging until `hasNextPage` is `false` before evaluating "clean." A PR with more than 50 threads is rare but stopping on a partial page would silently miss unresolved ones past the cutoff. - If `mergeable` is `CONFLICTING`, resolve it first (step 2). If it's `UNKNOWN` (GitHub still - computing it), just wait for the next round (step 9) and recheck. Otherwise, if `mergeable` - is `MERGEABLE`, Greptile is 5/5, and every thread across all pages has `isResolved: true`, - stop — report the outcome (see "Reporting" below) and skip the rest of this list. + If `mergeable` is `CONFLICTING`, fix that first (step 2). Otherwise, if Greptile is 5/5 and + every thread across all pages has `isResolved: true`, stop — report the outcome (see + "Reporting" below) and skip the rest of this list. -2. **If the PR has a merge conflict**, resolve it: merge `origin/staging`, fix the conflicts for - real (don't just take one side), run the repo's usual checks, commit, and push — a plain - `git push` is fine since merging doesn't rewrite history. If there are also pending review - findings, fix those in the same pass rather than pushing the conflict fix alone. Then go to - step 8 to trigger a fresh review. +2. **If the PR has a merge conflict**, merge `origin/staging`, resolve the conflicts, run the + usual pre-push checks, push, and go to step 8 to re-trigger review. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / @@ -137,9 +131,8 @@ round. Always check both conditions freshly after every push. ## Reporting -When the loop ends, summarize: how many rounds it took, what was actually fixed (one line each, -including any merge conflict resolved), what was pushed back on as a false positive and why, and -the final Greptile score / thread count. +When the loop ends, summarize: how many rounds it took, what was actually fixed (one line each), +what was pushed back on as a false positive and why, and the final Greptile score / thread count. ## Hard rules @@ -149,4 +142,3 @@ the final Greptile score / thread count. pattern elsewhere in the codebase solving the same class of problem and match it. - Never silently drop a finding — every thread gets either a code fix or a reasoned reply. - Always re-run the `/ship`-style sync check before every push in the loop, not just the first. -- Never resolve a merge conflict by blindly taking one side — check the actual diff. diff --git a/.claude/commands/babysit.md b/.claude/commands/babysit.md index 390a8162494..0c75a25a962 100644 --- a/.claude/commands/babysit.md +++ b/.claude/commands/babysit.md @@ -6,11 +6,9 @@ description: Drive a PR to a clean review (Greptile 5/5, zero open threads) — Owns a PR end-to-end through review: ship it, wait for the automatic review round, and if it isn't already clean, drive fix → reply → resolve → re-review cycles until Greptile reports 5/5 -and there are zero open comment threads. Also keeps the branch mergeable against staging, since -a long babysit session can outlast staging moving underneath it — it's just a normal merge -conflict, resolve it like you would any other. Designed to be run under `/loop` (no fixed -interval — let it self-pace on review latency) so it survives across multiple wakeups in the -same session. +and there are zero open comment threads, keeping the branch mergeable against staging along the +way. Designed to be run under `/loop` (no fixed interval — let it self-pace on review latency) +so it survives across multiple wakeups in the same session. ## When to use @@ -52,16 +50,12 @@ round. Always check both conditions freshly after every push. stop yet: re-run the same query with `after: ""` and keep paging until `hasNextPage` is `false` before evaluating "clean." A PR with more than 50 threads is rare but stopping on a partial page would silently miss unresolved ones past the cutoff. - If `mergeable` is `CONFLICTING`, resolve it first (step 2). If it's `UNKNOWN` (GitHub still - computing it), just wait for the next round (step 9) and recheck. Otherwise, if `mergeable` - is `MERGEABLE`, Greptile is 5/5, and every thread across all pages has `isResolved: true`, - stop — report the outcome (see "Reporting" below) and skip the rest of this list. + If `mergeable` is `CONFLICTING`, fix that first (step 2). Otherwise, if Greptile is 5/5 and + every thread across all pages has `isResolved: true`, stop — report the outcome (see + "Reporting" below) and skip the rest of this list. -2. **If the PR has a merge conflict**, resolve it: merge `origin/staging`, fix the conflicts for - real (don't just take one side), run the repo's usual checks, commit, and push — a plain - `git push` is fine since merging doesn't rewrite history. If there are also pending review - findings, fix those in the same pass rather than pushing the conflict fix alone. Then go to - step 8 to trigger a fresh review. +2. **If the PR has a merge conflict**, merge `origin/staging`, resolve the conflicts, run the + usual pre-push checks, push, and go to step 8 to re-trigger review. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / @@ -136,9 +130,8 @@ round. Always check both conditions freshly after every push. ## Reporting -When the loop ends, summarize: how many rounds it took, what was actually fixed (one line each, -including any merge conflict resolved), what was pushed back on as a false positive and why, and -the final Greptile score / thread count. +When the loop ends, summarize: how many rounds it took, what was actually fixed (one line each), +what was pushed back on as a false positive and why, and the final Greptile score / thread count. ## Hard rules @@ -148,4 +141,3 @@ the final Greptile score / thread count. pattern elsewhere in the codebase solving the same class of problem and match it. - Never silently drop a finding — every thread gets either a code fix or a reasoned reply. - Always re-run the `/ship`-style sync check before every push in the loop, not just the first. -- Never resolve a merge conflict by blindly taking one side — check the actual diff. diff --git a/.cursor/commands/babysit.md b/.cursor/commands/babysit.md index bc8f971a028..babe3a4acd7 100644 --- a/.cursor/commands/babysit.md +++ b/.cursor/commands/babysit.md @@ -2,11 +2,9 @@ Owns a PR end-to-end through review: ship it, wait for the automatic review round, and if it isn't already clean, drive fix → reply → resolve → re-review cycles until Greptile reports 5/5 -and there are zero open comment threads. Also keeps the branch mergeable against staging, since -a long babysit session can outlast staging moving underneath it — it's just a normal merge -conflict, resolve it like you would any other. Designed to be run under `/loop` (no fixed -interval — let it self-pace on review latency) so it survives across multiple wakeups in the -same session. +and there are zero open comment threads, keeping the branch mergeable against staging along the +way. Designed to be run under `/loop` (no fixed interval — let it self-pace on review latency) +so it survives across multiple wakeups in the same session. ## When to use @@ -48,16 +46,12 @@ round. Always check both conditions freshly after every push. stop yet: re-run the same query with `after: ""` and keep paging until `hasNextPage` is `false` before evaluating "clean." A PR with more than 50 threads is rare but stopping on a partial page would silently miss unresolved ones past the cutoff. - If `mergeable` is `CONFLICTING`, resolve it first (step 2). If it's `UNKNOWN` (GitHub still - computing it), just wait for the next round (step 9) and recheck. Otherwise, if `mergeable` - is `MERGEABLE`, Greptile is 5/5, and every thread across all pages has `isResolved: true`, - stop — report the outcome (see "Reporting" below) and skip the rest of this list. + If `mergeable` is `CONFLICTING`, fix that first (step 2). Otherwise, if Greptile is 5/5 and + every thread across all pages has `isResolved: true`, stop — report the outcome (see + "Reporting" below) and skip the rest of this list. -2. **If the PR has a merge conflict**, resolve it: merge `origin/staging`, fix the conflicts for - real (don't just take one side), run the repo's usual checks, commit, and push — a plain - `git push` is fine since merging doesn't rewrite history. If there are also pending review - findings, fix those in the same pass rather than pushing the conflict fix alone. Then go to - step 8 to trigger a fresh review. +2. **If the PR has a merge conflict**, merge `origin/staging`, resolve the conflicts, run the + usual pre-push checks, push, and go to step 8 to re-trigger review. 3. **If no review has run yet** (fresh PR, no Greptile/Cursor comments): they usually run automatically on PR open — confirm via `gh pr checks ` (look for `Cursor Bugbot` / @@ -132,9 +126,8 @@ round. Always check both conditions freshly after every push. ## Reporting -When the loop ends, summarize: how many rounds it took, what was actually fixed (one line each, -including any merge conflict resolved), what was pushed back on as a false positive and why, and -the final Greptile score / thread count. +When the loop ends, summarize: how many rounds it took, what was actually fixed (one line each), +what was pushed back on as a false positive and why, and the final Greptile score / thread count. ## Hard rules @@ -144,4 +137,3 @@ the final Greptile score / thread count. pattern elsewhere in the codebase solving the same class of problem and match it. - Never silently drop a finding — every thread gets either a code fix or a reasoned reply. - Always re-run the `/ship`-style sync check before every push in the loop, not just the first. -- Never resolve a merge conflict by blindly taking one side — check the actual diff.