State Machine
Next-step presentation
Section titled “Next-step presentation”The six pipeline names are Design, Plan, Implement, Review, Rework, and Ready. They describe the next work, derived immediately from existing records without migration or reprocessing:
| Completed stage | Next step |
|---|---|
shipper:new | Groom, in the separate New attention area |
shipper:groomed | Design |
shipper:designed, simple route | Implement |
shipper:designed, complex or undecided route | Plan |
shipper:planned | Implement |
shipper:pr-open, with or without a pull request | Review |
shipper:rework | Rework; implementation runs next |
shipper:ready | Ready for final integration |
Ready is not shipped and does not prove that a merge is currently eligible. A rejected simple implementation that escalates to complex appears in Plan. Auto-ship says Review in either PR mode; Rework names the workflow position while running activity still names implementation.
CLI/MCP retain six next-step groups. For the same issue population, the five desktop columns are Design · Plan · Implement · Review · Ready: desktop Implement equals CLI/MCP Implement plus Rework. New stays in a separate attention area. Closure-held and failed issues are excluded; closure hold takes precedence over failure. Blocked, locked, operator-paused, and running issues retain their route-aware column membership. Attention counts overlap those memberships. MCP blocked rows remain in their next-step groups with blocker and ownership details; only stageless or unknown-stage blocked rows use Blocked attention — no next-step group.
Current open responses add nextStep, and status adds nextStepPipeline. Existing completed-stage
pipeline JSON fields, workflow labels, status filters, commands, and reset arguments keep their
meanings. Closed records have no future next step, and historical records are not rewritten.
Maintained GitHub label descriptions retain completed-work meaning and add next-step guidance. New repositories receive them during setup; existing repositories automatically refresh differing descriptions on their next normal label setup or maintenance, without separate initialization. A successful catalog cache skips repeat maintenance; a changed catalog or force refresh reads again. Existing colors and unrelated labels are preserved. Read-only status, list, and view make no label writes.
Route-specific resets and drops
Section titled “Route-specific resets and drops”Reset choices, previews, successes, and refusals lead with the next step, then the literal
completed-stage target and any route clearing. An eligible Design drop targets groomed and
clears the route to undecided. An eligible Plan drop targets designed for complex or undecided
routes; a simple-route Plan drop is refused with the route unchanged because designed would next
Implement. An eligible Implement drop selects preserving rework when offered by the existing
reset options. Otherwise it targets designed for simple and planned for complex or undecided.
The existing main-process preview validates the package; missing, stale, closed, merged, unreachable,
or unverifiable evidence refuses confirmation without falling back to destructive cleanup.
To deliberately discard work, choose Start over — in the card Reset menu; the existing cleanup
confirmation applies. The Plan column is different from --to planned,
whose next step is Implement. A successful --to designed says Implement for simple and Plan for
complex or undecided. New (--to new) also clears the route.
These translations do not override backward-only eligibility, failed-stage retry, preserving Rework exceptions, active-work restrictions, confirmation, destructive work-loss warnings, or work-preservation guarantees. Review and Ready are not drag-reset destinations. A non-failed same-column drop does nothing, including Rework already in Implement; repeat Rework menu resets remain real preserving operations. Dragging cannot force a route change beyond Design’s existing route clearing.
Shipper’s V7 issue-state store records workflow stage, priority, failed state, and any external closure hold. GitHub issue labels can mirror that state for people and integrations when issue projection is enabled. Each initialized local record has one workflow stage at a time, plus control and priority fields that project to zero or more GitHub labels in mirror-on repositories.
Labels
Section titled “Labels”Workflow labels
Section titled “Workflow labels”| Label | Description |
|---|---|
shipper:new | New issue, awaiting grooming |
shipper:groomed | Product-groomed, awaiting design |
shipper:designed | Design-reviewed; next Implement (simple), Plan (complex or undecided) |
shipper:planned | Implementation planned, awaiting coding |
shipper:pr-open | Implementation packaged; next Review, in either PR mode |
shipper:rework | Packaged change preserved; implementation re-delivers it |
shipper:ready | Review accepted; next final integration |
The complete managed label set consists of the seven workflow labels above, shipper:blocked,
shipper:failed, shipper:priority-high, and shipper:priority-low. If that set drifts from the
local store mirror, run shipper issue reconcile to inspect its exact extra and missing labels and
shipper issue reconcile --repair to repair it. Check output identifies this as
managed label-set drift. For example:
For one compatibility release, raw shipper:implemented is treated as Rework and raw
shipper:pr-reviewed is treated as PR Open. Reads preserve either raw label; the next ordinary
transition replaces it with the current destination.
managed label-set drift (extra: shipper:new; missing: none; repair direction: store -> GitHub)
Repair output names the Managed label-set drift repaired issue count.
For older GitHub-only label problems, see
missing or duplicate workflow labels.
Manual GitHub edits to known managed labels are overwritten from the local store; ordinary labels
and unknown labels with a shipper: prefix are neither reported nor changed. The obsolete
shipper:locked label is also ignored and preserved. The obsolete label
has no lock meaning, does not itself cause repair refusal, and may be deleted manually. The desired
managed set converges in one operation, so an extra stage or opposite priority label is removed while a missing label is added. Blocked state uses that same
convergence path, while canonical blocked/unblocked workflow comments are repaired separately.
Issue locks remain machine-local filesystem coordination rather than GitHub-owned state.
Numeric snapshots in issues/<GitHub-number>.json or the historical pending-import directory
refuse the entire store unchanged. Use a Shipper release from before this change to migrate it
before retrying this version; no specific release is promised. Supported local-ID V3 upgrades and
first-reconciliation blocked-state imports for fresh or recreated stores, including interrupted
imports, remain available. See retired snapshot recovery.
Control labels
Section titled “Control labels”| Label | Description |
|---|---|
shipper:blocked | Issue has unmet dependencies. Prevents advancement (except new -> groomed). |
shipper:failed | Operator action is required. Blocks next and auto-selection until reset or eligible preserving recovery clears it. |
Priority labels
Section titled “Priority labels”| Label | Description |
|---|---|
shipper:priority-high | High-priority issue. Processed first by auto-ship. |
shipper:priority-low | Low-priority issue. Processed last by auto-ship. |
Normal priority is the default when neither label is present.
External closure holds
Section titled “External closure holds”When a mirrored GitHub issue closes while its saved stage is between shipper:new and
shipper:pr-open, or at shipper:rework, Shipper preserves the active V7 record and enters an
operator closure hold. A legacy reviewed label receives the same protection as PR Open.
The hold is local authoritative state, not a GitHub label. It retains the stage, failed, blocked,
priority, linked-PR, and blocker fields, so a failed issue can also be held without appearing twice
in attention lists.
Evidence records the GitHub close reason, actor when available, the closing pull request or commit when GitHub supplies one, whether a closing PR is the issue’s own linked PR, and whether attribution is stable or retryable. Closing by the issue’s own merged PR before Ready still enters the hold; it does not skip review. Missing or ambiguous attribution remains retryable and can be enriched by a later refresh without producing another first-entry notification.
While held, Shipper refuses groom, ship, next, merge, change recovery, reset scan/execute, auto-ship, and Desktop drag/drop before side effects. Issue details and comments remain readable, and priority, block, ordinary unblock, and force-unblock remain available. Choose exactly one resolution; there is no default:
- Not planned accepts withdrawal after showing a token-bound dependent recovery preview. It writes not-planned history and runs the recovery rules below without releasing blockers.
- Reopen and resume requires GitHub to confirm the mirror is open, restores the saved workflow, failed, blocked, and priority labels, then clears the hold. A remote failure leaves the local snapshot unchanged.
- Complete asserts that the work shipped. It requires a fresh preview-bound confirmation token, names every dependent that would be rechecked (or explicitly says there are none), then completes the issue and releases eligible dependents.
If someone externally reopens the issue first, refresh or reconcile restores projected labels and
clears the hold automatically. shipper:ready remains outside this hold path: an externally merged
stored PR follows Ready completion, while other deliberate closure handling remains governed by its
existing terminal rules.
Failure outcome contract
Section titled “Failure outcome contract”Every failure path makes two independent decisions:
- Issue disposition: whether the issue enters durable
shipper:failedor retains its current workflow stage. - Repository safety disposition: whether the occurrence counts toward the repository-wide auto-ship safety stop.
Durable shipper:failed is allowed only when an operator must act before the issue can progress
and a recovery that preserves already-completed work exists in every
repository posture where the path is reachable. If either
condition is false, the issue retains its current workflow stage, is skipped for the rest of the
current run, and remains eligible for selection in a later run without any operator command.
A leave-in-place outcome may carry an explicit null repository-safety decision and remain neutral.
A failure path may instead carry a repository-scoped designation when the repository, rather
than the individual issue, needs the circuit breaker. That designation deliberately and explicitly
records that the occurrence counts toward the safety stop even though the issue retains its workflow
stage. It is never inferred from the issue disposition or from diagnostic text.
Every failure path must explicitly decide both axes. A path may not reach durable failure, leave-in-place, or safety-stop behavior by falling through an unclassified default.
The published qualifying safety-stop identifiers and their observed outcomes are:
terminal-stage-failure— a terminal stage, merge, worker, or Desktop command-result infrastructure failure.merge-retry-exhausted— the automatic merge retry ceiling is reached while the issue retains its stage.pending-checks-exhausted— the configured pending-check window expires and its durable report is saved.groom-output-invalid— Groom accepts without the required valid manifest, so the issue remains recoverable at its current stage while the repository circuit breaker counts the occurrence.worktree-setup-contention— the third consecutive automatic setup contention leaves the issue at its current stage and records one stable repository-safety condition; earlier occurrences are silent and neutral.upstream-merge-wait— concurrent parked not-attemptable Ready pull requests share one repository-scoped occurrence for that upstream wait. The first park contributes the condition; later parks add members without a new strike. The occurrence closes only after every included pull request becomes attemptable, merges, or changes head. The issues remain Ready, and no agent stage runs.
Historical safety records may contain merge-remediation-exhausted. Readers preserve that token
and its original meaning, but no current producer emits it and it is not a current qualifying choice.
Every producer supplies either literal null or one of those repository-scoped classifications.
Omission is invalid at runtime sidecar and IPC protocols and is a compile-time error on typed stage
and ship paths. Missing information never supplies an issue disposition or repository-safety
decision.
The exempt outcomes are active merge retries, review/Rework routing, active pending-check
deferrals, intentional parked work, operator cancellation or pause, returned-for-grooming outcomes,
stage-entry refusals, selection-time Ready exclusions, and manual runs. Blocked entry refusal and a
blocked post-work stop both carry explicit null and are neutral. When the post-work stop also
reports successful advancement, that advancement increments the repository progress generation
before the neutral blocked disposition is processed. A later observation of an existing condition
can therefore accrue as a post-progress recurrence; the earlier strike remains durable. Other exempt
outcomes record neither progress nor a qualifying condition and leave the safety ledger unchanged. A
pending-check deferral qualifies only when its configured window expires. An exempt outcome may still
be a durable per-issue failure and produce a nonzero command exit; repository safety disposition is
independent of issue disposition and process status.
S#232 removes the unused exhausted per-issue stage-retry class as an explicit amendment to the taxonomy published by S#233; no runtime path produced that class. The rest of S#233’s taxonomy remains unchanged.
This contract is S#232’s explicit amendment to, and supersedes, S#53’s blanket rule that every no-verdict stage crash enters failed state after retries and S#149’s blanket rule that exhaustion of either merge ceiling is durable failure. The lane-specific behavior described elsewhere on this page applies this governing rule.
Retry boundaries
Section titled “Retry boundaries”The general stage-scaffold retry budget is three attempts and applies only to non-zero agent exits from non-interactive design, plan, implement, and PR review runs. An attended operator cancellation is terminal for that command and is never retried. Groom has no general stage retry budget. Setup and orchestration exceptions do not inherit the agent-exit budget. Specialized merge, content-validation, GitHub-transport, and integration loops are independent mechanisms with their own boundaries.
Each scaffold attempt starts from an attempt-local workflow-branch checkpoint. If either the
initial agent invocation or an output-correction invocation times out after changing the committed
HEAD, Shipper keeps that committed tip for the next attempt. Any other agent exit resets only the
commits made since that attempt began, so commits preserved from an earlier timeout survive a later
crash. Attended cancellation is the exception: it retains the complete committed HEAD for a later
invocation while forced worktree teardown discards staged, unstaged, and untracked changes.
This branch-state rule is uniform across design, plan, implement, and PR review, although it is a no-op when an attempt makes no commits. Design and plan normally reset a reused workflow branch to the resolved base before their first attempt. A branch retained by cancellation is reused without that reset when its stage and transition evidence still match, and the next agent receives continuation context. An unrelated reset or stage transition, or a moved branch, disables continuation. Only explicitly validated continuation-lookup failure/recovery groups are transparent to that rule. Implement and PR review otherwise leave the branch unchanged before their first attempt.
When the attempt budget is exhausted, any commits preserved from a timeout remain on the workflow branch. The durable failure reports the branch and its current full commit ID so the operator can inspect that state before choosing recovery or reset. Attempt counts and downstream result, review, verdict, pull-request-content, and continuous-integration gates are unchanged.
Stage execution outcomes
Section titled “Stage execution outcomes”Groom, design, plan, and implement expose one stageExecutionOutcome on every unsuccessful run.
A leave-in-place outcome is unsuccessful, excludes the issue for the rest of the current run,
retains its workflow stage, remains selectable in a later invocation, and is neutral for the
repository safety stop. A durable-failure outcome commits shipper:failed to the local store
before best-effort GitHub comment and label projection, names the operator action, and is terminal
for repository safety.
Preparation uses positive evidence rather than a broad “network” guess. DNS, connection, transport-shaped timeout, unreachable-network, TLS/proxy, HTTP 408/429/5xx, and equivalent registry or fetch evidence leave the issue in place. Operator cancellation during a blocking preparation hook also leaves the issue in place. A blocking hook’s own timeout is not transport evidence; like a proven missing base or workflow ref, externally checked-out branch, refused worktree reset, missing install executable, broken manifest/lockfile/configuration, or unknown nonzero preparation command, it is durable. This classification does not add retries.
A GitHub read that fails before the stage mutates state leaves the issue in place regardless of
which read site failed. A configured model or reasoning effort rejected by the selected agent does
the same: correcting the setting lets a later invocation select the stage without resetting the
issue. Once durable failed state exists, ordinary groom, next, or ship commands never clear
it; recovery must be explicit.
Implementation delivery also has an explicit pr-creation-unconfirmed leave-in-place outcome. After GitHub
reports that a pull request already exists, or after an ambiguous protected create result, Shipper
performs a guarded confirmation restricted to an open, same-repository pull request whose head is
the packaged branch and whose base is the attempted base. An eligible candidate follows the normal
implementation-to-PR-Open success path, including the ordinary association record, without a
recovered/adopted distinction.
If that confirmation cannot be verified or returns no eligible candidate, a pull request may
already exist but could not be confirmed. The issue stays at its current stage, does not enter
failed state, and remains eligible for an ordinary later run. The outcome carries literal null
repository safety: repeated occurrences neither record a qualifying condition nor advance the
repository progress generation and never escalate through a per-issue consecutive-unconfirmed
counter. The existing protected outage
allowance and repository safety stop remain the only backstops. Confirming-read exhaustion uses
exit 79; a completed no-candidate result or other unverifiable confirmation uses exit 1. Both
are unsuccessful and require no reset or recovery command. Once the outcome is classified as
unconfirmed, Shipper does not close, edit, or recreate a pull request. Branch publishing and the
attempted create may already have changed GitHub.
Validated accepted work survives later advisory-note cleanup failure. Deterministic orchestration faults instead become durable: a missing mirror under mirror-on policy, missing or unrecognized workflow label, successful stage whose label did not advance, or exhausted transition cap.
Attended operator cancellation
Section titled “Attended operator cancellation”Operator cancellation is a first-class leave-in-place outcome only when an interactive TTY agent
session actually started. Prompt resolution, setup, or spawn failures cannot claim cancellation.
After the attended process returns normally, closes its tab, or quits the application, Shipper
validates the complete result artifacts exactly once. Complete valid artifacts remain authoritative
and finish the stage normally even when the process exit was nonzero. Missing, partial, unreadable,
or invalid artifacts produce one typed operator-cancelled result and exit 82.
Cancellation retains the workflow stage from session entry, records literal null repository
safety, creates one neutral operator_cancellation workflow-history event, and creates no failed
state, correction pass, agent retry, recovery bundle, or recovery instruction. Saved headless groom
question state is not consumed and remains resumable. This is neither a successful completion nor
the resumable exit-75 pause; it is also distinct from a supervisor-requested process stop.
For design, plan, implement, and PR review, committed branch work survives cancellation and
is offered to a later matching invocation with continuation context. Uncommitted work is discarded
when the worktree is torn down. shipper groom --auto, shipper next, and sequential shipper ship
start no later issue or stage after cancellation. Parallel automatic shipping stops refilling work
and drains only attempts that were already running.
S#352 explicitly amends S#251. Bounded correct-and-revalidate handling and the counted
groom-output-invalid durable failure remain the contract for non-interactive runs; neither applies
to an attended operator cancellation.
Clean GitHub-outage exhaustion
Section titled “Clean GitHub-outage exhaustion”When Shipper exhausts its clean GitHub-outage waiting allowance before committing a stage outcome,
the command exits 79. No failed state is committed: the issue retains its workflow stage, is
skipped for the rest of the current run, and can be selected normally by a later run. This
leave-in-place outcome is neutral for the repository safety stop and does not advance its
consecutive-failure count. Exit 79 is still an unsuccessful command result; it distinguishes this
retry-later outcome from both success and durable failure.
Work-preserving recovery by repository posture
Section titled “Work-preserving recovery by repository posture”Preserved groom output is posture-independent: every row below can use shipper recover S#123,
MCP shipper_recover, or Desktop Recover change. Shipper validates the stored tree first and
runs at most one unattended correction without restarting discovery or re-asking answered
questions.
| Repository posture | Recovery from durable shipper:failed |
|---|---|
| GitHub-backed | Use shipper recover S#123 or Desktop’s Recover change for an open stored pull request or a reachable pinned local change retained from an earlier posture. Recovery preserves completed local review rounds. For a stored PR confirmed closed without merging, use shipper issue reconcile --repair to preserve the implementation branch and return the issue to shipper:rework; shipper next then delivers the replacement PR. Use reset only when recovery is ineligible or its cleanup is acceptable. |
| Remote git without pull requests | Use shipper recover S#123 or Desktop’s Recover change for a reachable pinned local change or an open stored pull request retained from an earlier posture. A pinned revision remains eligible when its source branch has advanced, provided the revision is still reachable. Use reset only when recovery is ineligible or its destructive cleanup is acceptable. |
| Fully local | Use shipper recover S#123 or Desktop’s Recover change for a reachable pinned local change or an open stored pull request retained from an earlier posture. Recovery preserves the packaged record, prior review rounds, branches, worktrees, and workflow history while returning the issue to Change Packaged. Use reset only when recovery is ineligible or its destructive cleanup is acceptable. |
shipper:failed and an external closure hold are distinct durable operator-attention states.
Eligible groom/package recovery, reconcile recovery where applicable, or an acceptable reset clears
failed state. A closure hold is cleared only by its Not planned, Reopen and resume, or
Complete resolution (or the equivalent confirmed external reopen); reset never clears a hold.
Not-planned blocker recovery
Section titled “Not-planned blocker recovery”Every path that records a Shipper-managed blocker as not planned—direct close, held-closure resolution, and duplicate or not-planned grooming—evaluates open dependents whose stored blockers reference either the target’s Shipper local ID or its mapped raw GitHub issue number. Duplicate references identify one dependent and produce one disposition.
The impact preview and applied report use these stage rules:
| Dependent state | Recovery disposition |
|---|---|
shipper:new | No action. The issue stays at New and remains eligible for grooming. |
shipper:groomed through shipper:planned | Return to shipper:new. |
shipper:pr-open, shipper:rework, or shipper:ready | Remain at the current stage because a packaged change is in flight. |
| Failed at any workflow stage | Remain failed at the current stage; later clearing failed state does not revive this recovery. |
| Active issue lock | Defer durably and reclassify after that run releases its lock. |
| External closure hold | Defer durably until the hold is resolved by reopening; terminal hold resolutions discard the pending return. |
A return to New preserves the complete blocker set byte-for-byte and leaves shipper:blocked
authoritative. It also preserves failed and closure-hold fields; the movable classification requires
those fields to be clear before the transition. Clean recovery returns consume no failure, crash,
or retry budget. A raw legacy reviewed label follows the PR Open row without being rewritten by the read.
Deferred recovery is derived from unmatched not_planned_dependent_recovery workflow events, not
from snapshot flags. Each queued event names the not-planned target and the reason for waiting.
Issue-lock release and hold reopen automatically drain the relevant issue; repository auto-ship
selection also sweeps outstanding events. Draining re-reads current state, combines multiple queued
targets into one reset, and writes a resolved history record. If the dependent became failed or
entered a PR stage, it remains there with that durable reason. If it closed, the pending work is
discarded durably.
Recovery deliberately does not decide that the not-planned blocker is satisfied. The dependent remains blocked so grooming can decide whether to retain, replace, or release that prerequisite. Completed closure still follows the existing dependent-release path, and ordinary unblock and operator-only force-unblock behavior—including clearing all overridden blockers and its audit comment—are unchanged.
State transitions
Section titled “State transitions”stateDiagram-v2
direction LR
state "shipper:new" as New
state "shipper:groomed" as Groomed
state "shipper:designed" as Designed
state "shipper:planned" as Planned
state "shipper:pr-open" as PrOpen
state "shipper:rework" as Rework
state "shipper:ready" as Ready
state "operator closure hold" as Held
state "issue closed" as Closed
[*] --> New
New --> Groomed: groom
Groomed --> Designed: design accept
Designed --> Planned: plan accept
Planned --> PrOpen: implement accept
Rework --> PrOpen: implement accept
Designed --> PrOpen: implement accept (simple route)
PrOpen --> Ready: derived pr-review accept
Ready --> Closed: PR merged or observed merged
New --> Held: external GitHub closure
Groomed --> Held: external GitHub closure
Designed --> Held: external GitHub closure
Planned --> Held: external GitHub closure
PrOpen --> Held: external GitHub closure
Held --> Closed: not planned or confirmed complete
Closed --> [*]
Groomed --> New: design reject
Designed --> Groomed: plan reject
Planned --> Designed: implement reject
Rework --> Designed: implement reject
Designed --> Designed: implement reject (simple route, escalates to complex)
PrOpen --> Rework: derived pr-review reject (every route)
PrOpen --> Rework: reconcile closed-unmerged recovery
Ready --> Rework: reconcile closed-unmerged recovery
state "Control labels" as Controls {
state "shipper:blocked" as Blocked
state "shipper:failed" as Failed
[*] --> Blocked: dependency found
Blocked --> [*]: unblock accept
[*] --> Failed: classified durable failure
}
Failed --> New: reset --to new
Failed --> Groomed: reset --to groomed
Failed --> Designed: reset --to designed
Failed --> Planned: reset --to planned
Failed --> Rework: reset --to rework (with packaged evidence)
note right of Blocked
Blocks advancement except new -> groomed.
end note
note right of Failed
Requires operator action and a posture-valid, work-preserving recovery.
reset can target new, groomed, designed, planned, or rework;
if failed state still carries a workflow stage, that same stage is a recovery target.
end note
note right of Held
reopen exits the hold and restores the saved stage.
end note
Issue routes
Section titled “Issue routes”Every issue carries a durable route: undecided, simple, or complex. The route decides
which stages the issue passes through after design.
Design assigns the route. Every accepted design result must declare a route and a nonblank one-line reason. A design result that omits either is invalid stage output — the design stage’s bounded correction pass retries with feedback naming what is missing, and exhausting it fails the issue with the ordinary operator guidance. There is no setting that disables routing and no silent default. Route data on a rejected or failed design result is ignored; only an accepted design changes the route.
Design defaults to simple and routes complex only when an exclusion clearly applies. A clear
exclusion needs specific evidence tied to its rule: named artifacts, an identified seam or new test
infrastructure, or a concrete open question or alternative. General uncertainty is not an exclusion.
Only a rejected combined implementation permanently escalates to complex;
findings after an accepted implementation follow normal review and implementation rework. The exclusions are:
- Production code changes span more than one package or component, or alter a cross-package contract. Documentation, tests, fixtures, and generated reference output do not count toward this: one production package plus its docs and tests is still one package.
- It changes durable saved workflow data, how stages exchange results, Shipper’s bundled stage prompts, user-visible configuration or stages, or the set of available agent tools. Runbooks, guides, and other documentation are not stage prompts.
- It adds a dependency or changes build or continuous-integration configuration.
- The expected change is large: many files, a new architectural seam, or coordinated edits whose ordering matters.
- Verifying it requires new test infrastructure. Post-merge, live-environment, or operator
acceptance steps do not exclude
simple: neither route performs them, and PR review applies equally to both. - Design cannot state the implementation approach in a few sentences, or would leave open questions or alternatives to implementation.
Routes never revert to simple. An undecided route may become either simple or complex,
and simple may escalate to complex, but complex never returns to simple. A design result
declaring simple for an issue already stored as complex is not accepted, and the correction
feedback names the stored route.
What each route means for the stage sequence. A complex-route issue runs the full chain:
shipper:designed to shipper:planned, then one implementation-and-delivery step to the
review-entry state.
A simple-route issue never records shipper:planned. It runs one combined
implementation-and-delivery step from shipper:designed; on acceptance it moves straight to the
review-entry state, and on rejection it stays at shipper:designed while its route escalates
permanently to complex, so the next dispatch runs planning.
Where the decision is recorded. The accepted design review states the route and its reason
under an ## Issue Route heading, in the same place on every issue. The authoritative audit trail
is the ## Issue Route Changed entry in the issue’s workflow history, written whenever an accepted
design changes the stored route.
How transitions happen
Section titled “How transitions happen”A ready issue also reaches the same completed tombstone when Shipper observes that its stored linked
pull request was already merged outside Shipper. This recovery is ready-only: an externally merged
pull request linked to shipper:pr-open does not skip the remaining review
stages. Shipper performs no merge or remote-branch deletion on the recovery path, but it does run the
same guarded local branch cleanup and immediately re-check blocked dependents. Resetting first moves
the issue out of ready and clears a closed or merged PR association, so later external-merge recovery
is intentionally no longer eligible.
There is a separate reconcile recovery edge for a linked pull request positively confirmed closed
without merging. At shipper:pr-open, shipper:rework, or shipper:ready,
shipper issue reconcile --repair may move the issue backwards to shipper:rework, clear the
dead association, and clear failed state. Recovery requires an open or absent issue mirror, the
exact surviving PR head ref, no active issue lock, and lock-time stage/link revalidation. It
preserves the implementation branch; the next shipper next runs implementation from that branch.
This differs from ready externally-merged completion: a merged ready PR completes the issue, while a closed-unmerged PR returns surviving work to implementation. It also differs from manual reset cleanup. Reconcile recovery does not scan or delete artifacts, synthesize or delete a branch, adopt a replacement PR, close or mutate a PR, or rerun implementation.
For result-protocol stages after grooming, workflow state is transitioned by Shipper from the
agent’s result file. The agent writes result.json; Shipper resolves the verdict to a target
stage, commits that stage to the local issue-state store first, then mirrors the desired workflow
labels to GitHub when issue projection is enabled. If the GitHub mirror fails, the local transition
remains committed and shipper issue reconcile --repair is the recovery path. For example, after a
successful design the store stage changes from shipper:groomed to shipper:designed; a mirror-on
repository then mirrors GitHub to the same label set:
gh issue edit <ISSUE> --add-label "shipper:designed" --remove-label "shipper:groomed"A returned fail verdict uses that result-file transition path. No-verdict crashes and exceptions
are classified under the failure outcome contract, not by one
universal retry rule. Design, plan, implement, and PR review receive the scaffold’s three
attempts for non-zero agent exits. Groom has no general stage retry budget, and setup or
orchestration exceptions do not consume or inherit that agent-exit budget. Specialized merge,
content-validation, GitHub-transport, and integration loops remain separate.
When stage execution records durable failure, Shipper commits the authoritative local failed state before attempting the failure comment and failed-label projection in mirror-on mode. Either GitHub projection can be repaired later without undoing that local state. A merge failure that successfully routes the issue to Rework or PR Open is a normal workflow outcome and does not enter failed state.
Agent timeouts follow the same rule for direct and MCP-started issue stages. The configured limit is
per agent attempt, not per stage call; scaffold retries, grooming correction, question waits,
projection, run terminalization, cleanup, and ownership-safe lock release remain outside that
attempt timer. A terminal timeout records agent_timeout history with the stage, limit, exhausted
attempts, available session log, exact reset target, and either no preserved commit or the workflow
branch plus full preserved revision. It always records that staged, unstaged, and untracked work was
discarded. Mirror projection debt is repaired with shipper issue reconcile --repair without
rolling back failed state or retaining the run lock.
Ready still has deterministic prerequisites. With pull requests disabled it requires a packaged local change; with pull requests enabled it requires an open linked PR or an externally merged PR that can be completed. Mirror-on also requires a GitHub issue mirror. A missing package, mirror, association, or PR record records one durable failure with exact reset, PR-open, adopt, or reconcile guidance and is not selected by a later queue/run. A temporary linked-PR lookup failure is an untimed run skip instead.
Merge handling distinguishes whether Shipper obtained a durable determination about the pull request:
| Action or deferral | Causes | What happens next |
|---|---|---|
retry | Busy merge queue, local repository contention, temporary GitHub failure, or rate limit | The issue remains Ready. Auto-ship retries after per-issue delays of 30, 60, 120, then 240 seconds. Attempt five skips it for only the current invocation/enabled run; no failed state is persisted. |
rework | Merge conflict | Handling returns the issue to Rework with a current-package conflict record. The next dispatch runs implementation in the merge-rework situation. |
review | Failed checks, branch restrictions, branch still behind, failed branch update, or a determined unknown failure | Handling returns the issue to PR Open (Change Packaged without PRs). The next dispatch runs normal review pre-flight. |
fail-now | No usable repository merge method or a non-fast-forward/fetch-first local-base publication rejection | The issue enters durable failed state immediately with concrete repository-setting or reconciliation guidance. |
Selection-time excluded | CLI auto-ship in a pull-request-disabled repository finds a Ready issue without a packaged local change | The issue enters the same durable failed state as the attempted prerequisite path, but no attempt, worker, worktree, log, token accounting, or merge starts. The run exits 1; the outcome records neither a qualifying safety condition nor repository progress. |
| Deferred: checks pending | GitHub reports one or more CI checks still running | The issue remains at its current stage. Only this deferral owns a SHA-keyed deadline. Single-issue CLI runs and targeted or one-shot merges exit 78; --auto runs still exit 0. Desktop records Deferred. |
| Deferred: untimed run skip | Required approval, temporary Ready-prerequisite lookup failure, dirty local base, transient publication/handling failure, unconfirmed merge, or another current-run blocker | The issue retains Ready, is skipped for the current drain, and remains eligible in a later run. It consumes no retry count, creates no deadline, and is neutral for the repository safety stop. |
Typed cause or deferral fields select these actions; diagnostic reason text never does. Required
approval therefore retains Ready and consumes no retry attempt. A later capability snapshot
with APPROVED or CLEAN can merge. No usable merge method fails immediately and directs the
operator to enable a supported repository merge setting.
Pending checks are a typed deferral, not a retryable or remediable failure. Shipper visibly names
the PR and currently incomplete checks on each CLI deferral; Desktop keeps each command as a
Deferred issue run status in issue details and updates one informational notice. For one PR head SHA, a process or
Desktop application session measures total elapsed deferral time against
merge.pendingChecksTimeoutMinutes (default 60). A new commit changes the SHA and starts a full
new window; restarting the CLI process or Desktop application also resets the in-memory clock.
Passing checks permit the merge without intervention.
At the configured deadline, Shipper records that CI checks never completed, names every currently
incomplete check, and moves the issue into durable failed state. Recover an open linked PR with
shipper recover S#123 or Desktop’s Recover change. In store-only mode the same wait,
timeout, and recovery reporting remain local without GitHub writes. A required check that never
reports any status is not visible as pending and is outside this timeout bound.
Method selection and the single fallback after a rejected rebase update stay inside one merge attempt. A successful fallback emits no merge failure, so it does not advance the five-attempt retry count. A final command using the resolved method that fails and cannot be confirmed merged remains an unrecognized merge failure; Shipper does not choose another method from its diagnostic text.
A determined unrecognized failure routes to review; uncertainty after a merge request instead
produces the special unconfirmed-merge Ready deferral because the change may already have merged.
Failure classification comes from typed cause rather than diagnostic wording. Review and Rework
routes do not create merge-loop counters. Only retryable failures use the bounded retry sequence.
shipper new is Shipper-owned after draft validation: the agent writes draft files under
.shipper/output/, then Shipper writes a local issue-state record and creates a GitHub mirror only
when issue projection is enabled. Grooming updates authoritative title/body content in the local
store first, optionally projects it to GitHub with workflow comments, and commits the initial
shipper:groomed transition through the same local-authoritative workflow path. The result-file
transition contract owns the downstream workflow stages.
Feature-branch integration points use the resolved baseIntegration strategy before a stage result
is processed. The default rebase behavior replays feature commits and retains the existing
per-commit recovery budget. With merge, Shipper integrates base or remote feature updates with a
normal merge, preserves existing commit IDs, and creates no commit when the branch already contains
the ref. Each distinct merge receives one independent three-attempt conflict episode. Exhaustion
aborts that merge and restores the exact pre-integration branch and tracked worktree state.
When the implementation agent resolves and stages conflicts successfully, Shipper completes the selected
integration, reruns PR preparation against the final branch tip, and then processes the resulting
pr_spec as the normal implementation acceptance transition. In rebase mode, each stopped commit receives up
to three conflict-agent invocations. Git-observed advancement to the next replay step starts a fresh
three-attempt budget, with no overall cap across the finite replay. Shipper-owned
git rebase --continue calls and managed-path-only recovery do not consume an agent attempt.
After each agent-assisted advancement, Shipper reports the original commit and its n/m replay
position. If three agent invocations leave the same Git step unchanged, the current implementation
identifies that stuck commit and position before entering its existing shipper:failed recovery
flow. Unresolved or unstaged conflicts and non-zero agent exits still end the current integration
command unsuccessfully; their durable disposition remains subject to the governing contract.
See
PR preparation rebase recovery.
Publication follows the same strategy. Rebase mode retains its existing force-with-lease recovery
for computed non-fast-forward topology. Merge mode integrates an advanced remote branch and retries
only an ordinary fast-forward push; it never rewrites published feature history. Every local,
ordinary, and forced finalization path keeps the no-content publication guard, including for a
merge-only branch, but the guard is not itself a stage outcome. If a strict result refresh finds a
valid reject or fail, that verdict controls the outcome. A no-content accept, or absent,
unreadable, or invalid output, retains the publication refusal and durable failure path.
Pull-request completion strategy and local-delivery semantics are unchanged by this feature-branch
integration setting.
For no-PR local delivery, the base branch may already have advanced locally when publication runs.
A non-fast-forward or fetch-first rejection means the remote base diverged, so Shipper records a
durable fail-now outcome with reconciliation guidance. Other transport errors are untimed run
skips. A later attempt recognizes the already-advanced base and retries publication idempotently
instead of reintegrating the packaged change.
Mirror-mode flips do not rewrite workflow state. Stage labels, stageEnteredAt, completion
history, tombstones, PR associations, title/body content, and workflow events remain in the local
store and event log; reconcile only changes GitHub projections when mirror-on policy permits
issue-side effects. Open local-only records become explicit reconcile backfill candidates when
mirroring is enabled: eligible same-repository issue origins reconnect, origin-free or ineligible
PR/foreign origins receive new mirrors, and unsafe closed, missing, labeled, or claimed origins stay
Mirror pending (not currently repairable). This changes only mirror identity; workflow state and
history do not transition. Records completed during store-only remain tombstoned. On re-enable,
reconcile closes any old open mirror for a tombstoned local record instead of recreating a current
snapshot.
Rollback paths
Section titled “Rollback paths”A reject verdict rolls back to an earlier stage if the work is insufficient:
| Stage | Can roll back to |
|---|---|
| design | shipper:new |
| plan | shipper:groomed |
| implement (every route) | shipper:designed |
| pr-review (every route) | shipper:rework |
Implementation rejection returns to shipper:designed on every route, including Rework, and never
closes the pull request or deletes the branch.
Completed reviewer work reports an accepted agent result, while Shipper independently derives the
workflow gate. REQUEST_CHANGES, any canonical must-fix finding, or any known failed hosted check
rejects; pending, unavailable, passing, and deliberately absent local-only checks do not. Each
failed check is retained as a distinct locationless must-fix finding with available step and test
evidence. Acceptance retains the complete round and advances directly to shipper:ready.
Rejection retains and pins the round, records every cause, supersedes only older active rounds for
the current package, and returns every route to shipper:rework without changing route or package
identity. Work carrying legacy shipper:pr-reviewed is read as PR Open and enters normal review.
For implementation and review-rework implementation, these rollback targets also apply when publication
reports no content and the stage has a valid reject. The rejection record contains the agent’s
reason and explicitly says that nothing was published. It is an ordinary interior event: the issue
remains unfailed, the command exits successfully, and the occurrence does not increment the
repository-wide auto-ship safety stop.
A valid no-content fail instead remains a durable terminal-stage-failure: it marks the issue
failed, ends the command unsuccessfully, counts toward the safety stop, and uses the agent’s reason
rather than the publication condition. Missing or invalid output remains distinguishable because
its record uses the publication-condition diagnostic and agent-crash path. These transition,
diagnostic, and safety rules are identical with transport: "origin" and transport: "none".
Contentful rejects retain their stage-specific behavior: implementation publishes before
rollback, while review-rework implementation rolls back without publishing rejecting work.
Design, plan, and simple-route implementation can reject because of an external impediment. The stage must name a specific, separately identifiable external action that would make work proceed and that neither the stage nor implementer can perform in the sandbox. Orchestrator-owned merge/rebase transport and an absent deliverable from another issue qualify. Difficulty, size, unfamiliarity, missing context, perceived risk, or wanting more investigation do not.
The rejecting stage explains the impediment in ordinary prose in its workflow comment: the concrete blocking condition, why that condition is outside the stage’s control, and the external action or outcome needed to proceed. It may name a known related issue when that helps the operator act, but no reference is required. There is no prescribed heading, field list, claim identifier, exact wording, or validity vocabulary, and grooming does not adjudicate the report.
The report is recorded with the stage outcome and triggers only that stage’s ordinary rejection and
its normal one-step rollback — design to shipper:new, plan to shipper:groomed, and simple-route
implementation back to shipper:designed with its existing permanent escalation to the complex
route. By itself the report
never routes the issue to grooming, marks it durably blocked, creates another issue, or selects the
next action. The operator remains free to rerun the stage, return the issue to grooming, attach an
ordinary blocker, direct prerequisite filing, or respond another way, without first classifying the
report as valid or invalid.
Only on explicit operator direction does grooming file a prerequisite and attach it as an ordinary
blocker of the origin in that same directed action. It either blocks on an existing Shipper issue or
creates a plain normal-priority shipper:new request and blocks on that issue’s local Shipper
identity. The new-stage issue is ungroomed and therefore excluded from auto-ship until a human
grooms it. The blocked parent is also excluded while the prerequisite remains unmet, so it cannot
re-enter design or plan.
Ordinary rejection, rollback, durable blocker, and blocker-reconciliation behavior are unchanged, and stages outside this set keep their established rejection and failure behavior. Historical out-of-authority prerequisite claims and grooming determinations remain readable as advisory context: their wording, validity labels, dependency fields, and claim identities inform judgment but never bind or independently determine a later outcome.
Groom-created issue forms
Section titled “Groom-created issue forms”Grooming can create three distinct issue forms. Their similar creation point does not give them the same workflow meaning:
| Form | Initial stage and priority | Relationship to the origin |
|---|---|---|
| Follow-up | Independent normal-priority shipper:new issue with no explicit priority label | No blocker, blocked state, dependency, scheduling, ordering, or decomposition link |
| Prerequisite | Normal-priority shipper:new issue | Blocks the origin through its ordinary local Shipper identity |
| Decomposition child | shipper:groomed with its declared priority | Participates in the partial or full split and carries child grooming context |
Filing a follow-up never changes the origin’s blockers or blocked state. It is available when the origin remains open, whether or not that run also creates children, and when full replacement closes the origin after creating its children. The attribution naming the origin provides traceability only; it does not create a parent/child or scheduling relationship.
Like every shipper:new issue, a follow-up remains outside auto-ship while ungroomed. Its own
grooming independently decides scope and priority and advances it to shipper:groomed; from then on,
ordinary stage, priority, failed, blocked, and lock selection rules apply without reference to the
origin.
Reject is an interior event
Section titled “Reject is an interior event”shipper ship S#123, shipper ship --auto, and shipper next treat a stage reject verdict as
an orderly rollback, not as a terminal command failure. After Shipper applies the rollback
transition to the local store and projects it to GitHub only when enabled, the ship loop re-reads
the local workflow stage and continues from the corresponding stage.
The existing safety rails still apply:
- Every forward or backward label change counts toward the per-run transition cap
(
MAX_TRANSITIONS = 15). The current implementation recordsshipper:failedwhen the cap is hit; its lane classification remains subject to the governing failure contract. - If a reject does not produce an observable local workflow-state move, the run still aborts to avoid an infinite loop.
- Crashes (no verdict) and explicit
failverdicts still end the current command unsuccessfully; their issue and repository safety dispositions come from the failure contract. - A no-content reject from implementation or review-rework implementation remains safety-stop neutral;
the same publication condition with a valid
failkeeps its normalterminal-stage-failureclassification.
For recovery from failed issues, reject loops, or transition-cap exhaustion, see failed issues and rollback loops.
Rolling back to shipper:new is the one special case:
- Single-issue, worker, parked, and auto runs stop before running interactive
groom, leave the issue atshipper:new, apply no failed state, and exit zero. - Auto reports
↩ returned — grooming requireddistinctly from pass and fail, and adds the issue to the in-run skip set. The final return happens before transition-cap accounting; unrelated workflow cycles remain protected byMAX_TRANSITIONS = 15.
The next command
Section titled “The next command”Auto-advances an issue by reading its current workflow stage from the local issue-state store and
dispatching to the corresponding stage through the shared in-process dispatcher. next rejects
issues whose local snapshot is marked failed, and refuses blocked issues except shipper:new, which
can always be groomed. If the dispatched stage rejects, next logs the rolled-back local stage and
exits zero. Crashes and explicit fail verdicts still exit non-zero.
The ship --auto command
Section titled “The ship --auto command”Processes issues in priority order. Auto-ship chooses the highest-priority available issue first, then prefers the most-complete stage within the same priority tier:
shipper:ready > shipper:rework > shipper:pr-open > shipper:planned> shipper:designed > shipper:groomedshipper:new is excluded from auto-ship because grooming is interactive by default.
Issues are ordered by priority first: shipper:priority-high, then normal priority, then
shipper:priority-low. Within the same priority tier, auto-ship prefers the most-complete stage
using the ordering above. Within the same stage and priority tier, issues are processed FIFO by
the local issue-state replica’s stageEnteredAt timestamp. Issues with recorded timestamps sort
before issues whose timestamp could not be resolved, and issue number is the stable tiebreak.
After a qualifying merge, Auto-ship immediately re-checks blocked, non-New direct dependents of the
completed issue. CLI Auto-ship uses each successful merged result as the trigger. Daemon-hosted
Auto-ship, including manually and automatically started ship and merge sessions, triggers only
when Shipper actually performed the merge. Completions observed outside Shipper keep their existing
behavior.
The targeted pass includes dependents whose stored blockers name the completed issue as a native Shipper reference or as a GitHub issue number mapped to an active or tombstoned managed record. Transitive dependents and unmapped external GitHub blockers are excluded. The pass does not wait for repository idleness or ordinary worker capacity, does not preempt running work, and does not give a released issue queue-jumping preference. On both surfaces it remains subject to normal safety, lock, pause, quota, and dispatch controls. Daemon-hosted Auto-ship additionally honors repository enablement, halt, and the bounded launch-failure retry ledger. An issue that still has another unresolved blocker stays silently Blocked. Targeted attempts reuse the existing automatic-unblock status.
After exhausting available issues, auto-ship still evaluates all store-blocked issues under the repository’s normal projection policy when the repository is idle. This untargeted pass remains the backstop for unmapped external GitHub blockers and for dependents that a targeted attempt did not resolve. If any issues are unblocked, auto-ship loops back to process the newly available work under ordinary priority, stage, and FIFO ordering.
Sequential auto-ship and single-issue shipper ship S#123 runs execute stages in-process through
the same dispatcher that powers shipper next. Parallel auto-ship keeps one OS process per active
issue for fault isolation and communicates with each worker through a small fork/IPC protocol.
A non-NEW reject stays inside that per-issue ship loop. A reject chain that reaches shipper:new
records the successful returned outcome above. Auto-ship records the command outcome when the run
ends in an error such as a crash, an explicit fail, or transition-cap exhaustion. Issue
disposition and repository safety disposition are then evaluated independently under the
failure outcome contract.
When the current implementation records durable failed state, later auto-ship sessions omit the issue even with a fresh in-memory exclusion set. The current run’s skip set remains an additional same-session mechanism and is also the complete disposition for leave-in-place outcomes.
In a pull-request-disabled repository, CLI auto-ship prefilters a Ready issue whose packaged local
change is missing. It reports ⊘ excluded with the recovery instruction, records the canonical
durable prerequisite failure, and continues selecting without launching issue work. The exclusion
exits 1 but is neutral for the repository safety stop and leaves the durable condition ledger
unchanged. If the final selection pass excludes the remaining Ready tail, the closing report says
that Ready candidates remain but none is shippable; a mixed run that later drains shippable work uses
the ordinary completion headline.
The merge-retry counter is per issue and lives only for the current CLI run or Desktop session. A
retryable issue is excluded for 30, 60, 120, then 240 seconds while other eligible issues continue.
CLI does not sleep when only delayed work remains; sequential and parallel drains end and a later
run can try again. Attempt five retains the issue’s stage and failed flag, skips it for the rest of
that invocation or Desktop enabled run, and emits one issue-scoped merge-retry-exhausted
safety occurrence. It does not persist failed state. A fresh CLI invocation or an explicitly new
Desktop enabled run starts a new retry sequence and can select the issue without recovery.
Review and Rework destinations are ordinary workflow routing. They create no merge streak,
per-issue ceiling, durable failure, or safety-stop occurrence; the next selection observes the new
stage and dispatches review or implementation normally. Retry exhaustion counts once toward the
repository-wide safety stop; every other stage-retaining merge outcome is safety-neutral.
Pending-check deferrals do not participate in the merge-retry counter. Their separate SHA-keyed elapsed-time bound and recovery behavior are described in the merge outcome table above.
CLI and Desktop use the repository safety axis of the failure outcome contract for automatic shipping. Three distinct qualifying condition instances engage the stop. Manual runs do not participate. Repeated observations of one unchanged condition share one strike, including a repository condition observed by several issues. Issue-scoped conditions on different local IDs remain distinct. A changed revision or an intervening successful repository advancement creates a new condition instance.
The pending ledger is repository-scoped, durable from strike one, and shared by CLI and Desktop.
A restart cannot add another strike for an unchanged condition or forget an existing strike. On the
third condition instance, sequential CLI stops selecting work; parallel CLI refuses to refill slots,
drains work already in flight in receipt order, and abandons parked work. Both print the triggering
conditions and any eligible unattempted issue, then exit 1. Desktop keeps desired state enabled
while reporting Auto-ship suppressed.
This shared policy deliberately supersedes the earlier #1427 no-CLI-stop decision, #1428’s exhaustion exemption, and the stuck-only safety rationale inherited from #1413.
The local failed snapshot survives restarts and excludes the issue from later auto-ship sessions.
Mirror-on projects the failed label only after the local commit; store-only performs no GitHub
write. When the packaged artifact remains recoverable, use shipper recover S#123 or Desktop’s
Recover change. This preserves the packaged work and clears Desktop’s session guard. Use Reset
only when change recovery is ineligible or its cleanup is acceptable. See the
posture-specific recovery table for the
available work-preserving paths.
The shared per-run transition cap (MAX_TRANSITIONS = 15) prevents infinite loops, including tight
review/Rework reject and redispatch cycles.
Failed work recovery
Section titled “Failed work recovery”Grooming’s deferred question-file exchange checkpoints its coarse owner surface—Desktop, MCP, or an
orchestrated headless terminal—alongside answered rounds, pending ordinary authored questions with
stable IDs, grooming notes, and the question-round limit captured at session creation. Attended terminal
grooming does not create these durable checkpoints: it asks natively inside one live agent session.
When the answer-capable surface stops, grooming exits 75 as a clean
awaiting-answers pause and stays at shipper:new; it does not enter failed state. A later ordinary
headless groom resumes only from the recorded owner surface. A foreign surface is refused until an
explicit takeover permanently transfers the conversation, while a legacy record with no owner is
unowned and requires the same takeover gesture. Repeat suspension is unlimited and sessions have no
age-based expiry. Current cross-issue and recorded-blocker inputs are regenerated, and issue
title/body drift is disclosed so affected decisions can be revisited.
A saved session is discarded as invalid only when it belongs to another repository or issue, the
issue has left the New grooming entry stage, or the record is unreadable or invalid. A saved session
that still holds the removed compact-confirmation format is separately incompatible: Shipper discards
only that session file, plainly explains that grooming must restart for the issue, and never
transforms or resumes the removed format. Ordinary-question-only saved sessions remain resumable, and
historical session logs, transcripts, and grooming decision metrics are never rewritten or
reclassified. Surface mismatch
is not invalidation: the non-owner sees only issue identity, owner or unowned state, last-saved time,
answered-round count, and pending-batch presence. Question, answer, transcript, and note content stay
hidden. Attended terminal grooming creates no new question checkpoint; an existing terminal-owned
record is instead materialized into the worktree as read-only context that the live session resolves
through its native questions, is retained on cancellation or failure, and is cleared only after
successful owned completion. Desktop and MCP use the same owner
rule. Automatic groom --auto, next, ship, MCP advance, and auto-ship stand down neutrally at New for
a foreign or unowned session, repeat the visible refusal on later passes, and do not mutate failure or
repository-safety accounting.
Invalid result-bearing output from a non-interactive groom is captured before correction begins.
Those runs allow three validation passes. One atomic bundle per issue retains the newest complete
.shipper/input and .shipper/output trees, has no expiry, and survives worktree cleanup. A
successful fresh groom or groom recovery deletes it. Reset-to-New discloses and deletes it only
after the local reset transition commits. An attended groom instead follows the cancellation
contract above: it validates once and creates no recovery bundle when artifacts are invalid or
missing.
When a failed issue has a saved question session, recovery clears failed state, returns it to New,
reports the preserved answered-round count, and retains the conversation with its recorded owner
unchanged. It asks no questions and runs no agent. The owner can resume normally; another surface
must take over first, and an unowned session must be claimed through takeover. A question session
takes precedence over a simultaneous groom-output bundle and retains both; combining a session with
packaged work is ambiguous. Groom-output recovery validates first, skips the agent if validator changes made the output valid, and
otherwise allows exactly one headless correction. Another invalid result replaces the bundle and
keeps the issue failed. Simultaneous groom output and packaged artifacts are also ambiguous and
disable recovery. Invalid groom output is an ordinary counted groom-output-invalid auto-ship failure, and
failed recoverable issues remain suppressed from auto-selection.
A failed issue with a stored packaged artifact has one non-destructive recovery edge back to
shipper:pr-open. Use shipper recover S#123 in the CLI, shipper_recover through MCP, or
Recover change in Desktop. The retired shipper pr recover tombstone fails with guidance to use
the top-level command and performs no recovery mutation.
Recovery dispatches from the stored artifact rather than the repository’s current posture. A pinned
local change remains the source after pull requests are enabled, and an open linked pull request
remains the source after they are disabled. Eligibility requires failed state, no closure hold, and
a packaged artifact. It is independent of whether a crash cleared the stage label or left
shipper:pr-open, shipper:rework, or shipper:ready. A raw legacy reviewed label is eligible as
PR Open.
For a pinned local change, the exact packaged revision must still exist and be reachable from the stored source branch. The branch may advance beyond that revision. A missing source branch, invalid revision, or rewound/divergent branch makes recovery visibly ineligible and points the operator to Reset. A linked artifact must still identify an open pull request.
Except for Rework, recovery clears failed state and stores shipper:pr-open, displayed as PR Open when pull
requests are enabled and Change Packaged when they are disabled. It preserves the exact packaged
revision, source and target branches, title, body, branch and worktree state, workflow history, and
every completed or held review round. The next review appends a new round. Recovery performs no
reset cleanup, event supersession, or artifact mutation. Projection failures do not roll back the
local transition; each surface warns to run shipper issue reconcile --repair.
Preserving Rework reset
Section titled “Preserving Rework reset”Every successful explicit preserving reset and eligible active packaged-change recovery restarts the five-round review allowance, including repeated Rework resets, unchanged revisions, and exhausted allowances. Package identity, full review history, findings, dispositions, and Rework context are preserved; renewal resolves no findings and grants no approval. Eligibility, destinations, and independent retry limits are unchanged. Count completed approved, addressed, superseded, and zero-finding reviews since the latest established renewal in append order; attempts without a completed review consume nothing. The first five completed reviews may reject into Rework; a sixth rejection enters durable Failed over Rework and retains its round, while a sixth approval may advance under existing approval rules. Automatic transitions and rejections, ordinary retries, restart/resume, grooming recovery, superseded-branch restoration, refused actions, and cancellation do not renew. Renewal belongs to the local logical commit even when projection repair is pending; repair grants no second renewal. A committed pending action reports its operation ID and says not to repeat it: store access finishes the existing operation. Recovery uses Recovery committed; local recovery pending for this outcome. For pre-release retained packages, affirmative historical preserving reset or packaged-recovery evidence, including recorded Rework operator-reset history, establishes the latest renewal. Without such evidence the existing full count remains. Upgrade alone leaves Failed intact. Current-allowance progress can be 0 of 5 or 6 of 5; historical round numbers and order never change and may exceed six. Replacement package identities start independently.
Rework is the recommended preserving reset from PR Open, Ready, or Rework, and from a failed issue without a stage. It requires an open linked PR under GitHub PR policy or a reachable pinned local change under no-PR policy. All routes retain their route; early stages do not qualify merely because they hold a package. Missing, closed, merged, or unverifiable evidence refuses the reset without changes; inspect or repair the association with shipper issue reconcile --repair, or recover the local package first. Rework preserves the package, association, branches, worktrees, route and review history, and clears failure. Repeat Rework resets are real operations. Next, Ship, and auto-ship dispatch Rework to implementation, which re-validates and re-delivers the preserved branch and reuses its open pull request.
Every successful Rework reset records operator-reset; automatic review rejection records review-reject with the new round and rendered reason. It supersedes older unaddressed, unsuperseded rounds only for the current package while preserving all history. Every completed current-package round since the latest renewal counts toward the current five-round allowance regardless of handling or supersession; an addressed zero-finding round still counts, while replacement packages start fresh. The first five completed reviews in the current allowance may reject into Rework. A sixth rejection atomically retains and pins its round, establishes review rework, and enters durable Failed over Rework. shipper recover clears that failure while preserving Rework and the retained pin and restarts the allowance. Manual reset selects the newest eligible round, while a later roundless entry takes precedence, and never invents rejection. PR identity and the durable local package UUID scope retrieval. History survives restart. A committed operation awaiting journal recovery is reported as Reset committed; local recovery pending; store access rolls it forward once.
Failed Rework recovery retains its stage and existing pin, clearing failure only after the same package checks. Existing recovery destinations for other stages remain unchanged.
Rework execution. Next, Ship, auto-ship, and shipper implement S#123 run implementation for an issue at shipper:rework. Under the stage lock, implementation freezes one situation from current-package history: a held-round pin selects review rework; otherwise a newer roundless merge-conflict entry selects merge rework; no entry and every other roundless entry selects fresh. Review rework supplies the pinned round, all outstanding diff and locationless check findings, unresolved external threads, and the current-package round ordinal out of five. Every target requires one fix or reasoned won’t-fix reply. An unreadable pin remains review rework and fails without advancing if one correction cannot restore it. Merge rework requires no reply inputs. Acceptance reuses the existing branch and package association. A legacy shipper:implemented issue is handled the same way.
The reset command
Section titled “The reset command”reset --to rework preserves eligible packaged work as described above. Earlier targets are
destructive; an eligible Planned reset closes the PR and deletes managed branches under existing
safeguards. Foreign adopted branches remain protected.
- Without
--to, it presents an interactive picker of valid earlier targets. If the local snapshot is failed and still carries workflow stage label X, the picker also includes X. - With
--to <stage>, it resets directly to the specified target. Ordinary issues must target an earlier stage. A failed issue that still carries workflow stage label X may target X as recovery. - Valid backward reset targets are
new,groomed,designed, andplanned, as long as the target is behind the current stage or matches the failed issue’s retained workflow stage label. - Reset closes matching open PRs and attempts to delete recognized remote branches. Recognition
includes local-ID branches under the current
generatedBranchPrefixand the built-inshipper/fallback; legacy numeric branches are recognized only undershipper/. Historical custom prefixes and numeric branches under a custom prefix are never selected. Reset also removes matching local branches and local worktrees, records later-stage workflow events as superseded, deletes only their GitHub comment projections, commits the target stage to the local store, and projects workflow labels plus a reset notice comment only when issue projection is enabled. - Bodyless
verification_runhistory survives reset supersession just likestage_timinghistory; moving an issue backward does not hide earlier observed verification runs. - Deleting a remote branch that reset has already selected for cleanup skips Git’s pre-push hook, so
pre-push verification never begins for that deletion and no
verification_runis recorded for it. Averification_runalways means a hook actually started, so the absence of a record here is the correct reading, not a missing entry. Reset reaches that point only after its own safeguards pass: adopted foreign PR heads are never selected, linked PRs must be closed first, other branches are re-checked for open PRs, and remote cleanup does not happen at all when remote transport is disabled. This applies identically on the CLI, Desktop, and MCP reset surfaces. - Nothing else changes. Branch creation, branch updates, and branch deletion outside reset keep their existing pre-push verification, and the exception is not exposed as an option, flag, or any other reusable way to skip verification.
- Before discarding a pinned local change, every surface names its full packaged revision and source
branch and recommends
shipper recoverorRecover changewhen eligible. - Reset-to-New names preserved grooming output and saved question sessions (including answered-round counts) before confirmation, then deletes them only after the local state transition commits.
- Before closing an open linked pull request, every surface names it, warns that reset will close it
and clear the stored association, and recommends change recovery when eligible. MCP and Desktop
require explicit acknowledgement before mutation. MCP uses
confirm_discard: trueas its sole destructive acknowledgement for groom output, saved question sessions, local changes, and open pull requests. - Same-stage failed recovery preserves work already completed at X, including X-stage comments and links, while removing artifacts after X and posting the standard reset notice.
- For an
agent_timeoutfailure with committed preservation, the exact matching same-stage reset keeps only the recorded local branch ref when it still equals the full recorded revision. The physical worktree and all uncommitted work are removed. The reset event retains the timeout, stage, target, branch, and revision identity so the next groom or scaffold launch continues from that commit. A different target, missing ref, or moved ref uses ordinary cleanup or fails safe rather than claiming continuation. - Reset always clears local failed state and removes the mirrored
shipper:failedlabel. - Reset preserves local priority and the mirrored
shipper:priority-high/shipper:priority-lowlabels. - Non-failed same-stage reset and all forward targets are rejected.
For symptom-first reset guidance, see failed issues and rollback loops.
Pre-push verification duration is separate from stage timing and shipping duration. It measures only the observed hook interval and excludes transport, remote work, complete-evidence finalization, workflow-history persistence, and queue time.
Verification ownership
Section titled “Verification ownership”Verification is split three ways, and each owner runs a different scope.
Agents verify the work handled in their own session. Planning, implementation (including review and merge rework), and pull-request opening run the narrowest credible checks mapped to the plan steps, acceptance criteria, review findings, and changed code they handled, plus lint and type-check when the repository’s instruction file names them. Any other command qualifies as a default fast static check only when that file explicitly designates it as one.
Format checks, builds, full test suites, and combined complete-verification commands are reserved. An agent runs one only when a concrete obligation cannot be credibly verified without that exact check. A combined command is never a way to obtain lint or type-check: when either is not separately available, the agent records that limitation and leaves the combined command to Shipper and CI.
Agent checks run in the foreground. Every check an agent starts must finish before the session ends, and the session still produces its required result artifact. A session never ends merely to wait on a running check.
Failure and deferral handling is unchanged:
- A prescribed check that starts and fails is actionable. It is fixed or reported, never deferred.
- A prescribed focused or fast-static check the sandbox refuses before it starts is recorded as deferred to review.
- A check that needs genuinely unavailable external infrastructure — a service, secret, or CI-only environment — is recorded as deferred to CI.
- A sandbox-incompatible test fails loudly, is recorded as deferred to review, and receives its first execution during Shipper’s complete verification.
Shipper performs complete pre-push verification once per eligible tree. An existing green receipt satisfies a repeat on an identical tracked tree, as described in reusing green pre-push verification. When complete verification rejects a publication, its failure output returns to the agent in a follow-up session.
CI verifies every push. Nothing an agent defers, and nothing a receipt satisfies, removes a push from CI’s own complete run.
Review keeps its separate targeted-verification and environment-limitation behavior; this ownership split does not change it.
The first real affected-stage session after a change to this split is the representative transcript check for it. That sample is a post-merge observation, not a pull-request gate.
Repository-scoped verification capacity
Section titled “Repository-scoped verification capacity”Participating pre-push hook executions use one exclusive slot per canonical repository. Different
repositories can run concurrently. The same named or lineage identity is serialized across ordinary
clones, the managed clone, moved checkouts, and worktrees. Named identities reserve
~/.shipper/run/verification/named/<owner>/<repo>/capacity.lock; lineage identities reserve
~/.shipper/run/verification/lineage/<lineage-token>/capacity.lock.
Capacity is acquired independently for each hook execution attempt, immediately before that hook starts, and released by the same owner as soon as the hook completes or is interrupted. Remote upload, server response, fetch, base integration, transport recovery, and post-hook repair run entirely outside the reservation and are never counted as queue or verification duration. Active verification duration begins only after capacity is granted.
Only a positive wait produces a verification-queue-wait record. An uncontended acquisition creates
no wait record at all. When the publication also records executed-verification evidence, a granted
wait correlates with the verification_run for the same issue and stage immediately afterward.
Complex-route implementation publications still participate but do not create that execution
record. A timed-out wait has no verification run and no hook-rejection evidence, because the hook
never started. Both outcomes feed the verificationWait phase below.
For participation and exclusion rules, holder diagnostics, the cumulative 45-minute failure,
conservative abandonment, identity-resolution failure, former machine-wide reservations, the
SHIPPER_VERIFICATION_SLOT=exclusive hook contract, and worker-count precedence, see
waiting for repository-scoped verification capacity.
Shipping duration and timing history
Section titled “Shipping duration and timing history”Per-stage shipping durations measure active processing only. Whenever a stage suspends to wait on something outside its own work, the active segment stops and the suspended interval is excluded from that stage’s total. Waiting has never been counted as stage time and still is not.
Selected waits and every executed pre-push verification run are recorded separately as durable local timing history:
- Check settling belongs to
pr_reviewpre-flight and is recorded as aci-checkswait. No other stage records check settling: animplementrun at Rework delivers its change and exits, and the next review pre-flight waits on that revision’s checks. - Failed-check rerun waits belong to
pr_reviewpre-flight and are recorded asflake-rerunwaits. No other stage repeats that gate. - Timer-mode review-age waits and minimum-review-window waits belong to
pr_review, occur before reviewer launch, and are recorded asreview-windowwaits. - Agent retry backoff is recorded as an
otherwait with detailagent retry backoff. - Every other sleep — check polling, queue-lock polling, quota handling, and any unlabeled wait — is not classified and produces no timing history.
Each classified wait is finalized exactly once, whether the waited operation succeeded, failed, or timed out; the record carries only timing, never an operation outcome. Recording is best effort: if it fails, the operator sees a warning while the waited operation’s value, error, and the overall command outcome are unchanged. Environments that disable timing record nothing and behave exactly as before.
Shipping-duration results carry an optional phase breakdown alongside the unchanged per-stage breakdown:
verification— the total of executed pre-push-hook verification runs, of every result.verificationWait— the total of separately recorded verification queue waits, whether the queue was granted or the wait timed out.ciWait— the total ofci-checksandflake-rerunwaits.reviewWait— the total ofreview-windowwaits.agentRetryWait— the total ofotherwaits whose detail isagent retry backoff.
The five phases never overlap: each recorded entry contributes to exactly one of them, so no elapsed interval is counted twice. Receipt-triggered skipped verification and in-progress waits contribute to none of them. Malformed timing entries are rejected, excluded from every total, and reported as warnings; valid per-stage and phase results remain available.
Absent, zero, and missing evidence are distinguished deliberately. With no valid qualifying record, the entire phase breakdown is omitted. With qualifying records that all measured zero, the breakdown is present but empty. Otherwise it contains only the phases whose recorded total is positive — a missing phase means “no recorded evidence,” never “zero.”
Timing history is prospective: waits that happened before this feature shipped are not backfilled or
inferred from timestamps or wall-clock gaps. Completed wait and verification timing survives later
workflow resets under the existing local workflow-history lifetime, and there is no separate
retention control. The CLI prints no phase output. Desktop adds ciWait, reviewWait,
verificationWait, and agentRetryWait to the existing badge total and renders them as CI wait,
Review window, Verification queue, and Agent retry rows. It excludes verification because
verification execution is already included in stage time.
Locking
Section titled “Locking”Machine-local issue locks prevent concurrent execution on the same issue for Shipper clients running on one machine.
- Owner union:
locks/local-<id>.jsoncontains either arunowner with PID, token, andacquiredAt, or anoperator-pauseowner with a token and pause timestamp. Legacy PID records still parse as run owners. - Acquire: A run creates a
runowner. If either owner kind already exists, dispatch refuses; deliberate operator pause is reported visibly while ordinary run contention keeps its existing neutral stand-down behavior. - Pause handoff: Pausing a running command replaces its own token-matched owner atomically. Pausing queued work installs the pause with the matching reservation token before reservation cleanup. An idle pause refuses if a live run or queued reservation owns the issue.
- Dead-owner reclaim: If the recorded PID is no longer running, the next client removes and reacquires the lock automatically.
- Liveness-only run stale state: Dead run owners and corrupt or unreadable records are stale and reclaimable. A live or indeterminate run remains active indefinitely, regardless of age.
- Durable operator pause: An operator pause has no PID, never expires, and is never removed by a stale sweep or process cleanup. Time spent only under this owner does not accrue shipping duration.
- Resume: Resume removes only an operator-pause owner and always reports either
resumedoralready-resumed. - Release: Normal release removes the file only when the owner token still matches, so an old
owner cannot delete a successor lock.
shipper unlock S#123releases reclaimable debris; a live run owner first reports its PID and acquisition time. An operator pause reports its kind and pause time without process attribution. Either requiresshipper unlock S#123 --forcewith the matching assessed token so a successor cannot be deleted; Resume is the normal operator-pause release.
Issue locks are single-machine coordination. Two different machines operating on the same repository do not observe each other’s local lock files.
All workflow commands wrap their execution in withIssueLock().
For stale lock recovery, see stale locks.
Blocking
Section titled “Blocking”shipper:blocked mirrors local store-owned blocked state. Blocker relationships are stored as typed
local blockers: Shipper blockers keep local IDs, while external GitHub issue or PR blockers keep
their GitHub number. The label and canonical Blocked by #N comment are repaired from the store.
When Shipper detects a managed issue closed externally on GitHub, it records the close reason, actor, and closer evidence. New through Rework records enter the external closure hold described above, including when the closer is their own merged pull request. Stable evidence is deduplicated; unknown, absent, or unreadable attribution remains retryable. Ready records keep their existing verified externally merged PR completion path. Existing tombstones are not reclassified or downgraded, and store-only records never enter an issue-mirror closure hold.
- The
nextcommand refuses to advance blocked issues, exceptshipper:new. shipper shipre-tests stored blockers at entry and every stage boundary, including before its Ready-stage merge. Unresolved blockers stop later-stage work without marking the issue failed.- Auto-ship selection excludes blocked issues from the local issue-state snapshot, except for
shipper:new. - Targeted
shipper mergere-tests stored blockers before merge side effects. The untargeted merge queue excludes blocked Ready issues without re-testing or mutating their blocked state. - The
unblockcommand evaluates stored blockers deterministically. Shipper blockers clear only from completed tombstones. A raw GitHub number that maps to a Shipper-managed issue uses the same local tombstone rule; a number with no managed local record retains external behavior, where an issue clears when closed and a pull request clears when closed or merged.
For the short recovery pointer for blocked issues, see failed issues and rollback loops.
Key files
Section titled “Key files”| File | Role |
|---|---|
packages/core/src/lib/issue-state-store.ts | Local issue-state records, tombstones, and transactional mutation |
packages/core/src/lib/issue-state-authority.ts | Store-first workflow/priority/reset commits and optional label projection |
packages/core/src/lib/labels.ts | Mirrored label definitions (names, colors, descriptions) |
packages/cli/src/commands/stage-dispatch.ts | Shared in-process stage dispatch |
packages/cli/src/commands/next.ts | next validation and lock-wrapped dispatch |
packages/cli/src/commands/ship-execute.ts | Single-issue ship loop and stage progression |
packages/cli/src/commands/ship-auto.ts | Sequential and parallel auto-ship orchestration |
packages/cli/src/ship-worker.ts | Parallel worker IPC entry |
packages/cli/src/commands/reset.ts | Stage categorization and cleanup for reset |
packages/cli/src/commands/issue-list.ts | Label display and grouping |
packages/core/src/lib/lock.ts | Local issue lock acquire, ownership-scoped release, operator force handshake, and liveness classification |
packages/core/src/lib/auto-ship-selection.ts | Auto-ship selection and FIFO ordering |
packages/core/src/lib/github.ts | GitHub issue/PR helpers and association reads |
packages/core/src/lib/prerequisites.ts | Label existence validation |
Required-check obstruction and stalled remediation
Section titled “Required-check obstruction and stalled remediation”New automatic check stops apply only to independently acquired hosted checks. Shipper stops in Failed over Rework when current hosted evidence establishes an external obstruction after useful same-pass fixes, or when a completed, answered check-only review repeats with the same failing evidence on the exact unchanged revision. Both stops retain the package, branch, PR association, unpublished fixes, canonical answers, evidence and outstanding findings. Pending remote answer delivery remains visible separately from handled and resolved findings.
An external obstruction records concrete evidence, causal reasoning, an external remedy and why the failure still applies to final HEAD. For example, a required service explicitly rejects an expired organization credential while local code fixes pass: its owner must rotate the credential. A red check, a matching failure on main, or a missing tool alone does not establish that cause. Uncertainty calls for investigation; sandbox limitations and transient failures retain their normal verification and rerun opportunities.
Stalled remediation makes no external causal claim. For example, a completed V4 review’s only blocker is a required hosted verification failure, its canonical answer is retained, no code changes follow, and a fresh hosted acquisition repeats the identical identity and diagnostic evidence. Detection runs after existing refresh/rerun/flake handling and before another review agent. Nits are neutral. Changed HEAD, a passing check, a new blocker, materially changed evidence/procedure/source, pending or unavailable outcomes, incomplete evidence, legacy baselines, unanswered blockers, or request-changes review bodies prevent this mechanical stop. It does not execute a saved arbitrary local command.
Local verification is reported in existing prose summaries and relevant comments, without a
structured local evidence report. Every attempted non-permission local failure, including an
apparently unrelated or pre-existing failure, requires an anchored 🔴 must-fix: comment on the
most relevant changed line and REQUEST_CHANGES, followed by ordinary Rework fixes or prose
replies. Permission-denied and unattempted checks are limitations, never passes or waivers.
All previously required checks stay required. Local-only workflows have no local-evidence
automatic stop; the ordinary review-round cap still applies.
Historical local observations and failure records remain readable without migration. Outstanding historical local findings receive ordinary prose replies and never authorize a new automatic stop. Upgrade alone does not clear findings, renew allowance, or recover Failed. Publication and hook diagnostics and required hook execution remain unchanged.
Each applicable stop records one terminal safety occurrence. A skipped review creates no held round and consumes no review allowance. All required checks remain required: answers, reports, thread resolution and retry actions cannot authorize shipping or merging red checks.
Applying a remedy or restarting Shipper alone leaves the issue Failed. Inspect the durable diagnosis
with shipper issue view S#123 (or --json), then explicitly retry with preserving
shipper reset S#123 --to rework, or shipper recover S#123 when recovery is eligible. Existing
eligibility, locks, closure holds and confirmation rules apply. Both explicit paths renew the
five-round review allowance on unchanged HEAD without a dummy commit, preserve history and useful
unpublished work, and start a fresh attempt. Missing or divergent retained source refs refuse retry;
restore the source history first. Normal integration, hooks, verification and review must confirm
the remedy before advancement. Retained failure evidence becomes historical after renewal or
completion and remains available on Desktop, CLI and MCP, including failures with zero rounds.
Preserved-work continuation lookup failure
Section titled “Preserved-work continuation lookup failure”Continuation lookup uncertainty stops the stage before preparation or agent launch, records ordinary Failed state when available, and returns nonzero. Diagnostics identify the issue, stage, and underlying history, state, or ref problem and say “continuation could not be determined.” Repair that problem, then explicitly run shipper recover S#123 before retry. Repair alone or restart does not clear persisted Failed. Ordinary Reset, including Rework and --force, is refused while this failure is active. Continuation-lookup recovery takes precedence over grooming and packaged recovery and restores the original raw workflow label (including PR Open or Ready), branch, evidence, route, and associations without renewing the review allowance. If failure state could not be recorded, explicit Recover verifies the unchanged stage and branch context read-only and acknowledges a definitive lookup without writing a reset event. Uncertainty still refuses recovery. Confirmed non-applicability permits ordinary execution without broadening eligibility. When applicable work was recorded but its branch is confirmed absent, run output states exactly: “S#<id> <stage>: preserved work was recorded on <branch>, but that branch was not found.” This notice makes no claim of loss or safety. There is no automatic repair or new flag.
Only explicitly validated continuation-lookup failure and recovery groups are transparent to the original continuation classifier. All unrelated failures, resets, transitions, and completion barriers remain effective, and cancellation or timeout-reset continuation still requires the exact recorded commit revision. Recovery never copies preservation evidence or refreshes its revision.