Skip to content

shipper issue reconcile

Usage: shipper issue reconcile [options]

Check or repair local issue-state and GitHub mirrors

ArgumentRequiredDescriptionChoices
None---
LongShortValueDefaultDescriptionChoices
—repair--falserepair local records, tombstones, and GitHub mirrors-

Check whether local issue-state records and GitHub mirrors have drifted.

Terminal window
shipper issue reconcile

Repair local records and GitHub mirrors from the authoritative store.

Terminal window
shipper issue reconcile --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.

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:

  1. GitHub reports the linked pull request closed without merging.
  2. The issue’s GitHub mirror is open, or the local record has no issue mirror.
  3. GitHub returns the exact surviving refs/heads/<headRefName> ref; a prefix match is not enough.
  4. 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 considering shipper 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 considering shipper 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.

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:

Terminal window
shipper issue recover-comment S#42 --group <id> --attempt <token> --retry --accept-duplicate-risk

The 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.

CodeWhen
0No 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).
1Real drift is detected in check mode, or reconciliation fails.
  • 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 uses Recovery committed; local recovery pending for this outcome. For pre-release retained packages, affirmative historical preserving reset or packaged-recovery evidence, including recorded Rework operator-reset history, establishes the latest renewal. Without such evidence the existing full count remains. Upgrade alone leaves Failed intact. Current-allowance progress can be 0 of 5 or 6 of 5; historical round numbers and order never change and may exceed six. Replacement package identities start independently.
  • Locally reconstructable older issue-state replicas migrate automatically on first issue-state access without running --repair.
  • --repair retains 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) or Mirror 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.
  • --repair converges 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 as closed 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, --check surfaces open local-only records as pending mirror candidates, including records that remember a read-only adoption origin.
  • In mirror-on, --repair reconnects a remembered same-repository issue origin only when it is open, unclaimed, and carries no shipper: 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, --repair realigns previously frozen mirrors from the local store and closes open GitHub mirrors for tombstoned local records.
  • Numeric snapshots at issues/<GitHub-number>.json or _pending-blocker-imports/legacy/<GitHub-number>.json refuse 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.
  • --repair supports local-ID V3 blocker imports requiring GitHub data, initialization/recreation, and ordinary current-store repair. Locally reconstructable local-ID upgrades remain automatic.
  • --repair rewrites 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 kind are 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, and shipper: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 example managed label-set drift (extra: shipper:new; missing: none; repair direction: store -> GitHub). Repair output names the number of issues under Managed 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 obsolete shipper:locked label 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 ## Blocked comment missing Blocked 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 later mirror-on repair; 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. --repair reports it under a separate accepted but not applied count 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 Note body, 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:locked is obsolete, ignored and preserved during repair, and does not itself cause refusal. Operators may delete it manually; current lock state is local only.
  • --repair keeps 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