Skip to content

Skip Passes and end exhausted factory Commands as Paused Commands without agent-failed - #2207

Merged
JacobStephens2 merged 7 commits into
issue-583from
issue-2184
Nov 14, 2026
Merged

JacobStephens2 merged 7 commits into
issue-583from
issue-2184

Conversation

@JacobStephens2

@JacobStephens2 JacobStephens2 commented Nov 14, 2026 •

Copy link
Copy Markdown
Owner

Summary

When no route a Command may take has Plan usage left, thirdshift now pauses instead of failing:

  • Scheduled Passes skip without sleeping.
  • Live factory Commands end as Paused Commands.
  • The issue's Claim comes off without agent-failed.
 early_skip(repo, limit, harness, security)            # every Pass, before any GitHub work
   live Experiment? → skip
   GitHub Quota wait? → skip
+  every allowed route in cooldown / at its ceiling?   # plan_usage::pass_exhaustion
+    → skip "no allowed route has available Plan usage: claude in cooldown (five_hour) until …"
+      Activity log line only when the reason changes, per Pass kind / per named Custom process

 Sessions::run_step
+  every route held before the Step starts → PlanUsagePaused (no session started)
   session stops on a structured Plan-usage frame
     next route? → start the Step again there
-    none → the SessionFailure fails the Run
+    none → PlanUsagePaused   # also for a pinned or single Harness; never from agent text

 PlanUsagePaused
+  outcome "paused", exit 75; the record keeps `limit` and `resume_command`
+  Claim: release whatever is on origin, no agent-failed
+    took ready-for-agent off → put it back (record retaken_by_pickup)
+    untrusted edit since the last Trusted ready-for-agent → needs-triage
+    Security fix → agent-failed;  Triage run → needs-triage
+  Inbox: retaken_by_pickup → Done; otherwise Needs you as Paused
+  Run notification: always saved; not emailed when retaken under email.unless_merged
+  Spec run: a Ticket's child exits 75 → start no queued Ticket; running ones finish → Spec run pauses
+  Base fix child exits 75 → its Run pauses

A banner, Plan usage: every configured route is unavailable, so scheduled Passes skip: …; earliest reset …, shows on 1 Inbox and in thirdshift status while every default route on the machine is held.

Evidence

Execution evidence

These tests failed before their fixes during review, with the output shown, and pass now:

  • Before: cargo test --test plan_usage_pause a_review_run_whose_only_route_stops_at_its_limit_is_recorded_paused
    assertion left == right failed … left: String("failed") right: "paused"
    After: test a_review_run_whose_only_route_stops_at_its_limit_is_recorded_paused ... ok
  • Before: cargo test --test base_fix a_base_fix_that_stops_at_its_plan_usage_limit_pauses_the_run
    left: Some(1) right: Some(75)
    After: test result: ok. 17 passed; 0 failed (whole base_fix suite)
  • Before: cargo test --bin thirdshift plan_usage::tests::machine_exhaustion_reads_nothing_in_unit_tests (with HOME=/tmp/ts-home)
    test … FAILED (found was Some)
    After: test … ok

New and affected suites now pass:

  • cargo test --test plan_usage_pause: 14 passed; 0 failed
  • --test plan_usage_routing: 11 passed
  • --test pass_quota: 7 passed
  • --test out_of_disk: 4 passed
  • --test quota_wait: 10 passed
  • --test run_notification: 47 passed
  • --test grok_sessions: 6 passed
  • --test console a_pause_pickup_retakes_sits_in_done_beside_the_plan_usage_banner: ok
  • npm test --prefix console: # pass 205 # fail 0

The full suite was run with cargo test --no-fail-fast. Its only remaining failures are sandbox tests (bin sandbox::tests, and some tests in trial, scoring, experiments and bug_origins). They fail with sandbox failed to start: socat is not on PATH on this host, and they also fail on origin/issue-583.

Updated expectations: a Run stopped by a structured usage limit now ends paused (exit 75, subject : paused). This changed tests in run_notification.rs, grok_sessions.rs and pickup_run.rs (#9 paused: …). An Eval plan-limit pause still exits 1.

Visual evidence

1 Inbox uses the same fixture in Before and After:

Desktop 1440×900. Before, both pauses are in Needs you and there is no banner. After, the banner shows, #7 is in Done and #8 is in Needs you.

Before
After

Mobile 390×844, showing the counts. Before: Needs you 2, Done 0. After: Needs you 1, Done 1.

Before
After

Review

thirdshift-code-review was run against refs/remotes/origin/issue-583. Standards raised 12 findings and Spec raised 6. All were addressed in fcbe4489, bc1eb619 and 760f8ded, except the part listed below.

Review coverage: both reviewers read every file changed in the reviewed commit d5ed0102 except site/docs/index.html. That file is generated from docs/guide.md (npm run docs:check --prefix site passes), and the reviewers confirmed the exclusion.

Changed files the review left unread, because they were added or first changed after the review pass:

  • src/child_run/execution_tests.rs
  • tests/base_fix.rs (the reviewer's own test, added as written)
  • tests/grok_sessions.rs
  • tests/pickup_run.rs
  • tests/run_notification.rs

GLOSSARY.md and src/logs/activity.rs were read only as supporting files, before their fix-up edits.

Unaddressed findings

Standards

None.

Spec

  • Spec 4, partly: when several Harnesses are in cooldown, a Spec run paused by its Ticket may record a Harness other than the one that Ticket stopped on.
    • The full-window case the finding's test covers is fixed: a_spec_run_whose_ticket_pauses_at_a_full_window_records_the_stopping_harness passes.
    • What remains: plan_usage::latest_limit (src/plan_usage.rs) takes the most recent active cooldown on the machine.
    • Why: a Ticket's child Run reports its pause to the Spec run only through exit code 75 (run_ending::PAUSED_EXIT, read in child_run::Handle::ending). That is deliberately the one channel no agent can write (ADR 0031, "Only structured Harness wire-frame events … count … so an agent cannot forge a pause").
    • The child's own record still keeps its exact limit.

Security

None.

Security review outcome

No unaddressed introduced findings.

Merge Danger

Door: two-way

The new Command record field retaken_by_pickup is optional, and older readers ignore it. Reverting the change restores failing Runs, with no data migration.

Blast Radius: wide

  • Every Pass now reads ~/.thirdshift/plan-usage.json before any GitHub work. A stale cooldown there skips Passes until its reset or thirdshift plan-usage --clear.
  • A Run stopped by a structured usage limit now exits 75 with outcome paused instead of 1 with failed. That changes scheduler exit codes, notification subjects, and Failure limit counting, which is the point.
  • A paused Scoring run now emails when Run notifications are on.

Closes #2184

Built with claude 2.1.322 · claude-opus-5-5 · high

Scheduled Passes skip before any GitHub work when every route they may
take is held by machine-local Plan usage, logging each reason once and
showing a banner on the Inbox and in status. A live factory Command whose
routes are all held ends as a Paused Command (exit 75), recording the
stopping limit and Continuation, releasing its Claim without agent-failed
and putting ready-for-agent back for Pickup unless an untrusted edit or a
Security fix says otherwise. A Spec run starts no queued Ticket once one
pauses. Pauses Pickup retakes sit in Done and are not emailed under
email.unless_merged.

Closes #2184
…elpers

A Review run records its pause, and a Run whose Base fix pauses pauses
with it. Every Paused Command keeps a resume command, a Spec run paused by
a Ticket records what holds the routes, and a named Custom process's run
clears its last skip reason. Child Runs end as Ended::Paused, the pause
wording, Limit construction and allowed routes each live in one place,
the Run notification reads Pickup's retake from the Command record, and
the Plan usage banner reads nothing in unit tests.
Only a Command no route could take exits 75; an Experiment or Scoring run
paused at a plan-limit warning keeps exiting 1. A Spec run's notification
still names Tickets whose child Runs paused for Plan usage. Runs stopped at
a structured usage limit now end paused, as their tests now expect.
…rchitecture page

Clippy's result_large_err rejected Failure::Rejected once it carried a
Limit inline; the site test rejects a bare "Run" in hand-written prose.
… test

Ticket #9's 429 records muse's cooldown, so a sibling Ticket whose route
check came after it was not started and ended paused, making the Spec run
exit 75 instead of failing with #11's stream error. Hold #9's session
until #11's has started.
@JacobStephens2
JacobStephens2 merged commit 6401e07 into issue-583 Nov 14, 2026
24 checks passed
@JacobStephens2
JacobStephens2 deleted the issue-2184 branch November 14, 2026 08:55
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