Common Errors
Publication failures and reset cleanup
Section titled “Publication failures and reset cleanup”Diagnosis
Section titled “Diagnosis”A terminal publication report is written only after an attempted Git push remains unsuccessful under the existing retry policy. An observed nonzero pre-push hook status establishes local pre-push rejection. A Git rejection naming the attempted destination establishes remote rejection; GitHub is named only when the repository/destination evidence supports that attribution. Affirmative transport evidence establishes transport failure. Otherwise the cause is unavailable. Executor failure or capacity denial alone does not establish transport or hook rejection. Preparation, PR-service, teardown, and local-only failures remain separate. A failed publication does not establish missing or invalid agent output or invent a failing check.
Observations and logs
Section titled “Observations and logs”The report captures independently available source branch, checkout path, full commit ID, and observation time immediately before the terminal attempt. Its approximate plain reachable-commit count compares the tip with the locally available destination remote-tracking ref. Only proven local absence selects the labelled configured-base fallback. A failed comparison leaves the count/ref/fallback unavailable with separate reasons. These diagnostic reads perform no remote lookup, fetch, staleness analysis, topology assessment, or uncommitted-file inventory.
The combined raw excerpt, including omission markers, is limited to 4096 UTF-8 bytes without splitting characters. Metadata, full paths, tips, and unavailable reasons sit outside that limit. Publication evidence files are under ~/.shipper/logs/ and show their actual path plus complete, incomplete, or unavailable status. An available path alone does not imply complete evidence. Agent/session logs are separately labelled and are not the publication evidence log.
Reset preview and outcomes
Section titled “Reset preview and outcomes”Destructive reset discovers exactly the selected/invoking checkout plus the existing named managed clone. Local lineage repositories have only their selected checkout. Missing managed clones are not created or refreshed, and arbitrary clones are not searched. Checkouts sharing a canonical Git common directory are one branch set with their aliases displayed; separate clones with identical branch names remain distinct plans. Existing branch-recognition tiers are unchanged.
Before deletion, the preview shows each branch’s owning checkout, full tip or tip-unavailable reason, delete/retain/skip disposition, aliases, and discovery limitations. Normal main/user/linked checkouts holding a recognized branch are protected and retained; eligible issue worktrees are removed before branch deletion. Unknown identity, eligibility, or safety is skipped with a reason and reported as incomplete discovery. An unavailable tip alone does not prohibit an otherwise eligible deletion. CLI force skips confirmation only and still prints this preview. MCP dry-run is read-only; direct confirmed execution displays the same captured scan before its outcomes and preserves existing confirm_discard semantics. Desktop confirmation consumes its displayed plan; a missing or consumed preview requires a fresh preview.
Operations identify their owning checkout and distinguish successful deletion, intentional retention, already-absent/missing skips, and failed discovery or cleanup. Safe cleanup continues in other known locations after failures; scan, prune, deletion, or required-holder removal failures prevent full-success wording and the final success transition. PR/remote cleanup remains once per issue under existing guards. A later reset invocation rescans for retry. There is no added local tip/identity/safety re-verification between a captured preview and deletion; existing operation-level reclamation and PR guards still apply.
New validated publication-failure comment groups and captured run-history diagnosis survive reset as historical evidence. A reset, later transition, or newer different failure makes the older report historical rather than restoring current Failed state. Older Agent Failure headings are not inferred or rewritten. Preserving Rework keeps its existing eligibility, token, package, branch, and history behavior with empty destructive plans. These observations and best-effort cleanup provide no new retention or recovery guarantee.
Issue-worker identity failure
Section titled “Issue-worker identity failure”Symptom
Section titled “Symptom”Issue-worker identity check failed appears when a worker cannot verify its own exact process
incarnation. The worker retains its launch identity assessment and refuses its first lock-taking
action without taking a lock. Earlier non-locking admission refusals can still appear before this
failure. A verified worker keeps the same identity throughout later stages and lock-taking actions.
Read the displayed diagnostic before drawing a conclusion:
- Process ID identifies the receiving worker process.
- Liveness is
alive,dead, orindeterminate. Analiveresult alone is insufficient: the check also requires a process start token to identify that exact incarnation. - Start token available: yes/no states whether inspection returned a token.
- Envelope validation gives the separate validation reason when launch data was malformed.
- Inspection evidence contains the inspection result verbatim, including whitespace and long
output. If inspection supplied no evidence, the message says
No inspection evidence was available.
EPERM, a timeout, or empty output alone does not establish an environmental or sandbox cause. Validation evidence and inspection evidence describe separate parts of the identity check.
As independent platform context, macOS Seatbelt cannot execute /bin/ps inside any sandbox because
that program is setuid and entitled. A sandbox allowlist cannot change this constraint. The
Seatbelt backend limitations
explain the platform restriction on executing setuid programs. This fact does not diagnose the
cause of an individual identity failure.
Recovery
Section titled “Recovery”If the worker began inside a sandbox, relaunch it from a normal Shipper surface, such as the terminal, desktop, or Shipper MCP workflow. Otherwise, report the failure with all of the displayed evidence, including the process ID, liveness, token availability, and any validation reason.
Workers newly launched from the merged revision receive this behavior. Already-running workers must be relaunched to activate it.
See also
Section titled “See also”Headless Grooming Is Awaiting Answers
Section titled “Headless Grooming Is Awaiting Answers”Symptom
Section titled “Symptom”shipper groom exits 75 and reports that the issue is awaiting answers after the answer-capable
orchestrator disconnects or restarts.
This is a clean pause, not a failed issue. Shipper checkpoints answered rounds and, when durable, the pending question batch. Its message truthfully distinguishes full, partial, or zero preserved progress. Saved sessions survive CLI and MCP host restarts and have no age-based expiry.
Recovery
Section titled “Recovery”On the session’s recorded owner surface, run the same ordinary headless shipper groom S#123
command; no resume flag is needed, and repeated suspension is safe. A fresh MCP client counts as the
same surface, and MCP answer submission can reconstruct its runner from the durable pending batch.
Desktop, MCP, or terminal callers on another surface are refused with a content-free owner summary.
Use the explicit CLI --takeover, MCP takeover: true, or Desktop Take over gesture only after
the operator chooses to transfer the saved answers. A live issue run makes takeover unavailable.
Shipper preserves the original round limit and transcript while regenerating cross-issue and blocker
context. It discards and starts fresh only if the record belongs to another repository or issue, the
issue left New, or the record is unreadable or invalid. If a durability failure lost answered rounds,
the issue enters failed state: use shipper recover S#123 to clear the failure while retaining the
last checkpoint and its owner. That owner can resume normally; another surface must take over, and an
unowned legacy session must be claimed through takeover. Reset-to-New is the destructive alternative.
See also
Section titled “See also”Attended Agent Session Was Cancelled
Section titled “Attended Agent Session Was Cancelled”Symptom
Section titled “Symptom”An attended groom, design, plan, implement, or pr review session exits 82.
The CLI reports Operator cancelled <stage> ...; the issue remains at <prior stage>. Desktop shows
a finished neutral Cancelled by operator activity instead of a failure or pause.
The interactive TTY agent session actually started and ended without complete valid artifacts. Shipper validated once, retained the issue’s stage from session entry, and recorded a neutral operator cancellation. It did not run output correction or retry the agent, mark the issue failed, create recovery state, or count a repository-safety condition.
Recovery
Section titled “Recovery”No recovery action is required. Run the ordinary stage command again when ready. A saved headless
groom question session remains intact; ordinary shipper groom S#123 resumes it only from its owner
surface, while another surface must explicitly take it over.
For design, plan, implement, and PR review, committed workflow-branch work is retained and
reused with continuation context when the stage and branch still match; uncommitted work was removed
during worktree teardown. Compound groom, next, and ship runs deliberately stop before starting
another issue or stage.
See also
Section titled “See also”Groom Output Remains Invalid
Section titled “Groom Output Remains Invalid”Symptom
Section titled “Symptom”Grooming ends in shipper:failed with a report that the output remained invalid and was preserved.
Desktop may show the same issue with Recover change as the primary action.
The result-bearing output from a non-interactive groom failed all three normal validation passes. Shipper retains the newest complete input/output tree as one durable bundle for the issue; it does not expire. An attended session with invalid or missing artifacts is operator cancellation instead and creates no recovery bundle.
Recovery
Section titled “Recovery”Use shipper recover S#123, MCP shipper_recover, or Desktop Recover change. Recovery validates
the preserved artifacts first and uses one unattended correction only when still necessary. It does
not restart discovery or re-ask questions already answered in the preserved transcript. Another
invalid result replaces the bundle and remains recoverable. Use Reset-to-New only to intentionally
discard the preserved grooming work; every surface discloses that destruction first.
See also
Section titled “See also”Stale Locks
Section titled “Stale Locks”Symptom
Section titled “Symptom”An issue keeps a stale local lock: shipper status, shipper issue list, or desktop shows it as
locked, but no active Shipper run appears to be working on it.
Shipper uses a machine-local issue lock file to prevent two clients on the same machine from
advancing the same issue at the same time. The lock is normally reclaimed automatically when the
owner PID exits. Corrupt, unreadable, or process-less records are also reclaimable. Live or
indeterminate run owners remain active indefinitely regardless of lock age. A valid
operator-pause record is intentionally process-less: it never expires and is excluded from stale
cleanup.
Recovery
Section titled “Recovery”shipper status reports only the aggregate number of operator-paused issues; it does not list which
issues are affected. Find them from the operator hold badge on Desktop issue cards or the
per-issue <issue>: operator-paused - skipped lines printed by shipper unlock --stale. An
operator hold you do not remember setting is state retained from an earlier build; use Resume to
clear it. Existing holds are not expired or migrated automatically.
Choose recovery from the ownership state instead of treating every refusal as stale:
- Running, queued, or reserved workflow: wait for it to finish or use Pause/Stop/Cancel in the client that owns the controllable session. Issue details identify a reservation and its conflicting command.
- External live or indeterminate run: wait for it to finish or stop it from the owning client. Shipper reports the exact issue, PID, and acquisition time. Do not force-unlock healthy or indeterminate work.
- Operator hold: use Resume. Force unlock is available only as a deliberate, reviewed override when you intentionally want to discard the hold; it reports pause time without a PID.
- Confirmed dead run owner: use
shipper unlock S#123or Desktop’s Release lock action. - Invalid or malformed record: use
shipper unlock S#123or Desktop’s Unlock action.
The stale sweep clears only confirmed dead run and invalid records and leaves operator pauses, healthy or indeterminate runs, queued work, and reservations untouched.
On Desktop, unavailable or stale supervision claims do not by themselves disable otherwise-valid Groom, Ship, Next, Pause, Reset, or drag for an idle issue with no known owner; Merge keeps its existing supervision requirement. A known reservation or external owner makes mutation entries visible but disabled without unavailable tooltips or explanatory text and prevents drag. Ownership guidance is available in issue details. Queued Ship keeps Pause and Stop, queued Next keeps Stop without Pause, and Stop appears only for a queued or running session Desktop can actually control. Logs, issue details, and focus for an active Desktop Groom session remain available while mutations are blocked.
See also
Section titled “See also”Direct Daemon Startup
Section titled “Direct Daemon Startup”Symptom
Section titled “Symptom”A hidden shipper daemon run invocation printed a one-line startup decision, or exited
successfully or unsuccessfully, and you need to know what that line means.
Direct daemon startup reports the evidence behind its final decision. It never starts a competing daemon unless the endpoint is locally proven unreachable. Compatible peers, concurrent launches, and uncertainties after a peer accepted the connection are successful no-ops. An incompatible peer, an unanswered connect error without proven absence, and every explicit refusal fail without authorizing startup.
Recovery
Section titled “Recovery”shipper daemon run is a hidden operator and troubleshooting command. It is deliberately absent
from CLI help. As soon as the decision is known it writes exactly one concise line naming the coarse
outcome and discriminator, then exits 0 on success or 1 on failure.
Successful decisions go to standard output:
started (newly-started)already-running (confirmed-compatible-peer)already-running (concurrent-convergence)already-running (negotiation-timeout)already-running (negotiation-closed)already-running (protocol-error)
Failed decisions go to standard error:
already-running (incompatible-peer)already-running (connect-error)and, when the runtime supplied one,code=<CODE>refused (<refusal-code>), includingsocket-path-too-long,non-socket-endpoint,owner-guard-retained, andstartup-refusal
Accepted-peer uncertainty is a safe successful no-op: a short negotiation timeout can happen against a healthy but busy daemon. Unanswered connect errors and refusals fail honestly and do not start a competing daemon. The optional connect error code is the only low-level detail on this line.
Use the hidden shipper daemon inspect command when you need current daemon state. Inspection is
diagnostic only.
See also
Section titled “See also”Inspect Daemon State
Section titled “Inspect Daemon State”Symptom
Section titled “Symptom”You need to distinguish a healthy absent daemon from a reachable daemon or an unreachable endpoint whose live owner has published advisory state.
The daemon manages its own lifecycle and exits when idle, so its absence is not itself a failure. Live inspection and advisory fallback also provide different levels of detail.
Recovery
Section titled “Recovery”The intentionally hidden, read-only shipper daemon inspect command is a troubleshooting aid. It is
deliberately absent from CLI help. Inspection is diagnostic only: it does not mean the daemon should
be kept alive, and an absent daemon requires no lifecycle intervention. The command writes compact
JSON on one line; the examples below are formatted for readability.
A healthy result when no daemon is running is:
{ "running": false, "supervisionDeliveryHealth": { "status": "unknown", "reason": "daemon-not-running", "message": "Supervision delivery health is unavailable because no daemon is running." }}This is a normal state, not an error or a prompt for recovery. When the daemon is reachable, the result contains live service information:
{ "running": true, "reachable": true, "generation": "4.2.0-1a2b3c", "version": "4.2.0", "connectedClientCount": 3, "supervisionDeliveryHealth": { "status": "degraded", "degradedConnectionCount": 1 }, "attachments": [ { "client": { "kind": "desktop", "instanceId": "desktop-1" }, "alive": true, "facts": [], "supervisionDelivery": { "status": "degraded", "activeScopes": ["sessions"], "outstandingStateDeliveryCount": 2, "oldestOutstandingStateDelivery": { "scope": "sessions", "acceptedAt": "2026-08-20T12:00:00.000Z", "ageMs": 10000 } } } ]}The connected-client count includes the inspection command’s own short-lived connection, so it may be one greater than the number of other attached clients. Because inspection attaches as a client, it also restarts the client-free grace window when it disconnects; repeatedly inspecting an idle daemon therefore delays its designed idle exit.
Delivery health is independent from reachability. A connection is degraded only when a
supervision state write accepted by the daemon remains incomplete for at least 10 seconds. Initial
and resynchronization snapshots plus live state events on every supervision channel participate;
pings, service-information and command replies, shutdown/generation notices, and transient live
output do not. Equal accepted and completed progress stays healthy indefinitely while idle or
remotely quiescent, and an open socket cannot override an overdue delivery. Connections without an
active supervision subscription are not-applicable. Aggregate health is degraded when any
connection is overdue, and degradedConnectionCount is the number of independently degraded
connections. Oldest timing is omitted when no delivery is outstanding.
Delivery degradation is advisory: shipper status, shipper status --json, shipper daemon inspect, and MCP shipper_status continue to succeed. Completion, unsubscribe, subscription
replacement, or connection removal clears current degradation immediately without acknowledgement,
disconnect, restart, or stopped supervised work. A legacy peer with no health fields is explicitly
unknown (legacy-peer), never healthy by omission. Durable start, restore/removal, and immediate
rejection records are in the rotating daemon diagnostic log described below.
If a live owner exists but its endpoint is unreachable, inspection falls back to the advisory record:
{ "running": true, "reachable": false, "advisory": true, "generation": "4.2.0-1a2b3c", "version": "4.2.0", "connectedClientCount": 2, "supervisionDeliveryHealth": { "status": "unknown", "reason": "daemon-unreachable", "message": "Supervision delivery health requires a reachable daemon." }}The version, generation, and connectedClientCount fields appear only when matching advisory
facts are available. Advisory fallback therefore is not available for every unresponsive daemon.
A socket that accepts a connection but cannot complete negotiation instead reports a
Daemon endpoint state is indeterminate: ... error; Shipper does not silently replace that error
with advisory data.
See also
Section titled “See also”- Daemon runtime directory reference for the advisory record and diagnostic log
- Daemon lifecycle and dispatch for result interpretation
Auto-ship local eligibility is unverified
Section titled “Auto-ship local eligibility is unverified”A failed read of local issue eligibility makes the observation panel unverified immediately and writes a diagnostic. It warns after 30 seconds and becomes an error after 120 seconds from the first failure, regardless of the number of retries. Inspect the reported local cause; this is separate from a GitHub outage. Unrelated unexplained observation keeps its five-minute warning policy.
Local checks retry approximately every four seconds. The first successful local check, including an empty result, clears the condition automatically without Resume, acknowledgement, or restart. Later GitHub failure does not undo that local recovery. The board, terminal status, and MCP status show the same first/latest failure times, count, cause, severity, last success, and scheduling impact.
The original heartbeat deadline survives local failure. A due observation attempts safely permitted current work; if blocked, it shows the cause and next remote opportunity separately from the next local check. Recovery services overdue work once without catch-up bursts. A blocked heartbeat is not a completed one. Automatic starts wait for verified eligibility while queued reservations and running work remain intact; quota, ownership, safety, halt, pause, and capacity gates still apply.
Disabling Auto-ship shows inactive health and removes retry promises. Restart or re-enable starts unverified with a fresh episode and count until a fresh local success. Diagnostic history is retained. A halt does not stop local failure escalation. See Observation health and recovery.
Supervision delivery retries exhausted
Section titled “Supervision delivery retries exhausted”Symptom
Section titled “Symptom”Desktop shows that automatic retries are exhausted for one supervision state area. shipper status
reports the same exhausted condition and directs retry to Desktop. Healthy areas remain current.
Socket-write delivery health may still be healthy: this is receiver schema rejection, not a 10-second
incomplete write.
A supervision subscriber rejected an application payload. Automatic recovery makes five total attempts at approximately 0, 1, 3, 7, and 15 seconds. Reconnects and Desktop restarts do not reset that budget while the same background service is active. After exhaustion, the daemon probes once every five minutes. Identical rejections produce one start record, at most one aggregate update per minute, and one exhaustion or recovery record. Diagnostics retain payload byte size, a sanitized structural path, and a bounded summary; they exclude full state values, cursors, and delivery identifiers.
Recovery
Section titled “Recovery”Use Desktop’s Retry control for one additional attempt. shipper status is read-only and does not
retry. Only the smallest affected area is unavailable; starts are withheld only for a repository whose
live operational state is unavailable. Last-known values are not shown. If Desktop is wholly
unusable, restart the background service. Daemon restart starts a new daemon-lifetime episode.
See also
Section titled “See also”- Daemon runtime directory reference for the diagnostic log
- Inspect Daemon State for transport delivery health
Those references own the locations and lifecycle behavior.
Missing or Duplicate Workflow Labels
Section titled “Missing or Duplicate Workflow Labels”Symptom
Section titled “Symptom”shipper next, shipper ship, or the desktop app cannot tell which stage an issue is in because the
issue has no workflow label or more than one workflow label.
Every Shipper issue must have exactly one workflow label such as shipper:new,
shipper:rework, or shipper:ready. Missing labels usually mean the repository was not
initialized or the issue was not adopted. Duplicate labels usually mean labels were edited outside
Shipper.
Recovery
Section titled “Recovery”Run shipper setup if the repository has not been configured or if the workflow labels need to be
synced. Use shipper adopt https://github.com/owner/repo/issues/42 for a single existing GitHub
issue, shipper adopt https://github.com/owner/repo/pull/17 for a single existing GitHub PR, or
shipper adopt --all for existing GitHub issues that should enter the Shipper workflow. --all
remains issue-only. PR adoption rejects closed or merged PRs, draft PRs, fork PRs, PRs whose backing
issue still resolves to a different open PR, and PRs with ambiguous closing references with
actionable messages. Desktop users can perform equivalent label-state recovery through the app
without duplicating the CLI steps here.
See also
Section titled “See also”Local Issue-State Content Or Mirror Drift
Section titled “Local Issue-State Content Or Mirror Drift”Symptom
Section titled “Symptom”Shipper reports that the local issue-state replica is missing, malformed, pending a GitHub mirror,
has older local issue-state metadata that cannot be reconstructed locally, or has content drift. A
run may fail with recovery guidance to run
shipper issue reconcile --repair, repair may report that older metadata was recovered, or
shipper issue reconcile may report that GitHub title/body content differs from the store.
Null-mirror drift is reported per record as Mirror pending (repairable) when repair can
reconnect an eligible origin, create a mirror, or attach an exact match, and as Mirror pending
(not currently repairable) when a remembered origin is missing, closed, labeled, or claimed. The
latter names the origin and creates no replacement. Existing closed-mirror drift is reported as
closed GitHub mirror (the GitHub mirror exists but is closed; repair direction: reconcile closure by lifecycle state).
Either condition is real drift, so check mode exits 1.
Routine comment convergence handles outstanding delivery. Use shipper issue reconcile --repair
for already delivered comment content drift and unknown duplicate discovery or cleanup. A warning
names S# identity and the GitHub parent/comment ID when known; retry or run repair after fixing the
lookup failure. Local stages and committed note UUIDs remain authoritative while delivery is pending.
A saved comment is replaced only after direct comment 404 plus successful matching parent access;
listing absence and a bare 404 are inconclusive.
Recovery covers stage comments, notes, PR flake comments, Unblocked and Blocked comments.
Completed explicit repair posts wanted legacy work once when no attributable copy is found and no
concrete ambiguous-send evidence exists. Superseded or retracted absent work is retired without
posting. Closure alone does not retire historical comments, including post-closure notes.
Legacy work without an attempt token directs the operator to shipper issue reconcile --repair.
Uncertain delivery retains automatic duplicate protection: another send cannot be promised automatically.
Use shipper issue recover-comment S#42 to inspect without posting, then authorize one exact
current token with all four options:
shipper issue recover-comment S#42 --group <id> --attempt <token> --retry --accept-duplicate-riskThe earlier attempt may already have posted. Authorizing another attempt can create a duplicate comment.
A found attributable copy wins over authorization. Incomplete, unavailable, malformed or exhausted searches consume no token and never establish absence. Saved authorization, recovered receipt identity, verified body acknowledgement, retirement and cleanup are separate outcomes. Ineffective PATCH and residual cleanup remain pending. Obsolete uncertain work is retired only after evidence or authorization. Routine convergence retains conclusive absence for its matching attempt. Transient unavailable discovery retries at the next due ordinary synchronization without requiring inspection or repair. Explicit repair reuses a completed catalog per actual issue/PR parent and directly validates selected identities before mutation.
shipper issue recover-blocked-comment remains a Blocked-restricted compatibility wrapper using
the same flags and engine. Blocked ownership and slot ordering remain stricter: saved ID, heading or
body alone never establishes ownership. Store-only policy refuses GitHub recovery without writes.
A live stage defers authorization without recording it; rerun after the stage finishes.
Repair changes only Shipper-owned Blocked comments: a currently validated marker belonging to a known local Blocked group or explicit POST provenance for that same comment ID establishes ownership. A heading, matching body, author account, saved ID, or body hash alone does not. An unmarked legacy linkage is not acceptance proof. Unverifiable old copies remain for manual removal, including human Blocked text on an unblocked mirror with no Shipper history. Ordinary non-Blocked legacy-body adoption remains available in explicit repair.
Automatically eligible known-unsent workflow-comment delivery retries at the saved quota deadline or after 1, 2, 4, 8 and 16 seconds, up to six failed passes. Definitive permission/validation rejection stops immediately. Rate-limit evidence takes precedence over rejection and consumes no failed attempt. Uncertain delivery never automatically resends merely because discovery is empty. Existing unrelated GitHub errors keep their pending-mirror treatment without a new generic retry loop.
Plain repair may service stopped work once without resetting its automatic budget. If it still fails, its stopped state and attempt count remain; timers and priority changes do not restart a full cycle. Active GitHub holds still apply, including after a priority change. “GitHub delivery held before launch” means no invocation launched; “GitHub rejected delivery; automatic retries stopped” requires matching rejection evidence. Unknown delivery does not prove that a POST was rejected or absent.
A corrupt derived pending-work index produces issue-specific stopped repair guidance while healthy peers remain usable in status, lists, the desktop board and catch-up. Explicit repair reconstructs the index from local records. Malformed authoritative records or history require correction and are retained unchanged. After successful authorized recovery, an immediate pending read reflects verified completion before ordinary synchronization; a recovered receipt alone does not verify body or cleanup. Unrelated/newer work and size-limit debt remain pending.
Authorization-required work displays the exact group/token command above instead of promising automatic resumption. Duplicate-risk warnings apply only to unresolved attempted POSTs; proven-unsent/rejected posts, completed/retired work and accepted-edit observation do not receive them. Definitive causes appear once per target. Desktop shows stopped retries as attention rather than success.
V20 retains a one-time blocked-import sweep for affected V16–V19 stores, supported legacy V1–V10 stores and unreconciled V11–V15 stores. Reconciled V11–V15 stores retain their exclusion. The first mirror-on repair stages unclaimed managed blocked issues before clearing eligibility; ordinary reads do no remote discovery. Incomplete discovery remains pending for later repair.
Notes, CLI/desktop priority changes, and explicit mirror sync succeed locally during a running stage. Automatically eligible work reports:
Saved locally; GitHub delivery pending while a stage owns this issue. Delivery resumes at the next ordinary synchronization after the stage finishes.
Do not unlock a healthy stage for these operations. Lock release performs no GitHub work and never waits. Work resumes at the next ordinary issue synchronization. Later retry deadlines remain durable and the runtime services them automatically. If all processes exit, the next runtime startup resumes requests even with Auto Ship disabled. Current store-only policy cancels queued obligations. Newer request generations and independent error debt survive completion of an older generation. Upgrade all CLI, Desktop, and MCP clients sharing a V20 store before writing new recovery evidence; do not delete or downgrade stored evidence.
Shipper-managed issue title/body content is authoritative in the local issue-state store. GitHub is the visible mirror for users and integrations. Direct title/body edits in GitHub are not imported back into the store after adoption or migration; repair treats that as store -> GitHub drift. Pending local records can also exist when Shipper wrote the store record but failed before creating or attaching the GitHub mirror. A missing-mirror condition means the issue mirror is genuinely absent. A closed-mirror condition means the issue mirror still exists on GitHub but is closed while the local record remains active. Older metadata can exist on machines that ran earlier Shipper versions before issue-state schema bumps. Locally reconstructable older replicas migrate automatically on first issue-state access and emit a one-time informational notice. Older replicas with local-ID V3 blocked records that lack a stored local blocker list still need GitHub data and stay on manual repair. GitHub-number snapshot filenames are retired and refuse the store. Newer metadata means the local store was written by a Shipper CLI newer than the one currently running.
Recovery
Section titled “Recovery”If Shipper can reconstruct an older replica from local data, retry the original operation; Shipper
migrates that replica automatically on first access. Run shipper issue reconcile --repair for
supported local-ID V3 blocker imports needing GitHub data, malformed current stores, uninitialized
or recreated stores, pending GitHub mirrors, or content/label drift. Repair preserves the
next-local-ID floor from metadata, snapshots, tombstones, and pending identities. Fresh or recreated
stores retain first-reconciliation blocked-state imports from GitHub comments. Durable unfinished
imports survive interruption and retry even after metadata becomes reconciled. Local-ID V3 imports
with stored blocker lists complete locally in store-only mode; GitHub-derived debt waits for a
mirror-on repair. Current authoritative records do not reimport GitHub blocked state. Repair
recreates genuinely absent GitHub mirrors from the local store and overwrites GitHub title/body
content from the local store when drift is detected.
Closed mirrors are reconciled by lifecycle state instead of being retired indiscriminately. Ready
records with a confirmed externally merged stored PR complete through the existing Ready path.
Mirrored records from New through Rework enter a closure hold that preserves stage, failed,
blocked, priority, PR association, and blockers for a deliberate not-planned, reopen, or complete
decision. Ambiguous actor or closer evidence remains retryable for later attribution. If the mirror
is externally reopened, refresh or repair restores projected labels and clears the hold. Complete
requires an assertion/dependent preview plus its current confirmation token; not planned does not
release dependents. Reset does not clear a closure hold; choose one of the three closure outcomes
instead. In store-only, reconcile does not fetch, report, update, close, or create issue mirrors.
Retired GitHub-number snapshots
Section titled “Retired GitHub-number snapshots”Records named issues/<GitHub-number>.json or
_pending-blocker-imports/legacy/<GitHub-number>.json refuse the entire store before repair,
journal recovery, or writes, including orphaned records and malformed JSON or metadata. The
message names the repository, absolute store root, and every old record. Existing local issue
state is left unchanged. This Shipper version no longer migrates these records. Use a Shipper
release from before this change to migrate the store before retrying this version; no specific
release is promised. Supported local-ID V3 upgrades and old metadata alone are not retired, nor
is a current record merely because it has a githubIssueNumber field.
The obsolete GitHub shipper:locked label has no lock meaning. Repair ignores and preserves it
without refusing solely for its presence. Operators may delete it manually.
Newer store / stale reader
Section titled “Newer store / stale reader”When the observed issue-state schema is greater than the supported schema, the running reader is
stale. Restart or reconnect the MCP client and server. If a freshly launched server remains
incompatible, update the installed Shipper release or rebuild the source checkout before
reconnecting. Preserve the issue-state store unchanged. Use shipper_status after reconnecting to
compare both schema versions and verify that server.startedAt and server.buildIdentity identify
the new process and loaded bundle.
Make future content changes through Shipper intake, grooming, or adoption flows instead of editing the GitHub issue title/body directly.
See also
Section titled “See also”Adopted PR Closed Or Superseded
Section titled “Adopted PR Closed Or Superseded”Symptom
Section titled “Symptom”shipper next, shipper ship, shipper merge, or the desktop Ready merge action reports that a
linked PR was closed without merging or could not be found.
Native and adopted foreign-branch PRs are tracked through the same durable stored Shipper association. The association survives PR body rewrites and closing-reference edits. Shipper only follows the recorded PR while it is open. If a ready issue’s recorded PR was already merged outside Shipper, the merge command, merge queue, MCP merge tool, desktop refresh, and reconcile repair complete the issue without performing another merge or mutating the PR. Earlier PR stages are not eligible for merged completion.
Recovery
Section titled “Recovery”Use the recovery that matches the observed state:
- Merged: leave a ready issue in place and rerun
shipper merge, refresh desktop, or runshipper issue reconcile --repair; Shipper completes it automatically. - Closed without merging: run
shipper issue reconcile --repair. When it reports[closed-unmerged:recovered], runshipper next; Shipper revalidates the surviving implementation branch and delivers a fresh PR through implementation. - Not found: investigate whether the PR was deleted, moved, or recorded for the wrong
repository. Shipper preserves the association and stage because not-found is not abandonment
evidence. Re-establish the reference before retrying. Use
shipper reset S#42 --to reworkonly after independently confirming preserved work is available. - Status lookup failed: retry after GitHub access recovers. Shipper preserves the existing link and does not advise re-linking when it could not determine the pull request state.
- Head branch gone: restore the exact PR head branch, then rerun reconcile. Do not reset to Rework until the implementation branch exists again.
- Issue mirror closed: resolve or reopen the GitHub issue mirror before retrying. Do not reset while the issue-side closure remains unresolved.
- Active Shipper run: wait for the run holding the issue lock, then rerun reconcile.
- No link recorded or a known replacement: re-deliver with
shipper implement S#42, or runshipper adopt https://github.com/owner/repo/pull/<current-pr-number>for the intended replacement. The desktop adoption modal provides the same adoption path.
See also
Section titled “See also”GitHub Authentication Problems
Section titled “GitHub Authentication Problems”Symptom
Section titled “Symptom”Shipper cannot read issues, update labels, create pull requests, or clone GitHub-backed worktrees because GitHub API or git authentication fails.
Interactive runs need an authenticated GitHub CLI session. Containers and CI usually rely on Auth tokens, and git operations also need a usable credential helper.
Recovery
Section titled “Recovery”For local interactive use, run gh auth login and confirm gh auth status passes. In noninteractive
environments, export one of the supported
Auth tokens in the same environment that runs
Shipper.
If Shipper can authenticate but GitHub remains unavailable for the full clean-outage waiting
allowance, the ship command exits 79. That result is unsuccessful but non-durable: no failed
state is committed, the issue retains its workflow stage, the current run skips it, and a later run
can select it normally. It is neutral for the repository-wide safety stop. Restore GitHub
availability and run again; no reset or recovery command is required.
The same leave-in-place behavior applies to any GitHub read that fails before groom, design, plan, or implement has mutated stage state. A model or reasoning value rejected by the configured agent also retains the stage and needs only a settings correction; a later command selects it normally. Neither condition creates failed state.
See also
Section titled “See also”- Getting Started authentication
- Container token authentication
shipper setup- Failure outcome contract
Pull Request Creation Could Not Be Confirmed
Section titled “Pull Request Creation Could Not Be Confirmed”Symptom
Section titled “Symptom”Implementation delivery reports: “A pull request may already exist on GitHub, but Shipper could not confirm it. The issue stays at its current stage and will be retried in a later run.”
GitHub either refused creation because the packaged head already has a pull request, or a protected create attempt ended without confirmed pull-request metadata. Shipper then performs a guarded lookup restricted to an open, same-repository pull request with the packaged head and attempted base. A failed lookup, an empty filtered result, or only an ineligible candidate is treated conservatively because GitHub may still contain the just-attempted pull request.
Recovery
Section titled “Recovery”Restore GitHub availability when needed and rerun the ordinary ship command. The issue stays at its current stage, and on re-entry the initial lookup reconciles a now-visible eligible pull request without creating another one. No reset or recovery command is required.
This outcome adds no issue comment, pull-request association, or failed state. Once it 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. The outcome is explicitly neutral for repository
safety. Exit 79 means the confirming read exhausted the protected outage waiting allowance; other
unconfirmed results use exit 1. Neither result asks the operator to reset.
See also
Section titled “See also”Missing Local Base Branch
Section titled “Missing Local Base Branch”Symptom
Section titled “Symptom”In fully local mode, a stage reports that a configured local base branch does not exist, or that automatic detection could not find any of the listed candidate branches.
When transport is none, Shipper uses exact local branch references and deliberately ignores remote
and remote-tracking references. A configured defaultBaseBranch may have been deleted locally, or
none of the automatic candidates (such as main or master) may exist.
Recovery
Section titled “Recovery”Create or restore the named local branch, or update defaultBaseBranch at the settings source named
in the error. For an automatic-detection failure, create one of the listed candidates locally or
configure defaultBaseBranch. A proven missing base is durable, so after fixing the branch use the
explicit reset command named in the diagnostic before retrying.
Missing Local Workflow Branch
Section titled “Missing Local Workflow Branch”Symptom
Section titled “Symptom”In fully local mode, a stage reports that its required workflow branch does not exist locally.
The workflow branch may contain commits from an earlier stage attempt. Shipper refuses to recreate
it from the base or substitute another branch because doing so could hide lost workflow history.
Remote and remote-tracking references are ignored when transport is none.
Recovery
Section titled “Recovery”Restore the exact named local branch from the source that contains its workflow commits, then retry. Do not create a replacement branch from the base unless you have independently confirmed that no workflow history needs to be recovered.
Worktree Creation and Install Command Failures
Section titled “Worktree Creation and Install Command Failures”Symptom
Section titled “Symptom”A stage fails while creating its isolated worktree, preparing dependencies, or running the configured dependency bootstrap.
Shipper creates ephemeral worktrees under ~/.shipper/worktrees/. Dependency bootstrap consists of
the optional installCommand followed by executable .shipper/hooks/worktree-setup. If an initial
install fails while using Shipper’s marked shared npm, Turbo, uv, pnpm, Cargo, Playwright browser,
Electron runtime, or electron-builder packaging cache, Shipper leaves every shared cache intact,
rewrites only the marked variables to worktree-private paths, and retries once before classifying
the preserved command evidence. Positive registry transport evidence such as DNS failure,
connection reset, timeout, TLS/proxy failure, or HTTP 408/429/5xx retains the workflow stage and
needs no reset. A
missing command (exit 127 or ENOENT), broken manifest/lockfile/configuration, or unknown nonzero
exit enters durable failed state with a concrete repair and explicit reset action. A non-zero
.shipper/hooks/worktree-setup exit still warns and continues so an agent can inspect and repair the
worktree. Timeouts still abort bootstrap when installCommand or worktree-setup exceeds
hookTimeoutMinutes.
Shipper also runs installCommand after it integrates the base branch by merge or rebase. If this
post-integration install exits non-zero for any reason, it first falls back from marked shared paths,
then clears eligible private or custom worktree caches and retries once before the existing
three-attempt agent remediation path. Shared sentinels survive both retry paths, so a failing run
cannot delete cache data still used by concurrent worktrees. The automatic reset and retry still
happen even on code paths where no agent remediation is available.
The effective NPM_CONFIG_CACHE, TURBO_CACHE_DIR, UV_CACHE_DIR, PNPM_CONFIG_STORE_DIR,
NPM_CONFIG_STORE_DIR, CARGO_HOME, PLAYWRIGHT_BROWSERS_PATH, electron_config_cache,
ELECTRON_BUILDER_CACHE, and XDG_CACHE_HOME locations are eligible anywhere they safely resolve
inside the affected worktree. Any other configured worktreeEnv location is eligible only when it
resolves strictly below that worktree’s .shipper/tmp/ directory. The rule is the same whether the
effective worktreeEnv map came from user-global, repository, or personal settings.
Shipper removes only the locations named by qualifying entries, never .shipper/tmp/ itself and
never an active machine-wide or legacy shared-cache root.
Question audit trails, rendered grooming bodies and comments, failure markers, and other scratch
content therefore survive. Locations outside the worktree are never removed, and non-qualifying
custom entries are silently ignored. A cache-clear failure is reported but does not replace the
install error or prevent the single retry. This automatic recovery has no flag or setting to tune
or disable. sharedCache: false controls only the sharing performance optimization; it does not
disable local cache recovery.
Shared-cache preparation itself degrades per cache. If one child cannot be created, secured, or
probed, Shipper records one key-specific informational reason in run output, keeps that cache
private, and leaves every other eligible cache shared. A common-root failure reports one reason and
keeps all covered caches private. Neither case fails the stage, increments a failure counter, creates an alert or
notification, or changes board state. A string-valued operator override is reported once per run in
an aggregate notice that names every blocking worktreeEnv setting and says removing it restores
sharing. A deliberate effective sharedCache: false opt-out is silent; user-global false applies
machine-wide, while repository or personal true overrides it.
When neither installCommand nor executable .shipper/hooks/worktree-setup is configured, Shipper
prints a visible advisory warning and continues without installing dependencies. An empty
installCommand means setup deliberately recorded that no bootstrap is needed.
Recovery
Section titled “Recovery”Inspect the effective bootstrap command, timeout, and their origins first:
shipper settings get installCommandshipper settings get hookTimeoutMinutesIf a Shipper-owned shared cache is suspected of containing poisoned artifacts, the explicit clear action removes both the active machine-wide root and all orphaned legacy per-repository roots. It affects every repository on this machine, so coordinate with other workers before running it:
shipper cache clearNormal worktree preparation, install recovery, target reclamation, and orphan sweeps never clear, prune, evict, size-cap, seed, or copy the active or legacy shared roots. The explicit command also excludes worktree-private caches and operator-configured paths.
Check that the repository can create git worktrees, that the failing path under
~/.shipper/worktrees/ is writable, and that the effective bootstrap is valid. For multi-package
repositories, verify that installCommand and .shipper/hooks/worktree-setup cover every area
needed by the first build or test. If a post-integration install remains broken after automatic and
agent remediation, inspect the final command output rather than deleting additional worktreeEnv
locations. When the shared command is wrong, correct it in committed repository settings and commit
the result. If the diagnostic says the initial failure is durable, run its explicit reset only
after repairing the command:
shipper settings set installCommand 'npm install' --repoIf the operation is legitimate but slow, choose a timeout scope deliberately. Use --user when the
preference should apply across repositories, or --personal for only the current repository:
shipper settings set hookTimeoutMinutes 30 --usershipper settings set hookTimeoutMinutes 30 --personalSee the settings precedence for how these scopes combine.
Automatic orphan reclamation
Section titled “Automatic orphan reclamation”A detached dev server, file watcher, editor, or shell can keep its working directory inside a stage
workspace after the stage process tree exits. If it writes after Shipper removes the workspace, it
can recreate that generated directory without .git linkage or live ownership evidence. In older
versions, every later stage needing that path stopped immediately, and manually deleting the
directory helped only until the leaked process wrote again.
Teardown now runs worktree-teardown first, then finds every process whose working directory is the
workspace or a descendant. Shipper sends SIGTERM, waits on a fixed two-second rescan window, and
sends SIGKILL to matching identities that remain, with one final second for exit. It reports all
stopped PIDs and commands in one worktree line. Open files alone do not make an outside-cwd process a
target. If inspection, signalling, or exit is incomplete, Shipper emits one warning, records retired
parent provenance when possible, removes the workspace, and lets the stage retain its original
outcome. Use the worktree-teardown hook to stop
repository services gracefully before enforcement begins.
Every worktree-backed stage first evaluates only the exact branch and generated path blocking that stage. This synchronous target recovery runs inside the target transaction. After target preparation releases its path guard, Shipper opportunistically schedules trigger-driven background maintenance across every repository represented in the machine’s shared worktree root. The caller never waits for machine-wide repository inspection, even when inspection later fails.
Concurrent triggers coalesce within a process. Across processes, each task tries a fixed machine-wide lease once and skips immediately when it is contended; it does not wait or queue. Failed or contended attempts clear the in-process task, and failed tasks release any lease they hold. An abandoned lease ages out after a generous bound, so later triggers retry naturally. Global success, zero-reclamation runs, contention, skips, and failures produce no normal log or aggregate summary. They produce no recovery record, toast, notification, prompt, or board state. Debug-only diagnostics remain available for internal troubleshooting. There is no polling interval, per-second tick, or timer-driven directory scan.
A registered Shipper-managed target can be recovered regardless of age when its repository and
branch match. An unregistered target with valid .git linkage remains eligible when the link names
the current canonical Git common directory and the canonical path and generated name match. The
exact generated target can
also self-heal when .git is truly absent and no live-owner record exists, provided it lies inside
the managed root and the current parent repository registers no worktree there under any branch.
Empty, ignored, unignored, and otherwise non-empty contents are treated alike.
Before clearing that linkless target, Shipper runs the same cwd-holder termination sequence under
the path guard. If inspection is incomplete, a signal fails, or any selected or newly discovered
holder survives, the stage refuses deletion and names each known PID, command, and diagnostic so
you can stop it directly. Existing recovery lanes that still have ownership or parent attribution
do not run this new termination step. Each successful synchronous target recovery prints exactly
one stable record, [shipper] worktree-recovered {"path":"/canonical/path"}, and Desktop turns that
record into one transient Recovered abandoned stage workspace notification. Linkless recovery
does not add a second stopped-process success line.
Missing ownership evidence, Shipper’s own process, and the current run’s token-matched issue lock do not block target recovery. A live or indeterminate third-party owner, unreadable or malformed evidence, or mismatched repository, branch, or path does block it. Recovery discards uncommitted and untracked files without enumerating or preserving them, then continues the stage in the same run. The branch ref, committed history, and pull request state survive unchanged.
The global sweep remains narrower: recorded ownership still requires a confirmed-dead PID, and its
legacy fallback still requires a record-less, confidently attributed, unregistered directory
strictly older than 14 days. A linkless recreation reaches that same unchanged age-qualified lane
only when matching retirement provenance was written before removal, its named parent is resolvable
and no longer registers the path, the basename uses the current hashed --wt-- grammar, and a
complete inspection finds no cwd holder. The sweep stays silent, trigger-driven, non-blocking,
machine-wide, leased, and single-flighted.
This widened sweep lane is future-only. A recreation made before this behavior shipped has no
retirement provenance and is left alone regardless of age; the exact-target path clears it the next
time a stage needs that location, or you can remove it manually after confirming it is unused.
Malformed, unreadable, mismatched, or missing records, registered paths, holders, incomplete
inspection, and directories at or under 14 days remain untouched. There is no flag, environment
variable, setting, manual age override, command option, or shipper unlock --stale workflow that
disables, tunes, or loosens teardown enforcement, exact-target recovery, retirement evidence, or the
sweep.
See also
Section titled “See also”Branch Already Checked Out
Section titled “Branch Already Checked Out”Symptom
Section titled “Symptom”A stage stops before the agent runs with one of these message shapes:
Branch "shipper/s42-add-feature" is already checked out at /path/to/checkout
Refusing to recover worktree /path/to/checkout: the branch is checked out outside Shipper'smanaged workspace root. Switch that checkout to a different branch and rerun this stage.
Refusing to recover worktree /path/to/worktree: <ownership or attribution reason>.<The single action the operator should take.>Here, shipper/ is the configurable default. The actual error shows the repository’s resolved
generatedBranchPrefix, including an empty or
custom value. Inspect it when you need to interpret the branch name:
shipper settings get generatedBranchPrefixGit allows a local branch to be checked out in only one checkout or worktree at a time. Shipper needs to create a stage worktree on that branch. Before surfacing this failure, Shipper evaluated the named holder and refused to recover it because it was outside the managed workspace root or carried live, indeterminate, malformed, unreadable, or mismatched ownership evidence.
Recovery
Section titled “Recovery”Follow the single action named in the error:
- If a live or indeterminate third-party process owns it, wait for or stop that owner, then rerun.
- If the branch is checked out outside
~/.shipper/worktrees/, switch that named checkout to a different branch, then rerun. - If managed ownership or repository attribution is unverifiable, inspect the named directory and remove it manually only after confirming it is unused.
Shipper never switches, removes, detaches, or otherwise alters a main checkout, a folder-registered
developer checkout, or another holder outside its managed root. Changing generatedBranchPrefix
does not release the existing branch and is not a valid way to evade the conflict.
See also
Section titled “See also”Repository Settings Changes Before Setup
Section titled “Repository Settings Changes Before Setup”Symptom
Section titled “Symptom”A shipper settings set ... --repo or shipper settings unset ... --repo command fails with:
Repository settings change was not applied because repository setup has not created committed Shipper configuration. Run
shipper setup --repofirst, then retry the settings change.
Repository setup has not created .shipper/settings.json yet. Any repository-scoped set or unset
would otherwise create the first committed settings file without the CLI fingerprint recorded by
setup. Shipper therefore refuses every repository key and both mutation actions without writing the
settings file or its .shipper/ parent directory.
Recovery
Section titled “Recovery”Run shipper setup --repo to create the committed repository configuration, then retry the original
settings change. Use the deterministic command shown here rather than shipper setup --assist --repo, which delegates artifact creation to the setup agent and may not create the settings file.
The GitHub-backed setup route requires GitHub authentication and a GitHub remote. A committed
no-remote setup is also supported and needs neither. An origin on a non-GitHub host remains
unsupported by repository-target setup; use --user or --personal instead when the setting and
license key permit that scope. Commit the resulting .shipper/ changes according to the setup
guidance.
See also
Section titled “See also”Agent Setting Verification Failures
Section titled “Agent Setting Verification Failures”Symptom
Section titled “Symptom”A settings save is refused because a model or reasoning-effort value was rejected, or because verification could not run. The message names the affected stage and resolved agent. Rejections include the offending value and the agent’s response; a manual check also names the dotted key and settings file that supplied it. In Desktop, the message appears beside each refused control while unrelated edits from the same save still apply.
Changing an effective agent, model, or effort makes Shipper run a minimal real invocation for every affected stage. A rejected value is not written. Shipper also fails closed when the resolved agent is not installed, is not authenticated, is offline, or does not finish within the single 30-second overall verification deadline, including command preparation. Claude and OpenCode may accept an effort without confirming that they used it; this is a warning, the value is written, and the agent may ignore it.
Recovery
Section titled “Recovery”Run shipper settings check to check the stored combinations. Correct the named value at the
supplying scope, or install and authenticate the resolved agent, restore network access, and retry
when it is available. The refused settings file remains unchanged. Desktop retains refused drafts,
offers Check agent settings, and has no save-anyway control.
For an intentional unattended or recovery write, the CLI alone can bypass the probe:
shipper settings set commands.implement.model emergency-model --personal --skip-agent-verificationThe command reports that the setting was stored without a passing verification. Follow it with
shipper settings check when the agent is available again.
See also
Section titled “See also”CLI Version Drift and Version Mismatch
Section titled “CLI Version Drift and Version Mismatch”Symptom
Section titled “Symptom”Shipper warns or refuses to run because the repository’s recorded cliVersion does not match the
CLI that is running now.
shipper setup --repo records the Shipper CLI version in .shipper/settings.json. If the installed
CLI is newer only within the same major version, Shipper allows the command with a warning. Missing
or unreadable fingerprints are blocked when the repository has committed Shipper settings, and
installed CLI downgrades or major-version drift are blocked because the repository may have been
initialized against incompatible or newer Shipper behavior. Repositories configured only with
user-level settings do not require a committed fingerprint.
Recovery
Section titled “Recovery”Rerun shipper setup --repo from the repository root to refresh Shipper-managed files and the
recorded cliVersion. This also clears repeated same-major warnings by refreshing the fingerprint.
Use the SHIPPER_SKIP_VERSION_CHECK runtime override
only as a temporary escape hatch when you must run before refreshing the repository configuration.
See also
Section titled “See also”Agent and Hook Timeouts
Section titled “Agent and Hook Timeouts”Symptom
Section titled “Symptom”A stage stops with an agent timeout, or a hook, worktree setup, teardown, or install phase times out before it finishes.
Headless agent processes are bounded by agentTimeoutMinutes. File-based hooks and the worktree
dependency install command are bounded by hookTimeoutMinutes.
For issue-backed shipper_advance, shipper_groom, and resumed grooming through
shipper_answer_question, MCP gives agentTimeoutMinutes the same per-attempt meaning as the CLI
and imposes no earlier whole-stage deadline. Time paused for product-owner answers does not consume
an agent attempt’s allowance. A terminal MCP timeout returns only after local timeout history and
failed state are committed, the MCP-attributed run is terminal, and that run’s own issue lock is
released.
When an initial or output-correction agent invocation times out after making commits, the retry notice says that the next attempt is continuing from preserved committed work. The next agent is instructed to inspect those commits and continue instead of restarting. Staged, unstaged, and untracked files are still discarded. A timeout that adds no commits uses the ordinary retry notice and reports nothing as preserved.
A retryable non-timeout agent exit removes only commits made by that failed attempt. If an earlier attempt timed out and preserved commits, those earlier commits therefore survive a later crash in the same stage run.
Recovery
Section titled “Recovery”Inspect both effective timeout values and their origins:
shipper settings get agentTimeoutMinutesshipper settings get hookTimeoutMinutesFor a cross-repository preference, set the timeouts once at user-global scope:
shipper settings set agentTimeoutMinutes 0 --usershipper settings set hookTimeoutMinutes 30 --userFor a preference that should affect only the current committed repository, use personal scope:
shipper settings set agentTimeoutMinutes 0 --personalshipper settings set hookTimeoutMinutes 30 --personalRaise agentTimeoutMinutes for stages that need more runtime and hookTimeoutMinutes for hooks or
install steps that need longer. A value of 0 disables the corresponding timeout. See the
settings precedence to choose the appropriate layer.
If the stage exhausts its attempts, read the failure’s workflow branch name and full commit ID
before repairing or resetting. Commits preserved from a timeout remain on that branch after both
all-timeout and mixed timeout/non-timeout exhaustion; the failure report also confirms that
uncommitted work was discarded. The report includes the stage, per-attempt limit, exhausted attempt
count, available session log, and exact supported reset action, such as
shipper reset S#123 --to planned. If it
reports no preserved commit, reset performs ordinary cleanup. If it reports a branch and full
revision, that exact same-stage reset retains the unchanged ref while removing the worktree; the
next stage run continues from it. A moved or missing ref fails safe. In mirror-on mode, a missing
GitHub timeout comment or failed-label projection does not undo local failure or keep the lock; run
shipper issue reconcile --repair to repair that explicit projection debt.
See also
Section titled “See also”PR Preparation Rebase Recovery
Section titled “PR Preparation Rebase Recovery”Symptom
Section titled “Symptom”During shipper implement, a long rebase can report progress such as
rebase advanced past abc1234 resolve generated files (2/6). A failed recovery retains the
established three-attempt summary and adds the original commit and n/m replay position that
remained stuck.
Shipper owns the fetch, rebase, and push steps around delivery. The implementation agent resolves
and stages rebase conflicts when asked, but it does not run git rebase --continue or push the
branch. Each stopped commit receives three agent resolution attempts. When Git’s rebase state
advances to the next commit, that later step receives a fresh three-attempt budget; there is no
overall attempt cap across a progressing finite replay. Shipper-owned continuation and
managed-path-only conflict recovery do not consume agent attempts.
Recovery
Section titled “Recovery”If the agent resolves and stages the conflicts successfully, no user action is needed. Shipper
continues the rebase, reruns delivery preparation against the final branch tip, and proceeds like a
clean shipper implement run. Each agent-assisted advancement line, including its commit and n/m
position, appears in the CLI run log and the desktop board’s stage detail.
If one replay step remains unchanged after three agent invocations, the failure identifies the stuck commit and position. The issue’s local ship run log retains the complete terminal detail even when the published or stored Agent Failure record is shortened. Use that local log as the authoritative diagnostic for the failure.
If the stage is marked failed, use the failed-issue reset and recovery path instead of manually continuing the rebase.
See also
Section titled “See also”Merge-Mode Integration or Publication Failure
Section titled “Merge-Mode Integration or Publication Failure”Symptom
Section titled “Symptom”With baseIntegration: "merge", Shipper reports a merge conflict episode, refuses protected
.shipper/ scratch-history rewriting, or says that merge mode preserves feature history and no
forced update was attempted. With baseIntegration: "rebase", a terminal publication error may
instead include a line suggesting baseIntegration: merge.
Merge mode preserves existing feature commit IDs and permits only ordinary fast-forward publication. Each distinct merge has one three-attempt conflict episode; if it cannot progress, Shipper aborts that merge and restores the pre-integration branch and tracked worktree state. Protected scratch paths cannot be removed by rewriting history in this mode.
The rebase-mode discoverability line appears only when Shipper computes from branch refs that the attempted publication is non-fast-forward. It is omitted for a fast-forward attempt and for merge mode. Shipper does not read rejection wording to make this determination, does not infer that a remote forbids force pushes, and never changes the configured strategy automatically.
Recovery
Section titled “Recovery”Covered issue-aware simple implementation, PR-opening, and review-rework implementation publication calls write
one bodyless local issue-history verification_run record for every pre-push hook invocation whose
start Shipper observes. Each retry is a separate record. No record is written for no-transport
publication, when no executable hook or hook-start observation exists, or for pushes outside those
orchestrated workflows.
Only the hook’s aggregate status colors verification: 0 is green, another status from 1 through
255 is red, and an absent, malformed, or out-of-range status is unobservable. Remote rejection is
separate publication context, so a green hook followed by remote rejection stays green. Ordered
step observations and unavailable, partial, or reported coverage never override that color.
The record measures only time inside the hook wrapper, excluding transport, remote work, evidence
finalization, history persistence, and queue time. Rejected-line and step-mismatch details stop at
the first 20 in observed order while their totals count every occurrence. Hook output is not copied
into the verification record.
When an attempted push remains unsuccessful, read the Publication Failure comment and the publication report. It names checks and statuses only when observed. The combined raw excerpt is at most 4096 UTF-8 bytes, including omission markers; markers indicate omitted output without claiming exact dropped-byte counts.
Shipper appends every failed attempt to one timestamped pre-push-evidence-*.log under
~/.shipper/logs/, including attempts without a hook or with a reused verification receipt.
The report shows Full publication log (complete|incomplete): <path>, with reasons for incomplete
capture, or explicitly states why the log is unavailable. A recovered retry creates no terminal
Publication Failure comment; its evidence remains in the local log. Evidence files follow the
existing ship-log lifecycle without a new retention or recovery guarantee.
Successful recovery displays Recovered after a pre-push rejection; complete evidence: <path>
or Recovered after a pre-push rejection; incomplete evidence: <path> with capture limitations.
A failed-attempt verification record can point to an available complete or incomplete evidence
file; a pointer alone does not establish completeness. Recording remains best effort and cannot
change hook gating, publication, retry, remediation, stage, or process results. If collection,
validation, or persistence fails after a known hook start, Shipper emits one
Failed to record pre-push verification for S#<id> attempt <n>: <reason> warning and does not
invent a record.
Open the evidence file as plain text. Ordered attempt headers give timestamps, push commands, Git output and observed or unavailable Git/hook statuses. Optional hook streams and per-step status reports retain only supplied observations. Missing or truncated streams are disclosed as incomplete; complete hook spools alone do not establish a complete publication log.
Reporting pre-push step statuses
Section titled “Reporting pre-push step statuses”While its wrapper runs a repository’s pre-push hook, Shipper sets
SHIPPER_PRE_PUSH_STATUS_REPORT to an append-only JSONL file. Hooks may best-effort append one
version-1 record for each directly observed step:
{ "schemaVersion": 1, "type": "step", "step": "lint", "startedAt": "2026-08-11T12:00:00Z", "finishedAt": "2026-08-11T12:00:03Z", "exitStatus": 0, "reportedResult": "unobservable", "processes": "unavailable"}Every field is required. schemaVersion is 1, type is "step", and step is a nonblank name.
startedAt and finishedAt are valid timestamp strings. exitStatus is a nonnegative integer or
"unobservable"; reportedResult is "pass", "fail", or "unobservable"; and processes is
"unavailable" or an array of objects with a nonblank process name and a nonnegative integer or
"unobservable" exitStatus. No records means coverage is unavailable. A malformed or
unknown-version record, a report read failure, or an unobservable step exit status makes coverage
partial; otherwise one or more valid records makes it reported.
For example, a POSIX hook can preserve its real gating result like this:
run_check() { step=$1 shift started_at=$(date -u '+%Y-%m-%dT%H:%M:%SZ') if "$@"; then status=0; else status=$?; fi finished_at=$(date -u '+%Y-%m-%dT%H:%M:%SZ') if [ -n "${SHIPPER_PRE_PUSH_STATUS_REPORT-}" ]; then printf '{"schemaVersion":1,"type":"step","step":"%s","startedAt":"%s","finishedAt":"%s","exitStatus":%s,"reportedResult":"unobservable","processes":"unavailable"}\n' \ "$step" "$started_at" "$finished_at" "$status" >>"$SHIPPER_PRE_PUSH_STATUS_REPORT" 2>/dev/null || : fi return "$status"}Reporting must remain best-effort: a report-write failure must not change hook gating. Supply only
statuses the hook actually observed. Do not label unobserved descendants as observed, and do not
derive reportedResult or process exit statuses from human-readable command output. Hooks that do
not adopt this channel remain fully supported and produce evidence with coverage unavailable.
This channel does not claim Shipper traced every descendant of the hook process.
Reusing green pre-push verification
Section titled “Reusing green pre-push verification”Issue-aware simple implementation, PR-opening, and both review-rework implementation publication paths can skip
an executable pre-push hook when the same local issue already has an original actual green
verification_run record for the identical tracked tree. Tracked-tree identity is the only
eligibility key: the earlier run’s revision, workflow stage, and remote-publication outcome never
change it, so a revision-only commit over an unchanged tree stays eligible while any tracked-content
change must verify again unless that new tree has its own qualifying original green run.
Shipper looks for a source independently before every publication attempt and every later
publication call, after that attempt’s tracked contents are final. When one qualifies, the push runs
as git push --no-verify, so the hook never executes and the publication neither acquires nor waits
for repository-scoped verification capacity. The ship run log names the short tracked tree and the
original event:
Reusing green pre-push verification for tree 4f2c81a09b3d from event 0d4a2f1c-9f4e-4c2a-b6c7-1f9e5a7c2b83.Each such attempt appends one bodyless verification_run receipt that points directly at that
original actual run. A receipt is never itself a source, so receipts cannot chain, and a red,
unobservable, or another issue’s run can never authorize a skip. Because no hook ran, a
receipt-backed attempt creates no hook status or verification/queue-wait duration. A failed
receipt-backed push still collects publication evidence with its actual log availability.
Qualification is fail-safe. Any lookup, read, validation, or source-resolution doubt emits one
Could not qualify a reusable pre-push verification for S#<id> tree <short-tree>: <reason> warning
and then runs the hook with its normal success, rejection, capacity, and evidence behavior; a valid
history with no matching run is silent. Receipt recording is best effort in the same way as
executed-verification recording: once a valid source has authorized a skip, a failed receipt append
emits one
Failed to record skipped pre-push verification receipt for S#<id> attempt <n>: <reason> warning and
never turns an otherwise successful publication into a failure. The original run stays eligible for
a later attempt.
Reuse assumes that a repository’s tracked contents capture what its pre-push hook checks. A hook that depends on untracked state — machine-local files, environment values, or network results that are not part of the tree — or that inspects the commits being pushed rather than their contents, such as commit-message linting over the push range, branch-name checks, or signature requirements, can therefore be skipped when it would not actually have passed. A revision-only change keeps the tree identical, so those history-dependent hooks are skipped too. A repository-policy opt-out is the intended escape hatch for that case, but it is not part of this behavior today; there is no setting, no dedicated CLI, Desktop, or GitHub surface, and no promised latency for a receipt-backed publication.
Waiting for repository-scoped verification capacity
Section titled “Waiting for repository-scoped verification capacity”One exclusive slot per canonical repository serializes Shipper-triggered pre-push hook executions
across every checkout and worktree of that repository. Different repositories proceed independently
and do not wait for, block, reclaim, or alter one another’s reservations. Named identities use
~/.shipper/run/verification/named/<owner>/<repo>/capacity.lock; lineage identities use
~/.shipper/run/verification/lineage/<lineage-token>/capacity.lock. Same-repository serialization
covers ordinary clones, the managed clone, moved checkouts, and worktrees.
Only an actual Shipper-managed executable pre-push hook invocation participates, in an issue-aware
implementation, PR-opening, or review-rework implementation publication attempt. Local-only (transport: "none") publication, a repository with no pre-push hook, a hook file that is not executable, a
publication whose hook Shipper skips or that git never invokes, direct git push outside Shipper,
standalone npm test or agent check commands, and hosted continuous integration never acquire or
wait for this capacity.
Capacity is acquired once per hook execution attempt, immediately before that hook starts, and released as soon as it completes or is interrupted. Remote upload, server response, fetch, base integration, transport recovery, and remediation never hold the reservation and are never counted as queue or verification time. A retried publication acquires capacity again for each new hook attempt. No first-in-first-out or other acquisition order is promised; every waiter receives the same timeout and holder visibility.
An uncontended acquisition is silent and creates no wait record. On first encountering a holder, a waiting publication prints one line naming the holder PID plus every repository, issue, and stage value it can recover, and it prints that line again only if the holder changes. Waiting never repeats periodically. After a contended grant the publication prints its total queue time once:
Waiting for repository-scoped Shipper verification capacity for owner/repo; holder PID 40321, repository owner/repo, issue S#412, stage implement.Acquired repository-scoped Shipper verification capacity for owner/repo after 3m 12s in queue.A reservation is reclaimed only when its owner process is proven dead. Age never permits reclamation: a live, indeterminate, malformed, unreadable, or otherwise unverifiable owner is retained no matter how old the file is, and the waiter reports that holder once and continues toward its own timeout.
If Shipper cannot establish canonical repository identity, publication fails before the pre-push hook starts. The diagnostic states that repository identity could not be resolved and no pre-push verification was attempted. Shipper produces no verification evidence and does not fall back to machine-wide or checkout-local capacity.
A leftover former machine-wide reservation at ~/.shipper/run/verification.lock with no live older
owner is silent, harmless, and requires no cleanup. If a strict version-1 record references a
currently live PID, stage output warns with that PID and repository, advises restarting the daemon
and Desktop to end the mixed-version overlap window, and repository-scoped publication proceeds
without waiting or failing.
Each publication invocation may queue for a cumulative 45 minutes of same-repository waits. On
exhaustion the publication ends with the same durable issue and stage treatment as any other
terminal publication failure, reporting
Verification never started because repository-scoped capacity for owner/repo timed out after the cumulative 45-minute queue limit.
together with the last observed holder context. That timeout produces no verification_run record,
no hook-rejection evidence, no in-command requeue, and no agent remediation, because the hook
never started. It still collects a publication log with its actual availability and completeness;
capacity timeout alone leaves the push cause unavailable rather than proving transport failure. A later publication invocation receives a fresh
45-minute limit. Waits in other repositories do not consume this budget.
Every positive wait is recorded separately from active verification as a
verification-queue-wait record with outcome granted or timed-out, and both outcomes feed the
verificationWait shipping phase. See
State transitions for how that phase relates to
verification duration.
A hook granted exclusive Shipper capacity receives SHIPPER_VERIFICATION_SLOT=exclusive in its
environment. Repositories may use that marker to relax concurrency limits they impose only to
protect against overlapping local hooks. In this repository the test-worker precedence is: an
explicit positive whole number written using decimal digits alone in SHIPPER_TEST_MAX_WORKERS
always wins, and no ceiling is imposed on it, so a valid value greater than two is used as
requested; without one, an exclusive Shipper run uses exactly two test workers, while a direct
non-Shipper push exports 2. An empty, malformed (including signed or whitespace-padded),
fractional, zero, or negative value during an exclusive run is rejected with exactly one warning
naming the rejected value and the two workers actually used, and verification then continues
normally, so a worker-setting typo alone never fails a publication. Standalone test runs and hosted
CI never interpret the slot marker.
For other merge or publication conditions, resolve the reported merge conflicts and retry, or
remove protected scratch files without rewriting existing feature commits. Choose
baseIntegration: "merge" explicitly only when its preserved commit IDs, merge commits on the
feature branch, and ordinary-only publication match the repository’s requirements. Otherwise keep
the default rebase strategy and repair the remote branch or branch-protection condition.
For no-PR local delivery, a non-fast-forward or fetch-first rejection while publishing the already-advanced base is a different durable outcome: the remote base diverged and must be reconciled before recovery. Other transport failures are untimed skips. Fix transport and rerun; Shipper recognizes the advanced local base and retries publication idempotently.
See also
Section titled “See also”Review Diff Too Large
Section titled “Review Diff Too Large”Symptom
Section titled “Symptom”shipper pr review or shipper implement reports that the reduced review diff exceeds its
effective byte ceiling. The diagnostic gives the exact reduced byte count, effective ceiling,
supplying settings layer, reviewDiffCeilingBytes, the five largest file contributions with their
additions and deletions, and the exact personal-scope command that can raise the ceiling. It also
states that the setting is editable in the Desktop repository settings dialog. The review or
implementation agent does not run, and the issue enters durable failed state instead of being
selected again by auto-ship.
Shipper applies fixed size policy to complete unified-diff file sections. A section larger than 50,000 bytes is replaced with an explicit changed-file marker containing its path and GitHub’s addition/deletion counts. Selection is based only on section size, not filenames, so the same rule applies to lockfiles, generated schemas, snapshots, vendored bundles, minified assets, and ordinary source files. Shipper measures the resulting reduced diff afterward. Review and implementation rework proceed normally when that reduction brings the artifact within the effective ceiling.
The post-reduction ceiling defaults to 500,000 bytes and is controlled by the finite positive
whole-byte setting reviewDiffCeilingBytes. It has no force flag, skip-to-merge path, unlimited
sentinel, or other bypass. Raising the ceiling causes the complete reduced input to be reviewed; it
does not bypass review.
Recovery
Section titled “Recovery”Choose either equally valid remedy:
- Raise the finite ceiling for this repository with the exact value shown by the diagnostic, for
example
shipper settings set reviewDiffCeilingBytes 750000 --personal, or edit the same setting in the Desktop repository settings dialog. - Shrink or split the change so the post-reduction review diff fits the current ceiling.
Then run shipper recover S#123, followed by shipper pr review S#123. Recovery always stores
shipper:pr-open, so review must run again before Rework or Ready transitions can continue.
In a local posture that stage displays as Change Packaged. Recovery preserves the exact packaged
revision, source branch, stored artifact, and completed review history.
If the stored pull request is closed without merging, first run
shipper issue reconcile --repair; a reported recovery returns it to shipper:rework while
preserving the branch. For a merged, missing, or absent link, use an ordinary implementation-level
reset to an earlier active stage, or adopt the current open pull request when
that is the intended replacement. See the state-machine reset
contract for destructive cleanup rules.
Failed Issues and Rollback Loops
Section titled “Failed Issues and Rollback Loops”Symptom
Section titled “Symptom”An issue is marked shipper:failed, repeatedly rolls back after reject verdicts, stops after too
many transitions, or posts an Agent Failure comment after a stage exhausts its retries without a
verdict.
Shipper treats reject as a rollback event, not an immediate command failure. It can keep advancing
after rollback until the work reaches a terminal point, but the shared transition cap
MAX_TRANSITIONS = 15 prevents infinite loops and marks the issue shipper:failed when exhausted.
No-verdict crashes do not share one universal retry path. Design, plan, implement, and PR
review use the stage scaffold’s three attempts for non-zero agent exits. Groom and review-rework implementation
have no general stage retry budget, and setup or orchestration failures do not inherit that budget.
Specialized merge, remediation, content-validation, GitHub-transport, and integration loops are
separate. The canonical
failure outcome contract determines whether
each exhausted or immediately unsuccessful path may enter durable failed state.
Recovery
Section titled “Recovery”Choose recovery from the canonical
posture-specific recovery table.
If reset cleanup is acceptable, shipper reset S#123 moves to an earlier valid stage and
shipper reset S#123 --to <stage> chooses the target explicitly. A failed issue that still shows or
stores workflow stage label X may target X to preserve X-stage work, but Reset still removes
artifacts after the target. In either no-PR posture it clears the packaged LocalChangeRecord, so
it is not a work-preserving recovery for that artifact.
After a terminal crash, fix the underlying credentials, dependencies, repository state, or other
root cause. If the failed issue carries a pinned local change or an open linked pull request, run
shipper recover S#123. Recovery dispatches from that stored artifact regardless of current
posture, accepts a source branch that advanced beyond the pinned revision, always stores
shipper:pr-open, and preserves completed review rounds and workflow history. A missing source
branch, invalid revision, divergent or rewound branch, closed linked pull request, or absent
artifact makes recovery ineligible and points to Reset. For a closed-unmerged stored pull request,
shipper issue reconcile --repair may first return it to shipper:rework while preserving the
branch. Fixing the cause does not itself clear durable failed state.
For an exhausted design, plan, implement, or PR-review attempt sequence, inspect any workflow branch and full commit ID named in the failure before choosing Reset. A prior timed-out attempt’s commits remain on that branch even when a later attempt failed for another cause. Reset may intentionally discard that retained progress.
Ordinary stage-selection commands never auto-clear durable failure, including failures previously
caused by agent configuration. Missing mirrors, missing or unrecognized workflow labels, accepted
stages whose label did not advance, and transition-cap exhaustion also record durable local
stage_failure history with a named repair. If a design result was already accepted and only
adversarial-note cleanup failed, the accepted transition is preserved and the cleanup diagnostic
is reported instead of replacing completed work with failed state.
Do not recover by manually editing GitHub workflow or failed labels. Shipper mirrors those labels from the authoritative local issue-state store, so manual label edits are overwritten by the next store mirror and do not make the issue eligible for automatic selection.
Blocked issue recovery starts with shipper unblock. Desktop users can perform equivalent
label-state recovery through the app.
An external closure hold is not shipper:failed. Reset does not clear a hold; use its not-planned,
reopen, or confirmed-complete resolution. See the canonical
closure-hold distinction.
See also
Section titled “See also”- Reject is an interior event
- The reset command
- Blocking
- Protocol verdicts
shipper nextshipper shipshipper resetshipper unblock- Desktop reset flow
MCP Tool Loading Issues
Section titled “MCP Tool Loading Issues”Symptom
Section titled “Symptom”An agent does not see expected Shipper MCP tools. For a Shipper-dispatched worker stage this is the expected outcome rather than a fault. It is only a problem when a stage you opted back in still sees no tools, or when a run loads tools where the stage should avoid MCP access.
Shipper-dispatched worker stages — groom, design, the adversarial design review, plan,
implement, and pr_review — default to MCP loading disabled. Assisted
setup (setup) and issue intake (new) default to enabled. One precedence rule decides the rest,
with no stage exception: a one-run flag beats all settings, an explicit commands.<stage>.disableMcp
beats an explicit commands.default.disableMcp, and an explicit value at either scope beats the
shipped default. Shipper logs MCP loading enabled for stage <name>. only when a stage resolves to
enabled; a disabled stage prints no MCP line.
Recovery
Section titled “Recovery”Re-enable one stage with shipper settings set commands.<stage>.disableMcp false, or every stage
with shipper settings set commands.default.disableMcp false. For one run, use --enable-mcp to
force loading or --disable-mcp to prevent it. Read a stage’s effective value with shipper settings get commands.<stage>.disableMcp; the global key reports <unset> because the shipped
default varies by stage.
See also
Section titled “See also”Merge Failure Outcomes and Exit Codes
Section titled “Merge Failure Outcomes and Exit Codes”Symptom
Section titled “Symptom”Automation or an operator needs to distinguish a merge waiting on checks, a failure that will retry, a durable merge condition that returned to PR review, an untimed skip, and an immediate durable failure.
Typed causes—not reason or console text—select four actions plus typed deferrals:
-
Deferred: checks pending (exit
78): GitHub reports checks still running. The issue remains at its current stage without moving retry or the shared repository safety counter. CLI and Desktop name the PR and currently incomplete checks on every deferral; Desktop records aDeferredissue run status in issue details and updates one informational notice with the fixed deadline. Later passing checks permit merge without intervention. -
Will retry (exit
76): Shipper obtained no determination because the merge queue was busy, the local repository was busy, or GitHub failed temporarily. The issue retains its stage without a failure comment so the next dispatch retries the merge. Desktop keeps automatic retry/backoff quiet in issue run details. -
Returned to Rework (exit
77): a merge conflict atomically records the reason and returns the issue to Rework for implementation. -
Returned to normal review (exit
77): failed checks, a blocked merge, a still-behind branch, a branch-update failure, or an unknown determined condition atomically records the reason and returns the issue to PR Open (or Change Packaged). See the state machine’s canonical merge failure taxonomy for the complete list. -
Untimed run skip (exit
78for a targeted Ship or Merge): required approval, temporary Ready-prerequisite lookup failure, dirty local base, and transient publication or failure-handling trouble retain the stage and skip the issue for the current run. They create no deadline or merge-loop count and are safety-neutral. Required approval consumes no retry; an approved later attempt can merge. -
Fail now (exit
1): no usable repository merge method and divergent local-base publication enter durable failed state immediately with setting or reconciliation guidance.
Ready prerequisite failures also exit 1 durably: a missing packaged change under the no-PR
policy, missing mirror under mirror-on, missing PR association, or missing/closed PR prints the
exact reset, PR-open, adopt, or reconcile recovery. A temporary PR lookup failure is the untimed
skip above.
Pending checks have a separate total-elapsed bound configured by
merge.pendingChecksTimeoutMinutes (default 60). The in-memory clock is keyed by PR head SHA and
retains its first timestamp across repeated observations in one CLI process or Desktop application
session. A new commit starts a full new window. A process or application restart also resets the
window. At the exact deadline, Shipper durably reports
CI checks never completed within N minutes for PR #P: CHECKS, marks the issue failed, and
suppresses Retry. Store-only repositories persist the same local report and failed state without
GitHub label or comment writes. A required check that never reports any status is not visible as
pending and is not covered by this bound.
Auto-ship bounds the retry loop per issue. A no-determination failure
waits 30, 60, 120, then 240 seconds between attempts while other issues continue. CLI ends instead
of waiting when only delayed work remains. Attempt five retains the issue’s stage and failed flag,
skips it only for the current invocation or Desktop enabled run, and emits one
merge-retry-exhausted safety occurrence. It never calls durable issue-failure persistence. A
fresh CLI invocation or new Desktop enabled run can select it without recovery.
Determined conditions do not enter a merge cycle. They leave Ready immediately for either Rework or normal review and remain eligible through that ordinary workflow. A successful merge clears the temporary retry count.
Notification and summary causes are concise and long check-name lists are truncated. The ship log and existing PR failure comment retain the full diagnostic. Store-only repositories record the same reporting locally without GitHub writes.
Retry exhaustion counts once toward the current automatic run’s repository safety stop. Pending-check expiry remains durable across later sessions and requires recovery; retry exhaustion does not. Every other stage-retaining merge outcome is safety-neutral.
For shipper ship --auto, a run whose only unresolved outcomes are deferred or will retry exits
0 because those outcomes are not terminal failures. Any returned-to-review or fail-now outcome still
exits 1.
CLI and Desktop share the canonical failure-outcome and repository-safety contract. Three distinct qualifying condition instances engage the stop. An unchanged repeat contributes one strike total. A changed revision or a successful repository advancement before the repeat creates a new instance; advancement does not erase pending strikes.
The six current qualifying identifiers are terminal-stage-failure, merge-retry-exhausted,
pending-checks-exhausted, groom-output-invalid, worktree-setup-contention, and
upstream-merge-wait. Producers
must send one of those classifications or explicit null; omission is rejected rather than
defaulted. Active retry and pending-check loops remain neutral until their specific
ceiling is reached. Concurrent parked not-attemptable Ready pull requests share one
upstream-merge-wait occurrence until every included pull request becomes attemptable, merges, or
changes head. Blocked entry refusal and blocked post-work stop are also explicit neutral
outcomes, and an eligible advancement before a blocked stop advances the repository progress
generation before the neutral blocked outcome is processed.
Desktop’s leftStuck value describes card presentation and retry behavior, not repository safety.
The pending ledger is durable and shared by CLI and Desktop from strike one. A later invocation,
Desktop relaunch, or daemon restart neither forgets strikes nor recounts an unchanged condition. At
the threshold, CLI stops selecting or refilling,
drains work already in flight, abandons parked work, reports the triggering conditions and any
eligible unattempted issue, and exits 1. Desktop leaves desired state enabled and reports
Auto-ship suppressed.
Auto-ship launch retries and recovery-only waiting
Section titled “Auto-ship launch retries and recovery-only waiting”When automatic work fails before its run starts, Auto-ship remains enabled and recovery is
automatic. The Launch retry status reports repository or issue scope, consecutive attempts, the
latest command and classified reason, and either the scheduled next-attempt time or
recovery-only wake conditions. Short waits are 30, 60, 120, and 240 seconds after attempts one
through four; the fifth failure enters healthy recovery-only quiescence. In that posture, unchanged
four-second polls launch nothing. A cooled-scope change or ordinary heartbeat permits
one probe, and another failure increments the visible count without restarting the short schedule.
Inspect the latest run-history or supervision diagnostic error for the underlying checkout, reservation, setup, admission, or dispatch failure. No Resume or clear-retry action is required. Restart and notification dismissal preserve the episode. A successful automatic or manual start, issue ineligibility for issue scope, or disable followed by re-enable applies the documented selective reset rules. These states are separate from pending-check deferral, post-start merge retry, worktree contention, explicit halt, and the repository safety stop.
Recovery
Section titled “Recovery”Handle 75 from shipper ship as the pause sentinel, 76 as a will-retry merge failure, 77 as a
durable merge condition successfully returned to PR review, 78 as a typed deferral (pending checks
or an untimed skip), 79 as exhausted clean GitHub-outage waiting, and 80 as a startup refusal
caused by a running or could-not-determine dispatch holder. Exit 79 retains the workflow stage, skips the
issue for the current run, remains eligible in a later run, and is neutral for the safety stop. Exit
80 attempts no issue and is also neutral for the safety stop. Handle 1 as an immediate durable or
ordinary failure and 130 as interrupted shipper implement check polling.
For pending-check expiry or another durable failure whose packaged
artifact remains recoverable, use shipper recover S#123 or Desktop’s Recover change. Retry
exhaustion needs no recovery; start a later invocation or explicitly enable a new Desktop run after
the transient condition clears. If the source branch is missing or no longer contains the pinned
revision, the linked pull request is no longer open, or no packaged artifact exists, use Reset
instead. Do not edit GitHub labels by hand. Recovery commits locally first; projection failures warn
to run shipper issue reconcile --repair and do not undo recovery.
The ready-issue externally-merged recovery remains an exception: merge-lock contention there is quiet and the next refresh retries it without surfacing a failure.
See also
Section titled “See also”Rework execution or local recovery pending
Section titled “Rework execution or local recovery pending”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 selects review rework, merge rework, or fresh delivery from the current package history, then re-validates and re-delivers the preserved branch and reuses its open pull request.
If Reset committed; local recovery pending appears, the reset already committed. Do not retry reset. The next store access finishes the existing operation once; if access still fails, repair local store access first. Failed Rework recovery retains the current review pin and stage.
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.
CLI-only pending work survives process exit. Run shipper issue reconcile —repair to service it now; repair respects GitHub holds. Plain repair comes before duplicate-risk authorization when sufficient. No new CLI background process is required.