Skip to content

Pickup runs: thirdshift takes ready-for-agent issues on its own - #292

Merged
JacobStephens2 merged 35 commits into
mainfrom
issue-212
Oct 1, 2026
Merged

JacobStephens2 merged 35 commits into
mainfrom
issue-212

Conversation

@JacobStephens2

@JacobStephens2 JacobStephens2 commented Oct 1, 2026 •

Copy link
Copy Markdown
Owner

Closes #212

Summary

thirdshift pickup starts a Pickup run: one pass, with no Issue URL, that takes the lowest-numbered Ready issue and dispatches it as thirdshift <Issue URL> would. The operating system's scheduler starts each pass (ADR 0009).

thirdshift pickup [base <branch>] [merge|no-merge] [base-fix|no-base-fix] [parallel <n>] [email [<address>]|no-email]
  arguments, User config
  the Run notification's checks          # no address or key: exit 1, every pass
  the preflight checks of an Architect run
  the lock shared with an Architect run  # held: skipped
  the Sweep                              # in-progress off closed issues
  the Claim limit (pickup.limit, 3)      # at or over: skipped
  search open issues labelled ready-for-agent, lowest number first
    each passed over: one stderr line, the first reason that applies
    none is a Ready issue: skipped
  dispatch: a Spec run if it has sub-issues, a Run otherwise
    makes the Claim: in-progress in place of ready-for-agent
  ends as the dispatched run does, with one Run notification if asked

A skipped pass exits 0, with stdout empty, one line of reason on stderr and no Run notification.

The Claim is made by every Run and Spec run but a Ticket's Run and a Base fix, however it was started:

Run or Spec run on an issue
  preflight checks pass
  make the Claim                 # one request; creates the label if missing
  worktree, sessions, ...
  ended merged, issue closed  -> in-progress removed
  ended ready for review      -> Claim kept
  failed, issue was started   -> Claim kept    # an Issue branch on origin, or a pull request from one
  failed, never started       -> Claim released, labels as they were

Where the work lives:

src/
├── pickup.rs        # the search, the Ready issue rule, the skips
├── claim.rs         # make, release, remove on close, the Sweep, the open count
├── launch.rs        # the start an Architect run and a Pickup run share: checks and lock
├── args.rs          # the pickup command; one dispatch for architect and pickup
├── config.rs        # pickup.limit
├── notification.rs  # one checked core for the three Run notifications
└── github.rs        # issues by label and state, parent, timeline, label requests
.agents/skills/      # Day shift: ready-for-agent on a Spec means its Tickets are published
README.md, CONTEXT.md, docs/agents/triage-labels.md, thirdshift help

What the Spec review changed

 release the Claim after a failed Run
-  unless this Run's own branch is on origin, or has a pull request
+  unless the issue was ever started: any Issue branch for it on origin,
+  or a pull request from one, open, merged or closed
  • A failed retry on issue-<n>-branch-2 used to release its Claim though issue-<n> had left a pull request. The Spec releases only with "no Issue branch on origin and no pull request for the issue", which is now the same question the Ready issue rule asks.
  • in-progress is taken off as the issue spells it in the release and after a Self-merge too, as the Sweep already did. The case rule for a label has one home, in github.rs.
  • thirdshift help names the Sweep and drops "cleaned up", which the glossary lists under Avoid.
  • The glossary's Ready issue now says it carries no Claim, as the code, the README and the help do.

Seam tested: the thirdshift binary run against the fake gh and the fake claude.

Evidence

  • Before: a_failed_retry_keeps_its_claim_when_an_earlier_issue_branch_left_a_pull_request failed: the issue's labels were ["ready-for-agent"], not ["in-progress"].
    After: it passes.
issue #7 labelled ready-for-agent, with a closed pull request from issue-7
thirdshift <Issue URL of #7>, and the session exits 1
  -> a Failed run, nothing of issue-7-branch-2 on origin
  -> #7 is labelled in-progress, and stderr has no "releasing the Claim"
  • cargo test: every test binary passes, 0 failed, among them the 68 tests of tests/pickup_run.rs. cargo fmt --check and cargo clippy --all-targets are clean.
  • Not tested, as the Spec says: the crontab recipe in the README. Try it on a real machine when reviewing.

Merge Danger

Door: two-way

Reverting the merge takes pickup away again. What it leaves on GitHub is the in-progress label on issues and in the repository, which is removed by hand.

Blast Radius: labels

  • Every Run and Spec run started by hand or by an Architect run now relabels its issue: in-progress in place of ready-for-agent. Anything else that reads ready-for-agent on a running or reviewed issue no longer finds it.
  • The Architect run's skip reason is reworded to an Architect run or a Pickup run is already running on <owner>/<repo>: a script that matches the old line breaks.
  • A Pickup run on a schedule starts agent sessions unattended. The Claim limit, the lock and the Ready issue rule bound it, and nothing runs until a crontab line is added.

Unaddressed findings

Spec

  • A failure before the Claim is retried on every pass, and blocks the queue. A dispatched run that fails before it makes the Claim leaves no Claim, branch or pull request, so the issue stays the lowest-numbered Ready issue: each pass takes it again, exits 1 and sends its notification, and the Claim limit never engages. Two lasting cases: a local Issue branch a Failed run kept after its push failed, and an open ready-for-agent Spec whose Tickets are all closed with no Spec branch. Skipped because the fix changes the Ready issue rule the Spec fixed ("no Issue branch for it on origin"), which is the Day shift's decision. Worth an issue of its own.
  • A ready-for-agent Ticket whose Spec is a Ready issue gets no passed-over line, where the Spec says each issue that isn't a Ready issue gets one. Skipped: it was a deliberate choice in the Ticket's review, since "its Spec is not ready" would be false, and the README says so.

Standards

  • The Sweep's warnings are formatted by hand, not through progress::warn. Skipped: warn prints two lines and names a command to run by hand, and the Sweep's one warning: ...: <error> line is what the README documents and the next pass retries.
  • createdAt is parsed in two places in github.rs, and tickets() inlines label names. Skipped: the two sites report different sources in their errors, and tickets() reads GraphQL nodes, not the list label_names reads.
  • tests/pickup_run.rs copies seven helpers and four constants from tests/architect_run.rs. Skipped: helpers per test file are the practice here (KEY, ACCEPTED and the_one_notification were in several files before this branch). Moving them to tests/support is a refactor of its own.
  • pickup::run and architect each match on launch::start with the same AlreadyRunning arm. Skipped: three lines, mapping to two different outcome types.
  • dispatched takes parallel beside dispatch.parallel, and email beside the args it came from. Skipped: a Pickup run overrides parallel for an issue that is not a Spec, so the two differ on purpose.
  • Launch has a field git, while launch elsewhere names a &Git. Skipped: a rename across modules this Spec did not write, with no behaviour in it.
  • READY_FOR_AGENT lives in spec_run and is imported by four modules. Skipped: it sits with the other triage labels there, the one home the Spec names for the Unready Ticket labels.

Tickets

🤖 Generated with Claude Code

JacobStephens2 and others added 30 commits October 1, 2026 17:34
…lished

/to-spec asks whether Tickets will follow and labels the Spec needs-triage
or ready-for-agent; /to-tickets swaps the Spec's needs-triage for
ready-for-agent as its last step. The triage label doc says what
ready-for-agent means on a Spec and that in-progress is thirdshift's label
for a Claim.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hing, Claim defined in CONTEXT.md only

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…f ready-for-agent

Once its preflight checks pass and before its worktree is created, a Run or
a Spec run that is not a child Run swaps the issue's ready-for-agent for
in-progress in one request, keeping its other labels and creating the label
if the repository lacks it. A Ticket's Run in a Spec run and a Base fix make
none, and an issue already Claimed is left as it is. A Claim that can't be
made stops the Run before any work.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… Claim on StartedBy, the selection test off the Claim's calls, more Claim cases

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Day shift skills: ready-for-agent on a Spec means its Tickets are published
The Claim: a Run or Spec run marks its issue in-progress
… and runs it

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ue closed

A Run or a Spec run that fails with nothing on origin to take over, no
Issue branch or Spec branch and no pull request, puts its issue's labels
back as they were before the Claim. A Merge run takes in-progress off once
the issue is closed. Either failing is a warning naming the command to run
by hand.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ow a run with no Issue URL starts as its own type, Unready labels whatever their case, the glossary's Base branch

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
thirdshift pickup: take the lowest-numbered Ready issue and run it
… labels path, clearer names in the Claim, more release cases, the Self-merge's glossary entry

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
src/claim.rs: the Claim a run makes and releases, from this branch, beside
is_on, which the Pickup run asks whether an issue carries a Claim. The two
branches each lifted the same label test out of label_in_progress; it stays
one function under this branch's name, has_label, and is_on is what asks
about in-progress.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Releasing a Claim, and removing it when thirdshift closes the issue
…un passed over

A ready-for-agent issue is also not a Ready issue when it has a parent
issue, is labelled base-fix, has an open blocker, or is not settled: ten
minutes since ready-for-agent was applied and since a sub-issue or a
"blocked by" link was last added or removed, read from its timeline.
Each issue passed over gets one line on stderr with the first reason
that applies, before the line that says what the pass did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…made before any other work

A Pickup run asked for a Run notification now checks for an address and a
Resend API key before its preflight checks, the lock and any call to GitHub,
so a broken setup stops every pass with exit 1, one that would have been
skipped included. A pass that took an issue sends the notification the Spec
run or Run it dispatched would have sent by hand, titled from the listing the
issue was found in, and a skipped pass sends none.

Closes #285

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…imit open Claims, and takes in-progress off closed issues

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… whatever its case; the settle time in the line from its constant; the reason order tested through to not settled

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…label, one error for a whole number from 1 up, the Claim limit over 3 and a Sweep that can't list

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…side one home for a label whatever its case

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… Spec check before an issue is said to be taken, the notification's checks ahead of the clone's

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Conflicts: both sides added github::remove_label and the fake gh's label
DELETE. The Claim keeps issue-284's has and spelling helpers, with the
release and removal from issue-212; the README keeps When the Claim ends
and points to the Sweep for an issue closed some other way.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The rest of the Ready issue rule, and saying why an issue was passed over
…un notification beside the whole Ready issue rule

The help states the whole Ready issue rule and the lines on issues passed
over, and leaves --email and --no-email out of the flags that apply to the
dispatched run: they ask for the Pickup run's own Run notification.

Whether the issue is a Spec comes from the candidate query, so the Tickets
query this branch had moved ahead of the taking line is gone: nothing can
fail between the search and the issue being said to be taken.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…m limit before a search that says why it passed an issue over

The Claim's is_on and its ready-for-agent test use github::has_label, the
one home for a label whatever its case; the Sweep keeps its own spelling
helper, since it needs the label as the closed issue spells it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
One Run notification for a Pickup run that took an issue
…m limit beside a Pickup run's one Run notification

The notification's checks stay first, then the pass is run with pickup.limit.
A pass skipped for the Claim limit sends no notification: the case #298 left
for this branch is tested here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Claim limit, and taking in-progress off closed issues
JacobStephens2 and others added 4 commits October 1, 2026 20:32
… to it in thirdshift help

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… which skips have passed-over lines, the Base branch for a retry by hand, the skills by name

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
README: running a Pickup run on a schedule
… in-progress taken off as the issue spells it everywhere, the Sweep by name in the help, no Claim in the glossary's Ready issue

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JacobStephens2
JacobStephens2 marked this pull request as ready for review October 1, 2026 21:08
…hrough the Run ending module

Conflicts:
- README.md: main's step 6 with the Check re-run, this branch's step 7 with the Claim removed after a Self-merge.
- src/main.rs: both sides' imports, less those main's Run ending module took over; the help keeps both the Check re-run and the Claim paragraphs; the Pickup run shows its ending with run_ending::show, as a Run and an Architect run do on main.
- src/progress.rs: this branch's warn beside main's stamped, ChildLine and relay.
- src/run.rs: both sides' imports.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@JacobStephens2
JacobStephens2 merged commit c918382 into main Oct 1, 2026
13 checks passed
@JacobStephens2
JacobStephens2 deleted the issue-212 branch October 1, 2026 21:24
@JacobStephens2 JacobStephens2 mentioned this pull request Oct 2, 2026
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.

Pickup runs: thirdshift takes ready-for-agent issues on its own

1 participant