---
summary: "Retained merge outcomes, investigated recovery, and ownership-checked completion"
read_when:
  - Recovering a failed, accepted, or uncertain PR merge or maintaining its cleanup lifecycle
title: "PR merge recovery"
---

# PR merge recovery

Investigate retained outcomes before acting. A failed command, open PR, or absent process does not prove that a remote write did not execute. For fresh admission rules, see [PR merge admission](/reference/scripts/merge-admission).

- `merge-run` owns remote dispatch separately from the process lock. Before any merge/auto/queue request it records the exact repository identity, PR, main target, prepared head, observed main, method, route, and attempt in `refs/openclaw/pr-merge-outcomes/<PR>`. Private Git commits retain the required objects across worktree removal and GC. Do not delete or push these refs; process-lock recovery never clears them.
- A failed request can already have merged. After the reported exact process-lock recovery, repeat `scripts/pr merge-run <PR>` only for reconciliation. `OPEN`, a different head, reverted/partially applied content, elapsed time, or an absent process never proves non-execution. There is no automatic clear/retry override. Inspect the PR timeline, authoritative main history, and `git show refs/openclaw/pr-merge-outcomes/<PR>:outcome.json`; unresolved uncertainty requires operator action outside this automatic path. Keep the record for that investigation. If an older wrapper left `.local/merge-output.log` without an outcome record, even an empty capture blocks a fresh dispatch; preserve it and reconcile the earlier request manually.
- An explicit land/merge/ship request supplies standing operator authorization for investigated recovery on the same prepared head and merge method, including a newly reviewed/prepared replacement head that repairs the same authorized scope. Do not ask for renewed chat approval solely because main moved or a request failed. Investigate each retained outcome before deciding on a bounded recovery attempt; standing authorization does not establish non-execution or permit blind resubmission of accepted, pending, or unresolved requests.
- To repair an accepted or uncertain non-queue auto request, use `merge-recover <PR> <OUTCOME_OID> --confirmed-operator-recovery --cancel-auto` before replacing the remote head. The exact retained intent and matching open PR/head/base are required; a present request must also match the retained method. Cancellation is recorded before dispatch, preserves the original acknowledgment state, captures and ancestry, and reconciles a concurrent merge. Its two stability rereads may change only `mergeable` and `mergeStateStatus`, which do not authorize cancellation; every identity, head, main, queue, request, and policy fact stays pinned. Ordinary merge admission and other recovery comparisons remain unchanged. An already absent request is recorded as retired without a cancellation mutation; absence does not prove that the earlier submission never executed. This investigated operator decision uses standing land authority, not renewed approval or a replacement PR. An uncertain cancellation is observation-only on retry. Only confirmed retirement admits ordinary recovery on the retained or explicitly reviewed/prepared replacement head, with completed gates. Queue/admin cancellation is unsupported. Never clear outcome refs or edit receipts by hand.
- If a collaborator pushes after an accepted auto request, GitHub keeps auto-merge armed because the head precondition applies only at submission and can merge the pushed head. Reconciliation refuses with "PR identity/head/base drift" and prints the exact recovery command. After inspecting the PR timeline and main history, use `scripts/pr merge-recover <PR> <OUTCOME_OID> --confirmed-operator-recovery --merged-head <SHA>`: it requires the exact current retained accepted non-queue auto intent without any cancellation record, a MERGED PR with retained identity/base, an explicitly selected head equal to the authoritative merged head and strictly descending from the retained head, and a landed commit reachable from authoritative main whose tree reconstructs under the retained method; squash receipts require a single-parent landed commit. One successor through the existing CAS ancestry records `phase: merged`, `landed`, and `headDrift {actor, outcome, mergedHead}`; `head` stays the prepared intent head, and the merged head object is retained as a parent. It never dispatches, cancels, re-arms, or clears refs. Identity/base/method drift, non-descendant or unselected heads, OPEN/CLOSED PRs, unaccepted intents, retired or uncertain-retirement requests, queue/immediate/admin routes, stale OIDs, and unreachable or mismatched landed commits remain refused. `merge-run` then reconciles the receipt and `merge-complete` finishes after ownership-checked cleanup, with the completion comment naming both heads.
- After that investigation, ordinary recovery uses `scripts/pr merge-recover <PR> <OUTCOME_OID> --confirmed-operator-recovery` under the standing authorization above or explicit operator authorization for one new attempt. This is an operator decision, never proof that the prior request did not execute. It requires the exact current retained intent with either no recorded acceptance or confirmed auto cancellation, the same prepared head and merge method, fresh ordinary admission, and an immediate route for the new attempt. For a newly reviewed/prepared replacement head within the existing landing scope, explicitly select and append `--replacement-head <SHA>` with that exact full lowercase 40-character SHA; standing landing authority covers that repair without another chat approval. A scope or merge-method change still needs its own authorization. Never infer the selected head from PR/main/prep state. Replacement requires matching PR review stamps and prepare context, prepared-tree/gate bindings, completed CI proof, and a completed ClawSweeper review under the canonical freshness policy. Local evidence must remain unchanged through admission. Without the argument, changed-head recovery still fails. Recovery never rebases or bypasses CI, contributor-ancestry, branch, or owner gates. The successor-intent CAS consumes the exact outcome OID, retains the old outcome and objects as ancestry, and records the operator plus the explicit replacement head when supplied; each dispatch has its own capture. Replacement intent also snapshots existing regular capture files in its private Git tree under their original basenames, preserving the bytes through successful worktree cleanup and GC; symlink captures fail closed. Reusing the old outcome OID fails. Never infer authorization from an error string, an OPEN read, or a routine retry, and never clear the outcome ref.
- A confirmed-cancelled auto squash may recover an explicitly selected reviewed head, including the unchanged retained head, with `merge-recover <PR> <OUTCOME_OID> --confirmed-operator-recovery --replacement-head <SHA> --admin-evidence <path> --confirmed-operator-admin`. Current selected-head review, exact-head `github_pending` preparation, and all live prior-CI admin checks are required. The successor retains the original intent, cancellation, captures, and explicit replacement through the existing CAS ancestry; it never invents provider-rejection or pre-dispatch-refusal evidence. CI failure attribution must bind the selected head and current attempt. Explicit `--replacement-head` selection is still required for a retired-auto admin transition even when the SHA is unchanged; the no-argument rejected-admin recovery route stays separate. Unconfirmed retirement, renewed auto/queue requests, stale outcome/head/artifact bindings, and unrelated admin routes remain refused.
- Exception to the unaccepted-intent requirement: accepted immediate REST intents with a retained UUID, `asyncMerge.status: "failed"`, and null SHA may use the same explicit `merge-recover` command on the unchanged head and method. Re-read the authoritative async result before fresh ordinary OPEN-head admission; retain `recovery.asyncFailure` and the exact prior outcome through successor CAS ancestry. Pending, enqueued, missing/expired, SHA-bearing, or changed results remain refused; never clear refs or automatically resubmit.
- Explicit same-head prior-CI admin recovery may pair `merge-recover <PR> <OUTCOME_OID> --confirmed-operator-recovery` with `--admin-evidence <path> --confirmed-operator-admin` only for the complete known GitHub REST HTTP 405 base-modified rejection and matching `gh` diagnostic. The request was sent; `recovery.providerRejection` records its qualified rejection separately from local pre-dispatch refusals. Preserve the exact original regular capture and any previously qualified captures. The successor CAS retains those bytes and the prior intent, including through acceptance, completion, worktree cleanup, and GC. Fresh review, security, current admin authority, evidence integrity, merge-tree, and exact-head checks remain required; the same-head `github_pending` stamp is never rewritten as success. Unknown extra captures, changed or symlink captures, other 405s, timeouts, 5xx/mixed/truncated responses, accepted intents, other admin routes, and head replacement remain refused. Each further recovery requires its new outcome OID and explicit confirmation; never infer authority from an OPEN read or retry automatically.
- An unaccepted prior-CI REST admin intent on a retired head may transition to a fresh current-head auto request only with `merge-recover <PR> <OUTCOME_OID> --confirmed-operator-recovery --replacement-head <SHA> --auto-merge`. The explicit replacement must differ from the retained admin head. Its exact reviewed/prepared `github_pending` head must still be OPEN, `MERGEABLE/BLOCKED` or `MERGEABLE/BEHIND`, free of an existing auto/queue request, and pass the normal required-check, ClawSweeper, security, and owner gates. This route does not use `--admin-evidence`: the old exact-SHA REST request remains uncertain but is fenced from the different live head, while `recovery.staleHeadRetirement` retains its intent ancestry and every regular capture without interpreting its bytes; an empty capture is valid. The successor route is `auto`, never admin or immediate. Same-head, accepted, stale-OID, changed-head, missing/symlink/extra-capture, admission-time capture mutation, route/method, lifecycle, gate, and authority drift remain refused.
- An inspected Octopool refusal for a retained, unaccepted auto squash intent may use `merge-recover <PR> <OUTCOME_OID> --confirmed-operator-recovery --pre-dispatch-refusal <evidence-directory>` with an optional explicitly selected, reviewed/prepared replacement head under the landing authority above. Preserve the sole original `merge-output.<attempt>.log` and `qualification.json` in that directory. Qualification pins `outcome`, the capture Git blob OID as `capture`, and `inspected: true`; it is an operator attestation, never an automatic retry inferred from an error. For `kind: "octopool-0.6.10-auto-refusal"`, include the inspected `version`, `sourceRevision`, `parserSha256`, and exact `args` array: the helper accepts only the audited 0.6.10 parser that rejected `--auto` before dispatch and its complete refusal. `kind: "octopool-0.7.1-missing-subject-refusal"` uses those same fields, pinned to version `0.7.1`, source `7ab9b348c99a7be4fdc82c75cb06ebce44e0007e`, and parser SHA-256 `b32cb960537f5ffa1336a7689674afba9b4a2485b05e449acd2684a251ff8970`; it accepts only the complete refusal for the inspected auto-squash invocation with its body file but no `--subject`, which that parser rejects before starting the mutation child. `kind: "octopool-0.7.1-policy-timeout-refusal"` qualifies only the inspected initial policy-fetch timeout from the exact released 0.7.1 Darwin arm64 executable and eight source files pinned in `merge-pre-dispatch-refusal.mjs`. In addition to the common pins, attest `producer: "octopool"`, `command: "pr merge"`, `diagnosticsEnabled: true`, `version`, `sourceRevision`, `executableSha256`, and the exact `sourceSha256` path/digest map. This branch accepts only the complete single `class=timeout` error line; diagnostics, policy denials, and additional output remain refused. The retained native intent binds repo/PR/head/method/route; no uncaptured subject or full argv is required. That source revision returns this initial error before diagnostic setup or mutation dispatch; never manufacture a diagnostic. The new attempt still passes the actual string-rewrite policy and every existing review, CI, authority, and receipt gate. For `kind: "octopool-merge-diagnostics"`, attest `producer: "octopool"` and `diagnosticsEnabled: true`; the capture must contain exactly one complete diagnostic with `child_started=false`, a known pre-start outcome, no mutation exit status, and no response headers. Native GraphQL dispatch requests these diagnostics and supplies the captured current-head squash headline and body to the publication guard. Missing, changed, symlink, extra, accepted, or ambiguous evidence stays fenced. Recovery retains the qualification and capture bytes with the prior outcome ancestry. Same-head and replacement qualification both bind local review, preparation, and gates to the approved head, then recheck artifact bytes and the prepared branch after awaited admission. A head-bound `github_pending` stamp may be used only for this qualified recovery once the enforced CI gate and all required checks pass, with fresh `MERGEABLE/CLEAN` immediate admission; the stamp is never rewritten as successful proof, and pending, admin, queue, or auto recovery is refused.
- An operator-qualified legacy gh v2.98 DIRTY refusal may use `merge-recover <PR> <CAPTURE_BLOB_OID> --confirmed-operator-recovery --legacy-refusal <evidence-directory> --replacement-head <SHA>`. Freeze the original `merge-output.log`, `prep.env`, `prep.md`, and `gates.env` before refreshing preparation. The command requires no existing outcome, the sole original regular capture, its exact Git blob hash and complete source-qualified pre-dispatch refusal, historical PR/head/base identity, and all ordinary current review/preparation/CI/admission gates. Empty, uncertain, extra, changed or symlink captures remain blocked. Standing recovery authority and inspected caller/source evidence qualify the refusal; matching text alone does not authorize a retry. The new current intent retains original files and source commits with explicit `legacyRefusal` provenance; it never invents a historical intent or deletes the original capture. Subsequent recovery uses the recorded current outcome normally.

- Worktree cleanup (including GC dry-run and stale-entry provisioning) preserves `.local/merge-output.log` at its original path unless the corresponding outcome passes local record and retained-object validation. Empty captures and dangling capture symlinks count. Preserve the worktree, metadata, and local branches for manual reconciliation; do not trash, rename, or manufacture a receipt. `CLOSED`/`MERGED` and an absent process do not resolve dispatch uncertainty. Valid retained outcomes remain available after eligible cleanup; cleanup does not reconcile them or change process-lock recovery.

- A confirmed merge receipt precedes audits, comments, and cleanup. A comment POST has one attempt marker and is never blindly repeated. Recovery searches authoritative comments for that marker, reports completion pending, and leaves cleanup to the operator after ownership checks; it works without the original worktree/prep artifacts. Normal uninterrupted completion preserves comments and cleanup, with exact-head leased remote deletion. Authoritative branch absence completes cleanup; inspect warnings for advanced or inaccessible branches. Delayed recovery never deletes a recreated branch by name.
- After ownership-checked cleanup, explicitly finish a verified receipt with `scripts/pr merge-complete <PR> <OUTCOME_OID> --confirmed-operator-completion`. It revalidates the exact retained receipt and remote merge, requires native worktree/local branch/remote head branch absence, and never dispatches a merge or deletes resources. A `merged` receipt may post its first completion comment; `commenting`/`commented` require the existing exact attempt marker and never repost. Missing or ambiguous comments preserve pending state. For a retained prior-CI admin receipt, delayed completion reconstructs the historical parent comparison from retained admission main and the verified landed commit before checking cleanup absence. It labels the audit reconstructed after merge, claims no original at-landing audit, and retains the historical CI caveat. Legacy admin receipts without retained prior-CI proof still require owner review; no original audit is fabricated. Read the current outcome OID again after a state transition; stale OIDs are refused. Default `merge-run` remains reconciliation-only.
