Skip to content

Add resume/recover loop: never restart an issue from scratch - #36

Merged
lorenzoliuzzo merged 4 commits into
mainfrom
claude/great-brattain-d66a02
Jul 10, 2026
Merged

Add resume/recover loop: never restart an issue from scratch#36
lorenzoliuzzo merged 4 commits into
mainfrom
claude/great-brattain-d66a02

Conversation

@lorenzoliuzzo

Copy link
Copy Markdown
Contributor

Summary

  • Resume/recover loop: fleet_dispatch now persists enough state on every terminal outcome (branch pushed even on failure/partial, ledger entry carries outcome/attempt/commit-counts/blocker/final message) to resume an issue instead of restarting it from scratch. Routes via a pure _dispatch_decision (fresh/resume/skip); blocked issues file a cross-repo issue and are skipped until the blocker closes; after MAX_ATTEMPTS (3) an issue is marked needs_human.
  • Don't count transient failures toward the escalation cap: a new deferred outcome for session/rate-limit-style failures excludes them from the attempt count, so a string of rate limits resumes indefinitely instead of prematurely escalating to a human. Also fixes the resume prompt to not claim a branch exists when a transient failure stalled before the first commit.
  • Refuse to run when two accounts are the same Claude account: _preflight_distinct_accounts reads each config dir's accountUuid and refuses to run (dry-run or --execute) if two accounts collide or an identity can't be read - this was hit live (two config dirs both authenticated as the same account, silently halving effective concurrency).

These three commits were built in a prior session on this branch; branch already exists on origin, this just opens the PR that was never created for it.

Test plan

  • Per-commit test counts noted in commit messages (47 -> 56 -> 60 tests, ruff clean at each step)
  • Re-run pytest + ruff check fresh before merge to confirm current state against latest main

Co-Authored-By: Claude Sonnet 5 noreply@anthropic.com

lorenzoliuzzo and others added 4 commits July 7, 2026 17:03
Turns fleet_dispatch from one-shot ("try an issue once, PR or bust") into a
loop that moves forward across attempts, per the design agreed with the user:
agent-driven, triggered by stalled dispatch outcomes, all three parts.

A. Durable attempt state. Every terminal outcome now persists enough to resume:
   the branch is pushed even on failure/partial (not just success), and the
   dispatch ledger entry carries outcome, attempt number, commit counts, pushed
   flag, blocker ref, and the worker's final message. Outcomes are now the full
   set: success / needs_review / no_changes / failed / blocked / needs_human.

B. Resume from the prior attempt. main() reads each issue's last attempt
   (_last_attempt) and routes via a pure _dispatch_decision: fresh / resume /
   skip. A resumed dispatch checks out the prior pushed branch (base
   origin/<branch>) instead of main, and the prompt hands the worker the prior
   outcome + final message and tells it to `git log/diff` what's there and
   continue, not restart.

C. Blocker -> cross-repo issue. When an issue is blocked by a missing capability
   in another repo, the worker files an issue there (gh issue create, now
   allowlisted) and prints a `FLEET-DISPATCH-BLOCKED: <org>/<repo>#<n>` sentinel;
   the tool records outcome=blocked and skips the issue until _issue_is_open
   says the blocker closed. After MAX_ATTEMPTS (3) unresolved tries an issue is
   marked needs_human and left alone.

Commit-count uses merge-base so it's robust to main advancing under a concurrent
dispatch. +13 tests (blocker parse, decision table, attempt lookup, resume
prompt, main routing). 47 tests, ruff clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The live #17 run exposed this: both accounts hit their session limit
("resets 6pm"), so two attempts recorded 'failed' -- and under the plain
MAX_ATTEMPTS cap a third transient hiccup would have dumped a perfectly-fine
issue to needs_human. A rate limit is not the issue's fault.

- New 'deferred' outcome for a transient/infrastructure failure (session/rate
  limit, overloaded, service unavailable, matched narrowly in the worker's
  final message). It's resumable but excluded from the attempt count, so a
  string of rate limits resumes indefinitely without ever escalating.
- _last_attempt's cap count self-heals: it also ignores any 'failed' entry
  whose message reads transient -- including the two #17 failures already in
  the live ledger, recorded before this classification existed.
- Resume-prompt wording fix (also from #17): only claim "you're on the branch
  it left behind" when a durable branch actually exists. A failed run that
  stalled before its first commit (a session limit) leaves nothing, so the
  prompt now says "starting from main" instead of promising an absent branch.
  _prompt_for takes has_branch, set from the real remote-branch check.

56 tests, ruff clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The multi-account premise is silently void if two config dirs are logged into
the same account -- they share one session window and quota, so "concurrency"
buys nothing and one account drains twice as fast. This actually happened:
~/.claude-mythingslab and ~/.claude-lorenzoliuzzo were both authenticated as
lorenzoliuzzo@outlook.com (same accountUuid), so every "account2" dispatch ran
under lorenzoliuzzo and both hit its session limit -- invisible, because they're
separate directories with separate tokens.

_preflight_distinct_accounts reads each dir's accountUuid from .claude.json
(oauthAccount, no token touched) and main() refuses to run -- dry-run or
--execute -- if any two collide, or if an identity can't be read at all. Cheap,
local, and it makes the footgun impossible to hit silently.

60 tests, ruff clean.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
# Conflicts:
#	fleet_dispatch.py
#	test_fleet_dispatch.py
@lorenzoliuzzo
lorenzoliuzzo merged commit 70e6cde into main Jul 10, 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.

1 participant