shipper issue reconcile
shipper issue reconcile
Section titled “shipper issue reconcile”Usage: shipper issue reconcile [options]
Check or repair local issue-state and GitHub mirrors
Arguments
Section titled “Arguments”| Argument | Required | Description | Choices |
|---|---|---|---|
| None | - | - | - |
| Long | Short | Value | Default | Description | Choices |
|---|---|---|---|---|---|
| —repair | - | - | false | repair local records, tombstones, and GitHub mirrors | - |
Examples
Section titled “Examples”Check whether local issue-state records and GitHub mirrors have drifted.
shipper issue reconcileRepair local records and GitHub mirrors from the authoritative store.
shipper issue reconcile --repairPartial repair
Section titled “Partial repair”Repair reports each unreadable issue once, preserves its unresolved work, and continues with other eligible issues. The final line plainly identifies a partial outcome. This is an excerpt of a mixed partial repair summary; ellipses stand for unchanged counters:
Partial repair of issue-state replica for owner/repo at <store>. ... unreadable issues 2; unreadable local records retained 1; ... skipped issues (active locks or operator pauses) 2; ...- Unreadable issues counts distinct affected issue work items whose required read or source was unavailable, including pending blocker imports. A shared failed PR counts once for each owner.
- Skipped issues (active locks or operator pauses) counts issues skipped for an active run lock or a deliberate operator pause. Unreadable issues do not increase this count.
- Unreadable local records retained counts retained unreadable store files, not failed GitHub reads. These files are reported separately as unreadable local issue-state records.
Other verified drift can also make a repair partial while all three counts are zero. A normally
returned partial repair exits 0; exit 0 does not prove every issue converged. Resolve the named read
problem and rerun shipper issue reconcile --repair, or address the named lock, pause, or local-record
condition as applicable. Store-only repair does not read blocker comments or complete GitHub-derived
pending blocker imports; those sources remain pending until mirror-on repair can read them.
Closed pull request recovery
Section titled “Closed pull request recovery”For records at shipper:pr-open, shipper:pr-reviewed, shipper:rework, or shipper:ready, reconcile validates the
stored linked pull request. Plain shipper issue reconcile only reports what it finds.
shipper issue reconcile --repair automatically recovers a link only when all of these facts are
positively confirmed, in order:
- GitHub reports the linked pull request closed without merging.
- The issue’s GitHub mirror is open, or the local record has no issue mirror.
- GitHub returns the exact surviving
refs/heads/<headRefName>ref; a prefix match is not enough. - No active Shipper run holds the issue lock, and the stage and association still match when the recovery transaction commits.
Recovery clears the stale pull-request association, moves the issue backwards to
shipper:rework, and clears shipper:failed if present. It preserves
the implementation branch, so the next shipper next runs implementation against the surviving
work and prepares delivery again.
The per-issue output distinguishes every decision:
[closed-unmerged:recoverable]means check mode would recover with--repair;[closed-unmerged:recovered]confirms that repair cleared the association and moved the stage.[not-found]preserves the association and stage because an absent result is not proof that the work was abandoned. Verify or re-establish the PR before consideringshipper reset S#42 --to rework.[lookup-failed:pull-request],[lookup-failed:issue-mirror], or[lookup-failed:branch]preserves the association and stage. Retry after GitHub access succeeds.[branch-gone]preserves the association and stage. Restore the exact head branch before retrying or consideringshipper reset S#42 --to rework.[closed-issue-mirror]preserves the association and stage. Resolve or reopen the issue mirror before retrying; do not reset while the issue-side closure remains unresolved.
The decision tree is identical in store-only and mirror-on. Store-only performs only the
candidate-specific PR, issue-mirror, and exact-ref reads needed for this decision and remains a
general issue-side mirror no-op: it does not backfill, edit, close, or create issue mirrors.
Workflow comment convergence
Section titled “Workflow comment convergence”New workflow event comments carry an invisible, versioned identity tied to their local workflow comment group. It does not change the rendered Markdown. Fresh comment intent is committed atomically with the first comment-producing event. Delivery records the attempt before one nonretrying POST, with no comment-list read. Workflow comments, notes, and PR flake comments share this delivery worker; PR flake comments use their recorded PR parent and rendered PR body.
Routine convergence handles outstanding delivery only. Successfully delivered groups and completed
retirements make no comment requests when local intent is unchanged. Remote content drift and
unknown duplicate discovery or cleanup belong to explicit shipper issue reconcile --repair.
Local Blocked-slot changes use the recorded canonical comment ID and preserve it across owner
transfer when ownership is proven. Blocked retirement deletes only proven owned IDs, excluding the live canonical ID.
Saved-comment work reads that ID directly, without searching issue history. Replacement requires a
direct comment 404 plus successful matching parent issue/PR access with the same credentials.
Listing absence or a bare 404 cannot authorize replacement. A failed or inconclusive lookup retains
local workflow progress and pending delivery; the warning names the issue/comment and recommends
retry or shipper issue reconcile --repair.
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.
Legacy delivered ordinary immutable comments remain delivered. Blocked ownership requires a currently validated marker for a known local Blocked group or explicit POST provenance for that same comment ID. Heading, body equality, account, saved ID, or hash alone never establishes ownership. Marker acceptance records safe recovery but alone never grants later mutation rights if the marker is gone. Unmarked legacy linkages remain ambiguous and may require manual removal; human Blocked comments, including on no-history unblocked mirrors, are preserved. Store advancement to V14 preserves event bytes and old uncertainty without inventing readiness or provenance. Reading a canonical receipt does not clear retained cleanup debt; explicit repair still verifies owned stale and duplicate copies.
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.
All supported comment families and issue projections permit six failed passes, with 1, 2, 4, 8 and 16-second backoff. Holds consume no failure; definitive rejection stops immediately. Stopped work remains undelivered with its cause and safest action. Unrelated edits cannot bypass a hold or retry stop.
Notes, CLI/desktop priority, and explicit synchronization save locally during another live stage. Saved locally; GitHub delivery pending while a stage owns this issue. Delivery resumes at the next ordinary synchronization after the stage finishes. Lock release performs no GitHub work and waits on no deadline. Due work resumes at the next ordinary issue synchronization, including stage completion, a touching command or repair. An existing daemon or desktop runtime can deliver unattended with Auto Ship disabled. Future retry deadlines remain scheduled; current store-only policy cancels automatic requests. Completion clears only its own generation and preserves newer requests and independent error debt. Recovery during a live stage is deferred without consuming its authorization token.
For ordinary non-Blocked groups, repair selects one canonical workflow comment per local group in deterministic order: the live stored GitHub comment ID, the oldest exact identity match, then an untracked comment whose complete body is byte-for-byte equal to the fitted legacy body. Exact legacy-body adoption is repair-only and never applies to Blocked text. Repair stamps an adopted legacy comment with the invisible identity, so GitHub may show a one-time “edited” badge even though the rendered content is unchanged. It also removes exact duplicates and stale copies attributable to that group.
There is no identity backfill pass. A tracked historical ordinary non-Blocked workflow comment without the identity is current when its fitted markerless body matches, and repair leaves it byte-for-byte unchanged. Marker absence alone is never drift. If the body has genuine drift and must be rewritten anyway, the replacement receives the identity.
After a canonical comment exists, repair reports an untracked comment as a workflow comment
leftover only when its first line is byte-for-byte equal to that same group’s expected first line
and its complete body is unequal. The leftover is never adopted, edited, or deleted. Unowned Blocked text is also reserved from ordinary body-based adoption or cleanup. It remains an
incomplete action on every repair run, including otherwise write-free runs, until the operator
removes the GitHub comment manually and reruns shipper issue reconcile --repair.
Issue-note identities and blocked-comment convergence remain separate from workflow event comment
identity and cannot claim one another’s comments. In store-only, reconcile performs no GitHub
issue-comment read, post, edit, or delete.
Exit Codes
Section titled “Exit Codes”| Code | When |
|---|---|
| 0 | No real drift is detected, only informational differences (updated-at or a retained workflow comment edit accepted but not applied) are found, or repair returns normally, including partial repairs (exit 0 does not prove every issue converged). |
| 1 | Real drift is detected in check mode, or reconciliation fails. |
Constraints
Section titled “Constraints”- 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. Legacy reviewed labels normalize to PR Open for this check. 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 freezes one situation for the run: a current held-round pin selects review rework; otherwise a newer roundless merge-conflict entry selects merge rework; every other case is a roundless continuation of the retained packaged change with no correction delta, new reset instruction, accepted plan, or accepted design. Review rework consumes the pinned round, outstanding local findings, and supplied unresolved external threads, requires one nonblank fix or reasoned won’t-fix disposition per target, and durably delivers successful replies with configured best-effort resolution. An unreadable pin or dispositions still missing after one correction fail without advancing. Merge rework requires no review replies. Every situation reuses the preserved branch and open pull request without editing its metadata. Every successful explicit packaged preserving reset and eligible active packaged-change recovery restarts the five-round review allowance, including repeated Rework resets, unchanged revisions, and exhausted allowances. Package identity, full review history, findings, dispositions, and Rework context are preserved; renewal resolves no findings and grants no approval. Eligibility, destinations, and independent retry limits are unchanged. Count completed approved, addressed, superseded, and zero-finding reviews since the latest established renewal in append order; attempts without a completed review consume nothing. The first five completed reviews may reject into Rework; a sixth rejection enters durable Failed over Rework and retains its round, while a sixth approval may advance under existing approval rules. Automatic transitions and rejections, ordinary retries, restart/resume, grooming recovery, superseded-branch restoration, refused actions, and cancellation do not renew. Renewal belongs to the local logical commit even when projection repair is pending; repair grants no second renewal. A committed pending action reports its operation ID and says not to repeat it: store access finishes the existing operation. Recovery usesRecovery committed; local recovery pendingfor this outcome. For pre-release retained packages, affirmative historical preserving reset or packaged-recovery evidence, including recorded Reworkoperator-resethistory, establishes the latest renewal. Without such evidence the existing full count remains. Upgrade alone leaves Failed intact. Current-allowance progress can be 0 of 5 or 6 of 5; historical round numbers and order never change and may exceed six. Replacement package identities start independently. - Locally reconstructable older issue-state replicas migrate automatically on first issue-state access without running
--repair. --repairretains first-reconciliation blocked-state imports for fresh or recreated stores and can create missing open GitHub mirrors.- Post-commit GitHub write failures are retained as
Mirror pending (repairable)orMirror pending (not currently repairable)while the local workflow command remains successful; rerunning that command is not required and there is no dismissal or acknowledgement path. --repairconverges current content, the complete managed-label set, identity-owned workflow and note comments, and tombstone closure. It clears Mirror pending only after every applicable surface verifies clean; partial repair retains the condition.- API/read/mutation/verification failures are repairable. Stable verified drift after an accepted mutation, including accepted-but-not-applied workflow comments, is not currently repairable and remains visible without repeated ineffective writes.
- In
mirror-on, each null mirror is reported as Mirror pending (repairable) when repair will reconnect an origin, create a new mirror, or attach an exact content match, and as Mirror pending (not currently repairable) with a named obstacle otherwise. - In
mirror-on, an existing but closed issue mirror is reported asclosed GitHub mirror (the GitHub mirror exists but is closed; repair direction: reconcile closure by lifecycle state). - Either the missing-mirror or closed-mirror condition is real drift and exits 1 in check mode.
- Repair completes Ready records only through the existing confirmed externally merged PR path. New through Rework records instead enter an operator closure hold with preserved active state and stable or retryable actor/closer evidence.
- A held closed mirror remains operator-actionable drift, so check mode continues to exit 1 until the hold is resolved; repair does not silently clear or retire it.
- A later external reopen restores projected labels and resumes the saved state. Held records require an explicit not-planned, reopen, or token-confirmed complete decision; there is no default.
- In
mirror-on,--checksurfaces open local-only records as pending mirror candidates, including records that remember a read-only adoption origin. - In
mirror-on,--repairreconnects a remembered same-repository issue origin only when it is open, unclaimed, and carries noshipper:label. The local store then converges onto that issue. PR-only, foreign, and origin-free records receive new mirrors; closed, missing, labeled, or claimed origins remain Mirror pending (not currently repairable) and receive no replacement. - Repair reports reconnected mirrors, created mirrors, and records left unmirrored separately. Reconnect assignment follows the adoption assignment setting; assignment failure is a warning and does not detach the mirror.
- Local-only tombstones with no GitHub mirror are preserved as local history and are not retroactively created as closed GitHub issues.
- In
store-only, check and repair are issue-side GitHub mutation no-ops. Closed-PR recovery may perform candidate-specific PR, issue-mirror, and exact-ref reads, but it does not report general mirror drift, update mirrors, close mirrors, or create mirrors. - After mirroring is re-enabled,
--repairrealigns previously frozen mirrors from the local store and closes open GitHub mirrors for tombstoned local records. - Numeric snapshots at
issues/<GitHub-number>.jsonor_pending-blocker-imports/legacy/<GitHub-number>.jsonrefuse the entire store before writes, even with malformed JSON or metadata. The refusal lists the store and all old records and leaves existing local issue state unchanged. This Shipper version no longer migrates these snapshots: use a Shipper release from before this change to migrate the store before retrying this version. No specific release is promised. --repairsupports local-ID V3 blocker imports requiring GitHub data, initialization/recreation, and ordinary current-store repair. Locally reconstructable local-ID upgrades remain automatic.--repairrewrites current metadata when needed and preserves the next-local-ID floor from metadata, snapshots, tombstones, and reserved pending legacy identities.- Otherwise-valid event records with an unknown
kindare skipped with one newer-build warning per event-store path and process. Read, known writes, and reconcile continue; a build that does not recognize the event leaves its marker-shaped GitHub comments unclassified and untouched. - Unknown-kind tolerance does not apply to malformed JSON or envelopes, unknown entry types, invalid projection records, or unsupported per-record schema versions; those remain fatal.
- Stores written by a newer Shipper are not repaired or rewritten; upgrade Shipper and do not delete the store because deletion discards the reserved next-local-ID floor.
- After a record is in the V7 store, local workflow/title/body/priority/failed/blocked/closure-hold state, typed blockers, and workflow event history are authoritative and repair store -> GitHub.
- Local workflow history is forward-only from the point the local event log became authoritative; pre-activation GitHub workflow comments are not backfilled into history, imported into the local event log, or automatically cleaned up.
- The complete managed label set is the seven workflow labels plus
shipper:blocked,shipper:failed,shipper:priority-high, andshipper:priority-low. Manual GitHub edits to any of these labels are not authoritative; repair restores the exact set from the local store. - Check mode reports the difference as
managed label-set drift, for examplemanaged label-set drift (extra: shipper:new; missing: none; repair direction: store -> GitHub). Repair output names the number of issues underManaged label-set drift repaired. - Ordinary labels and unknown labels with a
shipper:prefix are outside the managed set, so reconcile neither reports nor changes them. The obsoleteshipper:lockedlabel is also ignored and preserved. - GitHub blocked labels/comments are imported during first reconciliation of a fresh or recreated store, and for supported local-ID V3 sources. Successfully imported current records remain locally authoritative; separately recorded unfinished blocker imports retry until complete, even after active metadata is reconciled. Partial repair preserves pending GitHub blocked evidence while successfully repaired peer records remain usable.
- When required blocker information is unreadable, repair names the affected issue and directs the operator to resolve the read failure and rerun
shipper issue reconcile --repair. A readable## Blockedcomment missingBlocked by #N.instead requires correcting the comment to include the actual blocker number(s), then rerunning repair; its import stays pending until the corrected information is readable. - In
store-only, local-ID V3 blocker imports with stored blocker lists complete locally. Imports requiring GitHub evidence and their identity reservations remain pending until a latermirror-onrepair; store-only repair does not read or change blocker comments. - Missing, stale, failed, or unverified body-drifted workflow comment projections are repaired from the local workflow event log.
- Reconcile independently reads an existing workflow comment after editing it. If GitHub accepts the edit but the durable body still differs, the result remains
Mirror pending (not currently repairable)until a later convergence verifies the expected body. - The exact retained expected/observed body pair is informational in check mode and exits 0.
--repairreports it under a separateaccepted but not appliedcount and exits successfully without retrying the same ineffective edit; any change to either body makes the projection repairable again. - In
mirror-on, live note projections use their exact versioned ownership marker: repair adopts the oldest untracked canonical match or recreates a missing comment, restores the fitted## Shipper Notebody, and removes every duplicate. Retraction cleanup deletes both the tracked comment and all canonically marked copies. - Marker lookalikes, headings, body equality, embedded or malformed markers, and markers with trailing content are never claimed as note mirrors. In
store-only, existing note mirrors remain frozen; a later mirror-on repair converges them from local note/retraction history. - GitHub
shipper:lockedis obsolete, ignored and preserved during repair, and does not itself cause refusal. Operators may delete it manually; current lock state is local only. --repairkeeps structural/local mutations excluded while another Shipper run owns the issue. Mirror synchronization is durably queued and reports local success with pending synchronization; it resumes at the next ordinary issue synchronization after the stage finishes. Required structural repair remains explicitly incomplete until the stage finishes. No force-unlock is needed for mirror synchronization.- A repair running inside the issue lock context it owns does not skip itself. Dead-owner, corrupt, unreadable, and process-less records are stale and do not cause a skip; live or indeterminate owners are honored indefinitely.
- Check mode does not inspect or annotate local issue locks; its drift report and exit status are unchanged.
- The command is manual and repo-wide by default.
- Updated-at-only timestamp differences are displayed as informational notes and do not affect the exit code.
- Troubleshooting: local issue-state content or mirror drift