Skip to content

shipper merge

Usage: shipper merge [options] [target]

Run the merge queue for Ready changes

ArgumentRequiredDescriptionChoices
[target]noReady Shipper issue, PR number, or URL to merge-
LongShortValueDefaultDescriptionChoices
—interval-60polling interval in seconds-
—once--falseprocess the queue once and exit-
—dry-run--falseprint actions without executing-
—repo-<owner/repo>-repository (default: inferred from cwd)-

Visit every initially eligible Ready item once.

Terminal window
shipper merge --once

Preview the complete Ready pass without changes.

Terminal window
shipper merge --once --dry-run

Preview merging a specific issue.

Terminal window
shipper merge S#42 --dry-run

An untargeted —once run captures eligible Ready issues in queue order before processing any of them. It visits each captured issue once, including local changes when pull requests are disabled. New Ready issues wait for a later pass. A captured issue that is no longer Ready or is blocked at its turn is skipped; a branch made behind by an earlier merge remains eligible for normal branch updating. Item merges, routes, deferrals, skips, and failures do not stop later items. An unreadable queue interrupts the pass and leaves the remaining captured issues listed as unprocessed.

The final report lists each visited identity and outcome, initially excluded Ready records as skipped outside the eligible pass set, an interruption when present, and totals for merged, routed, deferred, failed, skipped, and unprocessed. Failure or interruption exits 1; otherwise any deferral exits 78; otherwise the complete pass exits 0. In an untargeted dry run, projected outcomes say would merge, would route, would defer, or would fail; actual inspection errors remain actual, and the report states that nothing changed.

Before any branch update or merge, Shipper reads one GitHub capability snapshot containing the repository’s rebase, squash, and merge-commit permissions plus GitHub’s computed rebaseability for the pull request. It selects the first available method in this fixed order: rebase, then squash, then merge commit. Rebase remains preferred only when the repository permits it and GitHub reports that the pull request can be rebased. Shipper never infers method availability from failure text.

Typed causes select the action. A required approval retains the issue at Ready as an untimed run skip and consumes no retry attempt; a later approved attempt can merge. If no repository merge method is usable, the issue fails immediately with guidance to enable rebase, squash, or merge commits. Merge conflicts return to Rework for implementation. Failed checks, branch restrictions, behind/update failures, and determined unknown failures return to PR Open for review.

For a behind branch, the update method follows the selected merge method. A rebase merge first rebases the branch onto its base. A squash or merge-commit merge instead merges the base into the branch without rewriting branch history. If the bounded rebase update is rejected, Shipper excludes rebase once inside the same merge attempt, performs the non-rewriting update, and uses the first available non-rebase method. This internal resolution does not consume merge-retry budget.

--dry-run performs the same read-only capability resolution. Its output names the effective method, why it was selected, how a behind branch would be updated, and the post-merge branch cleanup that would follow. It does not update or merge the pull request.

After a pull request is confirmed merged, Shipper always clears its stored pull-request association and completes an active local issue record, even if closing the GitHub issue fails or the mirror was already closed. An existing completed or not-planned local outcome is preserved. Shipper does not close an already-closed GitHub issue again or reopen it. Mirror-close trouble produces one informational message and never fails, demotes, blocks, or retries the successful merge.

After a Shipper-initiated merge is confirmed, Shipper separately attempts to delete a same-repository remote head branch. Deletion is best-effort: a refusal is logged as a warning and does not change the successful merge outcome. A cross-repository or unavailable head repository is skipped. If Shipper instead observes that the pull request was merged outside Shipper, it performs neither a merge nor remote deletion and retains the guarded local branch cleanup.

With effective pullRequests: "none", Shipper validates the recorded local change’s pinned source revision and target before preparing integration. If the target branch is checked out in any worktree, Shipper advances it with git merge --ff-only in that checkout; if it is checked out nowhere, Shipper advances it with a guarded update-ref that verifies the expected old revision. A stale source revision, a target that changed during preparation, or a non-fast-forward result is refused instead of silently changing the recorded contract. Uncommitted changes in the target’s checkout defer the merge and report the worktree path.

After the target advances, effective transport selects delivery. With transport: "origin", Shipper publishes the advanced target branch to origin; with transport: "none", it performs no fetch or push and the merge remains local. Shipper then completes the local issue and cleans up the recorded source branch. A non-fast-forward or fetch-first publication rejection fails durably because the remote base has diverged and requires reconciliation. Other transport failures are untimed run skips; the next attempt idempotently republishes the already-advanced base.

Ready is not sufficient by itself. Under the no-PR policy, the issue must retain its packaged local change. Under the PR policy, it must retain an open linked PR (or an externally merged PR that can be completed). Mirror-on also requires a GitHub mirror. Missing package, mirror, association, or PR records fail durably once and print the exact reset, adopt, PR-open, or reconcile recovery. A temporary linked-PR lookup error is an untimed run skip instead.

CodeWhen
0A complete one-shot pass has no failed or deferred items, including an empty pass; targeted and polling behavior retains its existing status rules.
1Validation or lock acquisition fails, or any one-shot item fails or queue-wide discovery or processing interrupts the pass.
78A targeted or one-shot merge returns a typed deferral, including pending checks, required approval, a temporary prerequisite lookup, or a transient local publication failure.
  • —interval must be a positive integer
  • On the PR-enabled path, reported pending checks defer the merge with the PR and check names visible. Targeted and one-shot commands exit 78; a long-running merge process rechecks no more often than every 30 seconds and otherwise follows its —interval cadence. The SHA-keyed in-session bound is merge.pendingChecksTimeoutMinutes (default 60), and a new SHA or process restart resets it.
  • Only checks-pending creates or retains a deadline. Required approval, temporary linked-PR lookup failures, dirty local bases, and transient publication failures are untimed run skips.
  • On the PR-enabled path, the configured pending-check deadline reports that CI checks never completed and gives shipper recover S#ID and Desktop Recover change as recovery actions. Store-only reporting remains local. For a PR without a Shipper-accepted branch update, a required check that never reports a status is not visible as pending and is outside this bound. After an accepted update, Shipper waits up to 60 seconds for a different PR head to be published. If publication is delayed, the issue stays Ready and the next merge queue observation retries without another branch update. When the old head reported checks and checks gate merging, the published updated SHA gets a separate full merge.pendingChecksTimeoutMinutes registration period starting at its first known-empty check observation. If checks never register for that SHA, the issue stays Ready with a revision-specific deferral and the next queue observation rechecks after CI publishes or is corrected. This differs from exhaustion of checks that registered but remained pending, which follows the ordinary issue-failure path.
  • On the PR-enabled path with merge.requirePassingChecks=true, reported failed checks block merging. Pending checks reported by GitHub defer a blocked merge even when this setting is false. If GitHub still reports no checks after a short startup grace, Shipper treats that as an empty check set and the actual merge logs: “No CI checks configured; merging without check verification.”
  • A ready issue whose linked PR was already merged outside Shipper is completed without another merge, push, PR mutation, comment, or remote-branch deletion. Closed-unmerged PRs require reset; missing PRs require re-linking. See Merge behavior.
  • Troubleshooting: adopted PR closed or superseded