Repository navigation
Parallel Tickets: spec.parallel and parallel <n> - #163
Merged
Merged
Conversation
…c.parallel Whenever a Ticket's Run ends, the Spec run reads the graph again and starts ready Tickets, lowest number first, until the limit is reached: the parallel <n> flag, else spec.parallel in the User config, else 3. Setup writes spec.parallel at its default. parallel on an issue with no sub-issues stops before any work. Tickets' Runs share the Launch directory, so worktree creation and cleanup there take a file lock, and a git command that fails on a lock file another git holds is tried again. The fake gh and claude lock their state files, as parallel child Runs call them at once. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…#144 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Each running Ticket's thread now runs main's interrupt-aware wait: it passes an interrupt on to its Run and reports the Ticket as interrupted, so an interrupt during a parallel Spec run ends every running Ticket through its Failed run path before the Spec run ends as interrupted. Main's terminal setup tests now expect the spec.parallel key. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30T00:43:09Z, host tracewake. Uncommitted work at the time of failure is included in this commit.
JacobStephens2
marked this pull request as draft
September 30, 2026 00:43
Parallel scheduling now keeps main's rule that a failed Ticket stops only the Tickets it blocks, and passes the Spec review's Logs through. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nd a clearer name for the Tickets read again Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
JacobStephens2
marked this pull request as ready for review
September 30, 2026 02:45
A blocker's Run closes its issue before it ends, so re-reading the Tickets after another Ticket ended could find the blocker closed and start the blocked Ticket before the blocker's Run had landed. This made independent_tickets_run_at_once_and_the_ticket_they_block_waits_for_both flaky on macOS CI. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve spec_run::run taking both main's Goal (Repair loop and Self-merge for the Spec PR) and this branch's parallel Ticket count. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two Tickets of a Spec run fetching the same Base branch from one Launch directory can race on refs/remotes/origin/<base>: one fetch fails with "cannot lock ref ... is at X but expected Y". Treat that like a held lock file and run the command again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
A Spec run now runs several ready Tickets at once. The limit comes from
parallel <n>, elsespec.parallelin the User config, else 3. main's rule still holds: a failed Ticket stops only the Tickets it blocks.The Tickets' Runs share one Launch directory. Two things keep git's lock files from failing them:
Command surface and config:
Harness: the fake
ghholds a file lock for each call and lets go while hook scripts run, since those callghthemselves. The fakeclaudelocks its record file.Help and the README's "Spec runs" section document
parallelandspec.parallel.Evidence
thirdshift parallel 2 <Spec URL>→unexpected argument.[spec]in the User config →unknown section [spec]. Tickets ran one after another.After: new tests in
tests/spec_run.rs, all passing:independent_tickets_run_at_once_and_the_ticket_they_block_waits_for_both: Numbered Issue branches after a merged or closed PR #21's and CI watching, CI-fix Repair and the Repair cap #22's sessions each wait for the other's to start, so the test fails unless both run at once. Headless sessions leave smoke-test files in /tmp #23 checks that both files are on its branch, and the Spec review runs last.parallel_1_runs_the_tickets_one_at_a_time,spec_parallel_in_the_user_config_sets_how_many_tickets_run_at_once: CI watching, CI-fix Repair and the Repair cap #22 needs Numbered Issue branches after a merged or closed PR #21 already landed.the_parallel_flag_wins_over_spec_parallelwithout_a_limit_at_most_three_tickets_run_at_once: the most sessions seen running at once is 3, across 5 Tickets.parallel_on_an_issue_with_no_sub_issues_stops_before_any_work_naming_the_flagtwo_tickets_starting_together_in_one_launch_directory_both_get_their_worktreesa_failed_ticket_stops_only_its_dependents_and_is_not_started_againpasses with parallel scheduling. It now compares the Tickets that ran as a set, since Headless sessions leave smoke-test files in /tmp #23 and A thirdshift-plugin-* temp dir can outlive its Run #24 run alongside Numbered Issue branches after a merged or closed PR #21.git::a_lock_another_git_holds_is_waited_for: a heldconfig.lockor ref lock is released after 300ms. Before the retry it failed withcould not lock config file .git/config: File exists.worktree::a_worktree_is_created_only_once_another_run_lets_go_of_the_launch_directory: fails with the lock removed.command_surface.rscovers the argument errors and the help.user_config.rscoversspec.parallel = 0 / -2 / 1.5 / "3"andspec.paralel.setup.rscoversspec.parallel = 3with a comment.cargo test --no-fail-fastpasses, with clippy and rustfmt clean. In one earlier full run,merge_run::ctrl_c_after_the_merge_finishes_the_post_merge_steps_and_exits_as_mergedhit its 10s "agent never started" deadline under load. That test is untouched by this branch. It passed 3/3 alone and in the next full run.Merge Danger
Door: two-way
Blast Radius: Spec runs
Single-issue Runs are unchanged except in two places.
Git::runnow retries on a held lock file, so a stale lock left by a crashed git makes a Run wait up to 10s before failing. Worktree creation and cleanup also take a file lock in.git/.Unaddressed findings
Standards
launch_directory()in theworktree.rstests nearly copiesrepo_with_origin()in thegit.rstests. They are private test helpers in two modules and differ in setup (clone vs remote config). A shared test-support module for two callers isn't worth it.parallelarm, theSPEC_BRANCHarm andask_once. Each is two lines with a context-specific message. Sharing it would add indirection for little gain.Parallel { tickets, asked }lives inspec_runbut is built inmain.rsand read inrun.rs. It keeps the limit and whether the command asked for it together.run.rsis where the "not a Spec" check has to happen, once the Tickets are read.land_ticketsschedules, reads the graph again and reports. The report is already its own function (report), and the loop is the scheduler. Splitting the loop out would only move its locals into a return type.ended/endingsfor the channel ends. They sit a few lines apart, and each one's type makes its role clear. (The vaguerreadwas renamed toreread.)DEFAULTSand the help.the_defaults_setup_writes_are_what_a_run_does_with_no_user_configalready ties the code default toDEFAULTS. The help is prose.held_lockmatches git's English stderr. git has no exit code for a held lock. ForcingLC_ALL=Con every git call would also change the language of hook output that the Run relays.Spec
worktree add -b/branch -D, which can fail partway, where a retry would be wrong. The lock also covers cleanup, the other half of creation.branch::selectandpull_base_branchrun outside the worktree lock and rely only on the retry. They are reads or idempotent fetches, which is the case the retry exists for.'<path>.lock': File existsandcould not lock config file). Nothing else has been seen in tests.claudealso takes a lock, which wasn't asked for. It was needed: parallel sessions write its record file at once.Closes #143
🤖 Generated with Claude Code