Skip to content

CI watching, CI-fix Repair and the Repair cap - #22

Merged
JacobStephens2 merged 3 commits into
mainfrom
issue-9
Sep 25, 2026
Merged

JacobStephens2 merged 3 commits into
mainfrom
issue-9

Conversation

@JacobStephens2

@JacobStephens2 JacobStephens2 commented Sep 25, 2026 •

Copy link
Copy Markdown
Owner

Summary

After the PR check, the Run now goes through the spec's full Repair loop:

 implement session; push; check the PR
-merge origin/<base>; conflict → conflict Repair
-push; exit 0
+repairs = 0
+loop
+  merge origin/<base>
+    conflict → Repair (repairs++; 4th → "repairs exhausted: conflict"); push; continue
+  push
+  wait ≤ grace period for any check run or commit status on HEAD
+    none → no CI, counts as passing
+  watch until none pending
+    green → done
+    red → CI-fix Repair listing "- <name>: <details URL>" (4th → "repairs exhausted: CI red"); push; continue
+PR still open, ready, mergeable (UNKNOWN polled up to the grace period)? else Failed run
+exit 0
src/
├── ci.rs        # waits for and watches checks on the head commit
├── poll.rs      # until / within the grace period; env overrides THIRDSHIFT_CI_GRACE_MS, THIRDSHIFT_POLL_MS (tests only)
├── github.rs    # + checks_on (gh api --paginate: check-runs and status), mergeable
├── prompt.rs    # + ci_fix_repair, the spec's CI-fix Repair prompt
└── run.rs       # repair_loop, ensure_pr_ready_and_mergeable

Tested seams

  • The thirdshift CLI (the spec's only seam): tests run the binary against real git and the fake gh/claude. The fake gh now serves check runs, commit statuses and mergeability, and it has a test-only gh fake … subcommand the scripted agents use to set CI on their HEAD.

Evidence

  • Before: the Run exited 0 after the merge step no matter what CI said. tests/ci.rs did not exist (its first test, no_checks_within_the_grace_period_means_no_ci_and_success, failed on this code).
    After: cargo test: all suites pass, including 11 new scenarios in tests/ci.rs:
    no checks within the grace period        → exit 0
    checks pending then green                → exit 0
    red → CI-fix Repair (exact prompt) → green → exit 0, fix pushed by thirdshift
    red after 3 Repairs                      → "repairs exhausted: CI red", PR draft, failure commit
    conflict + CI-fix + CI-fix, then a 4th needed → "repairs exhausted: conflict"
    Repair, then base moves on               → base merged again; CI watched on the merge commit
    PR closed / unmergeable / draft at end   → Failed run
    mergeable UNKNOWN then MERGEABLE         → exit 0; stays UNKNOWN → Failed run
    
    Mutation check: with the cap set to 4, both cap tests fail.

Merge Danger

Door: two-way

A single revert undoes it. Nothing is persisted beyond what a Run already pushes.

Blast Radius: Runs

Every Run now waits at least 60 seconds after its last push when a repo has no CI, and polls GitHub every 10 seconds while checks run. A Run that used to exit 0 with red CI or an unmergeable PR now ends as a Failed run with its PR back in draft. Red CI or a conflict can now cost up to 3 extra agent sessions per Run.

Unaddressed findings

Standards

  • Timing env vars read inside poll.rs rather than injected: the tests drive only the binary, so env vars are the injection point the spec chose ("overridable through an environment variable for tests").
  • Data Clump (issue, base, branch, pr_url): this predates the change (the conflict Repair prompt already took it). Bundling it is a separate refactor across prompt.rs and run.rs.
  • enum Ci is a Mysterious Name: at the call sites (Ci::Absent | Ci::Passed, Ci::Failed(checks)) it reads as the state of CI, and a longer name adds nothing there.
  • ci::sleep's interrupt check repeats session.rs's wait loop: the session loop also polls the child process and stops it, so the shared part is two lines.

Spec

  • Watching can call CI green early if a fast status lands before other check runs register: the spec defines the wait as "any check or status". A settle delay would be a guess at timing that GitHub doesn't document.
  • A CI-fix Repair that correctly records a flaky or base-branch failure in "CI notes" leaves CI red, so the next pass burns a Repair and the Run ends in repairs exhausted: CI red: this follows the spec's pseudo-code as written. Changing it (e.g. accepting noted failures) needs a spec decision.
  • mergeable ignores branch protection (mergeStateStatus would be stricter): the spec asks for "mergeable". Required reviews would block every unreviewed PR anyway.
  • stale and action_required check runs count as failed: exit 0 requires passing CI, and neither is a pass. Treating them as red is the conservative reading.
  • Timing out on a mergeability that stays UNKNOWN is a failure reason the spec doesn't list: without a limit the final check could hang forever. Failing matches "exit 0 only when … mergeable".

Closes #9

🤖 Generated with Claude Code

JacobStephens2 and others added 3 commits September 25, 2026 12:20
After the PR checks, thirdshift now loops: merge the Base branch, push,
wait up to the grace period for checks or statuses on the head commit,
and watch them. Red CI starts a CI-fix Repair listing the failed checks,
then goes round again. Conflict and CI-fix Repairs share a cap of 3;
needing a 4th is a Failed run. Before exiting 0 the PR must still be
open, ready and mergeable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Waiting now lives in a poll module (until / within the grace period)
that both CI watching and the final mergeability check use. Check runs
and statuses are read across every page. Mergeability is an enum, the
open-PR check is shared, and an UNKNOWN mergeable state is tested.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JacobStephens2
JacobStephens2 merged commit 7af3208 into main Sep 25, 2026
1 check passed
JacobStephens2 added a commit that referenced this pull request Sep 30, 2026
Parallel Tickets (#143) and the Spec PR's Repair loop and Self-merge
landed on main. The Tickets checklist now follows the parallel loop: it
is written once per batch of Tickets started and whenever one ends, and
the Spec PR opens after the first Ticket lands. Tests that need #21 to
land before #22 starts run with parallel 1, and the Spec PR is found by
its head rather than by its index.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

CI watching, CI-fix Repair and the Repair cap

1 participant