Skip to content

Runs share one Base fix - #242

Merged
JacobStephens2 merged 3 commits into
issue-221from
issue-224
Oct 1, 2026
Merged

JacobStephens2 merged 3 commits into
issue-221from
issue-224

Conversation

@JacobStephens2

Copy link
Copy Markdown
Owner

Summary

One broken Base branch gets one Base fix. A Run that meets Inherited failures now looks before it writes:

 BaseFix::fix (the Run's only red checks are Inherited failures, base-fix given)
+  lock <git common dir>/thirdshift-base-fix.lock      # Runs from one Launch directory look one at a time
+  open issue labelled base-fix, not the Run's own,
+  titled "CI red on <base>: …" naming every one of the Run's checks?
+    a Run from this Launch directory is running it  -> wait on it; fail if it ends with the issue still open
+    one from here started it, and it has ended      -> start it again on the same issue
+    no mark here (another clone or machine)         -> wait for the issue to close
+  none:
     write the Base fix issue
+  mark it as running (thirdshift-base-fix-<n>.lock, held while the child Run runs)
+  unlock
   start the child Merge run into <base>, wait for it
   merge <base> in, watch CI again

Waiting counts as the Run's one Base fix. If the issue closes and the checks are still Inherited failures, the cause is CI red on <check>, which also fails on <base> at <sha>, even after Base fix <issue URL> closed; fix <base> first.

In a Spec run, Ticket Runs are child thirdshift processes (ADR-0006), so "in-process" sharing is a lock in the Launch directory's git common directory: the first Ticket writes the issue and starts the Base fix, the others find it and wait.

src/
├── base_fix.rs   # the look, the wait, the running mark, title matching
├── git.rs        # Git::common_dir, Git::lock (worktree.rs now uses it too)
├── github.rs     # open_issues_labelled: gh issue list --label
tests/fakes/gh.rs # gh issue list --label; gh fake on-issue-view

Seam under test: the binary, with the fake gh and claude (tests/support::Scenario).

Evidence

  • Before: two_tickets_meeting_the_same_inherited_failure_share_one_base_fix never ended: each Ticket wrote a Base fix issue, and one waited forever on an issue nobody closed. a_run_that_finds_an_open_base_fix_issue_for_its_checks_waits_on_it_and_starts_none failed with Base fix …/issues/9 failed: no PR found (the Run wrote its own issue beside the open Keeping the PR mergeable: merge the Base branch and conflict Repair #8).
    After: cargo test: every target passes, 0 failed (base_fix 14, spec_run 67). cargo clippy --all-targets and cargo fmt --check are clean.

New tests, by acceptance criterion:

AC1  a_run_that_finds_an_open_base_fix_issue_for_its_checks_waits_on_it_and_starts_none
AC2  a_found_base_fix_issue_that_closes_with_the_checks_still_red_fails_the_run_naming_it
AC3  two_tickets_meeting_the_same_inherited_failure_share_one_base_fix
     a_shared_base_fix_that_fails_fails_the_ticket_that_started_it_and_the_one_waiting_on_it
also an_open_base_fix_issue_for_another_base_branch_or_other_checks_is_not_waited_on
     a_check_whose_name_has_a_comma_is_found_among_the_checks_an_open_base_fix_issue_names
     a_base_fix_that_failed_and_left_its_issue_open_is_started_again_by_the_next_run_to_find_it

Merge Danger

Door: two-way

Only Runs given base-fix reach the new code. Reverting restores one Base fix per Run. Leftover thirdshift-base-fix-<n>.lock files in a clone's git directory are inert.

Blast Radius: contained

  • A Run given base-fix can now wait with no time limit on an open base-fix issue written from another clone or machine that nobody is working on. That is the spec's "waits for that issue to close"; the README says so.
  • The shared Spec run tests coordinate two parallel Tickets through the fake gh call record, with a 10 second cap in the fake agent: on a very loaded machine they would fail, not hang.

Unaddressed findings

Standards

  • "Taken" is a new term not in CONTEXT.md. It is a private type name inside base_fix.rs, explained by its doc comment, not domain vocabulary a user meets.
  • started: bool on Taken could be a two-variant enum. It has one reader, awaited_end, and an enum would add more code than it explains.
  • looking names a lock guard. It reads as intended next to its comment ("held from the look for an open Base fix issue…").
  • open_issues_labelled returns Vec<(IssueUrl, String)>, and --limit 1000 is a bare literal. One caller destructures the pair at once; the literal matches the gh label list and gh pr list calls already in github.rs.
  • The issue title is the data store for matching. The spec defines the match that way ("one whose title names the same Base branch and covers its Inherited failures"). The bug it caused, names holding , , is fixed.
  • BOTH_TICKETS_HAVE_LOOKED greps the fake gh call record's layout. The record is the harness's documented artifact; a fake command just for this would be more surface than the two tests warrant.

Spec

  • The fake gh gained issue list --label, not --search. Deliberate: gh issue list --search goes through GitHub's search index, which can lag an issue just opened, so a second Ticket could miss the first's Base fix issue. The label list is consistent, and the title is matched in thirdshift.
  • "In-process" sharing is a file lock across child processes. Ticket Runs are child processes (ADR-0006), so nothing in one process could be shared; the lock also serves separate Runs from the same clone.
  • Not asked for: the running mark, the cause Base fix <url> ended with its issue still open, and starting a failed Base fix again. Without them a failed Base fix leaves its issue open and every Run that finds it waits forever, as the first version of this branch did in its own tests.
  • Not asked for: closed / not closed in the Run notification's Base fix: line. The Run that waits never sees a merge, only the issue, so merged would claim more than it knows.
  • Not asked for: skipping the Run's own issue, and taking the oldest match. The first stops a Run on a base-fix issue from waiting on itself; the second makes the choice deterministic.
  • A Base fix issue from another clone or machine that nobody works on is waited on with no limit. The spec says to wait for it to close, and from another clone there is no way to tell a slow Base fix from a dead one. Documented in the README.

Closes #224

🤖 Generated with Claude Code

JacobStephens2 and others added 3 commits October 1, 2026 02:35
…ch directory at a time

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… left open, share the Launch directory lock

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
README.md: keep issue-221's wording that base-fix, no-base-fix or the User
config's base.fix holds for each Ticket's Run, with issue-224's shared Base
fix and its "One Base fix per Base branch" section.

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.

1 participant