How to Recover an Existing PR with default-workflow¶
Home > How-To > Recover an Existing PR with
default-workflow
Use this guide when a pull request already exists and default-workflow needs
to resume it on the same branch after interruption, owner exit, or rate limit.
Before You Start¶
You need:
- a writable checkout of the target repository
- the existing PR number
- the exact existing PR branch name
- issue requirements or acceptance criteria for the recovery scope
gh auth statusworking so the workflow can inspect PR and check metadata
Do not create a replacement branch and do not merge the PR manually. The workflow owns branch reuse, readiness remediation, publish, and finalization.
1. Configure the Node Heap¶
Large workflow-owned agent runs use this heap setting:
Persist the same value in ~/.amplihack/config when nested workflow agents need
it without re-exporting the variable.
2. Confirm the Existing PR and Branch¶
From the repository root, inspect the PR without mutating it:
PR_NUMBER=579
EXISTING_BRANCH=fix/issues-577-578-copilot-hooks-and-additive-copy
gh pr view "$PR_NUMBER" --json number,headRefName,baseRefName,state,url,headRefOid
For the PR 579 recovery example, the head branch is:
If the PR points at a different branch, stop and recover through workflow context. Do not retarget, merge, close, or recreate the PR by hand.
When the recovery is tied to a known head, require the local checkout, PR head, and expected head to match before any readiness verdict:
expected_head_sha=$(gh pr view "$PR_NUMBER" --json headRefOid --jq .headRefOid)
local_head_sha=$(git rev-parse HEAD)
pr_head_sha=$(
gh pr view "$PR_NUMBER" --json headRefOid --jq .headRefOid
)
if [ "$local_head_sha" != "$expected_head_sha" ] ||
[ "$pr_head_sha" != "$expected_head_sha" ]; then
printf 'blocked: local HEAD (%s), PR head (%s), and expected_head_sha (%s) must match\n' \
"$local_head_sha" "$pr_head_sha" "$expected_head_sha" >&2
exit 1
fi
A different head blocks the exact-head no-op path because the workflow cannot claim readiness for a commit it did not inspect.
3. Launch default-workflow with Recovery Context¶
Run the workflow from the recovery worktree:
REPO_PATH=/home/user/src/amplihack-rs
BASE_REF=$(gh pr view "$PR_NUMBER" --json baseRefName --jq .baseRefName)
PR_URL=$(gh pr view "$PR_NUMBER" --json url --jq .url)
amplihack recipe run default-workflow \
-c "repo_path=${REPO_PATH}" \
-c "pr_number=${PR_NUMBER}" \
-c "pr_url=${PR_URL}" \
-c "existing_branch=${EXISTING_BRANCH}" \
-c "base_ref=${BASE_REF}" \
-c "expected_head_sha=${expected_head_sha}" \
-c "goal_already_met=false" \
-c "task_description=Recover PR #579 after interrupted workflow; resolve Copilot hook readiness and additive-copy readiness only; do not manually merge" \
-c "issue_requirements=#577: Copilot plugin and native hooks are staged, registered, idempotent, and verified. #578: mapped framework directories replace stale amplihack-owned trees safely, preserve rollback, and guard source/destination aliasing."
The workflow verifies that the PR belongs to the repository at repo_path and
that the PR head branch matches existing_branch.
Terminal-state recovery depends on the same configuration context:
| Context value | Purpose |
|---|---|
repo_path |
Repository where the worktree, diff, and status checks run. |
pr_number |
Existing PR to inspect for merged, closed, or active state. |
pr_url |
Same-repository PR URL when the workflow receives a URL alongside or instead of the number. |
existing_branch |
Branch expected to match the PR head and local checkout. |
base_ref |
Comparison base for no-diff and obsolete proof. Resolve it from PR metadata or the repository default branch before terminal-state checks. |
expected_head_sha |
Optional exact head guard for no-op and recovery readiness evidence. |
goal_already_met |
Optional design signal. Keep it false unless external evidence says the goal is already satisfied; it never overrides Git, PR, diff, or CI evidence. |
task_description and issue_requirements |
Scope context for recovery and remediation. They do not prove obsolete state; obsolete proof must come from Git and PR evidence. |
4. Check Branch Reuse Evidence¶
The workflow emits a recovery identity block. This example shows PR 579:
{
"pr_recovery": {
"pr_number": 579,
"existing_branch": "fix/issues-577-578-copilot-hooks-and-additive-copy",
"branch_reused": true,
"replacement_branch_created": false,
"manual_merge_performed": false
}
}
Expected behavior:
branch_reusedistruereplacement_branch_createdisfalsemanual_merge_performedisfalse
A branch mismatch is a blocker unless the workflow records why the original branch cannot be used.
5. Review Copilot Hook Readiness¶
Hook readiness is complete only when the workflow proves the Copilot plugin and native hook contracts are usable.
Look for evidence like:
{
"hook_readiness": {
"status": "ready",
"copilot_plugin": "verified",
"plugin_json": "verified",
"hooks_json": "verified",
"copilot_config_registered": true,
"native_claude_hooks": "verified",
"user_config_preserved": true,
"duplicates_created": false,
"verification_failures": []
}
}
The evidence covers these files when Copilot CLI is installed:
| File | Ready condition |
|---|---|
~/.copilot/installed-plugins/amplihack@local/plugin.json |
Declares name: "amplihack" and hooks: "./hooks.json". |
~/.copilot/installed-plugins/amplihack@local/hooks.json |
Maps Copilot events to the intended amplihack-hooks binary path and subcommands. |
~/.copilot/config.json |
Preserves unrelated fields and contains one enabled installedPlugins entry for amplihack. |
~/.claude/settings.json |
Contains native amplihack hook entries in canonical order while preserving unrelated user entries. |
Missing ~/.copilot/ is a supported no-op for hosts without Copilot CLI:
Malformed Copilot config is visible evidence, not proof of readiness. Current
install behavior warns and continues when it cannot parse the config, and it
does not fail the whole install for Copilot plugin registration errors. If the
workflow cannot prove the required installedPlugins entry after such a
warning, hook readiness is blocked.
Use the right Copilot config schema for the surface you are checking:
amplihack install registers the plugin under installedPlugins, while older
copilot_setup helpers use a legacy plugins array.
Typical targeted checks include:
cargo test -p amplihack-cli writes_plugin_manifest_with_hooks_field --lib
cargo test -p amplihack-cli writes_hooks_json_with_amplihack_hooks_subcommands --lib
cargo test -p amplihack-cli preserves_unrelated_config_entries --lib
cargo test -p amplihack-cli validate_amplihack_native_hook_contract --lib
The exact command set may vary, but the evidence must prove the same behavior.
6. Review Additive-Copy Readiness¶
Additive-copy readiness is complete when mapped framework directories are replaced safely and install verification proves the staged tree is complete.
Look for evidence like:
{
"additive_copy_readiness": {
"status": "ready",
"source_layout": "bundle",
"mapped_directories_replaced": true,
"stale_destination_files_removed": true,
"rollback_on_swap_failure": "verified",
"same_path_alias_guard": "verified",
"install_manifest_complete": true,
"verification_failures": []
}
}
The workflow verifies:
| Check | Ready condition |
|---|---|
| Source root | amplifier-bundle/ or legacy .claude/ is a real directory, not a symlink. |
| Staged replacement | Copy happens through a sibling .staging directory before replacing the destination. |
| Stale cleanup | Files removed from the source bundle are absent from the replaced destination. |
| Rollback | A failed final rename restores the previous destination tree. |
| Same-path guard | Source and destination aliases are detected before destructive replacement. |
| Verification | Source-derived verification proves required destination directories and bundle assets exist. |
| Layout marker | .layout records bundle or legacy so later checks use the correct mapping. |
| Manifest | install/amplihack-manifest.json records staged files, directories, binaries, and hook registrations. |
Typical targeted checks use real test filters such as:
cargo test -p amplihack-cli copy_amplifier_bundle_replaces_existing_atomically --lib
cargo test -p amplihack-cli copy_dir_recursive_skips_same_path --lib
cargo test -p amplihack-cli install_writes_layout_marker_atomically --lib
cargo test -p amplihack-cli read_manifest_rejects_path_traversal_in_dirs --lib
Missing source assets, symlinked source roots, unsafe path entries, failed copy/swap, incomplete verification, or an incomplete manifest are blockers.
7. Interpret Terminal-State Outcomes¶
During recovery, the workflow checks terminal state before any version bump, commit, push, PR creation, CI wait, or merge. A successful terminal state means the recovery is complete without more Git or GitHub mutation.
The terminal-state block uses the same shape in publish, PR review, and finalization:
{
"workflow_terminal_state": {
"terminal_success": true,
"terminal_state": "NO_DIFF_SUCCESS",
"terminal_reason": "Worktree is clean and the branch has no meaningful diff against origin/main.",
"publish_status": "NO_DIFF_SUCCESS",
"should_publish": false,
"should_finalize": false,
"should_run_ci_wait": false,
"should_merge": false
}
}
Use the status exactly as emitted:
| Status | Meaning | What the workflow does next |
|---|---|---|
MERGED |
The PR is already merged, or it is closed with merge evidence. | Stops successfully before publish, CI wait, or merge. |
CLOSED_OBSOLETE |
The PR is closed without merge evidence, but the branch is clean and the intended changes are already upstream or no meaningful work remains. | Stops successfully and records the obsolete proof. |
NO_DIFF_SUCCESS |
The worktree is clean and no meaningful diff or branch-only commits remain against the intended base. | Stops successfully without creating a no-op commit or follow-up PR. |
FOLLOWUP_CREATED |
Meaningful unmerged work remains and the workflow created or updated a PR for it. | Continues through normal CI and merge readiness checks. |
BLOCKED_CI |
Required checks are failing or policy blocks merge. | Fails loudly; do not reinterpret as terminal success. |
Closed-unmerged PRs are failures unless the workflow proves
CLOSED_OBSOLETE. A closed PR with remaining diff evidence is reported as a
blocker, not as recovered work.
Dirty worktrees also block terminal success. If git status --porcelain has
any output, the workflow cannot prove whether recovery work is complete or safe
to ignore, so it fails before accepting MERGED, CLOSED_OBSOLETE, or
NO_DIFF_SUCCESS.
Malformed inputs, unavailable PR metadata, missing base refs, and gh command
errors fail closed. Treat them as blockers to fix in workflow context, not as
accepted no-op recovery.
8. Let workflow-publish Own Commits and Pushes¶
If readiness remediation changes files, the workflow commits and pushes those changes to the existing branch. This example shows PR 579:
{
"workflow_publish": {
"target_pr": 579,
"target_branch": "fix/issues-577-578-copilot-hooks-and-additive-copy",
"changes_required": true,
"pushed": true,
"replacement_branch_created": false
}
}
If no changes are required, changes_required and pushed are false, and the
workflow records that no publish action was necessary.
If the terminal state is already successful, publish is skipped instead of being reported as a no-op publish attempt:
{
"workflow_publish": {
"target_pr": 579,
"target_branch": "fix/issues-577-578-copilot-hooks-and-additive-copy",
"publish_status": "MERGED",
"terminal_success": true,
"terminal_state": "MERGED",
"version_bump_ran": false,
"commit_ran": false,
"push_ran": false,
"pr_create_or_update_ran": false,
"ci_wait_ran": false,
"merge_ran": false
}
}
Those suppression flags are part of the recovery evidence. They prove the workflow did not perform Git or GitHub mutation, including version bump, stage, commit, push, PR create/update, CI wait, merge, or duplicate/no-op follow-up PR creation after completion was already established.
Dirty readiness documentation or evidence files are handled in one of two ways:
- committed by the workflow as recovery evidence, or
- explicitly classified as outside final readiness.
Unclassified dirty readiness files block final readiness.
9. Use workflow-finalize as the Final Decision¶
The finalization step emits the recovery decision. This example shows PR 579:
{
"workflow_finalize": {
"pr_number": 579,
"final_status": "ready",
"hook_readiness": "ready",
"additive_copy_readiness": "ready",
"manual_merge_performed": false,
"remaining_blockers": []
}
}
If hook readiness and additive-copy readiness are already satisfied at the checked head, the workflow emits an accepted no-op instead of inventing a change. The no-op names the exact head, files modified, and the observed check state:
{
"workflow_finalize": {
"pr_number": 579,
"head_sha": "8fb46865fb4412038b9313a62c02cc5aa0693132",
"final_status": "ready",
"changes_required": false,
"files_modified": [],
"hook_readiness": "ready",
"additive_copy_readiness": "ready",
"check_state": {
"lint_format": "green",
"builds": "green",
"test": "in_progress",
"merge_state": "blocked"
},
"no_op_justification": "No workflow-owned hook or additive-copy readiness changes are required at head 8fb46865fb4412038b9313a62c02cc5aa0693132. Lint/Format and build checks are green; Test is still naturally in progress and branch protection keeps the merge state blocked, so the PR is workflow-ready but not merge-ready.",
"manual_merge_performed": false,
"merge_bypass_performed": false,
"nested_default_workflow_launched": false
}
}
Do not convert that result into a manual merge. Pending Test and blocked merge state mean the PR remains under normal GitHub protection while being recovered from the workflow's point of view.
Finalization re-checks terminal, dirty, diff, PR, and CI state before reporting completion. A terminal-success finalization looks like this:
{
"workflow_finalize": {
"pr_number": 579,
"final_status": "finalized",
"terminal_success": true,
"terminal_state": "CLOSED_OBSOLETE",
"terminal_reason": "PR #579 is closed without merge evidence, but the branch is clean and equivalent changes are present on origin/main.",
"remaining_blockers": [],
"version_bump_ran": false,
"commit_ran": false,
"push_ran": false,
"pr_create_or_update_ran": false,
"ci_wait_ran": false,
"merge_ran": false
}
}
If finalization observes a dirty worktree, a closed-unmerged PR without obsolete
proof, a meaningful unmerged diff, or failing required checks, it emits a
blocking status such as BLOCKED_DIRTY_WORKTREE, BLOCKED_CLOSED_UNMERGED,
BLOCKED_UNMERGED_DIFF, or BLOCKED_CI. Those statuses require remediation;
they are not successful no-op completions.
Interpret the status exactly as emitted:
| Status | Meaning |
|---|---|
ready |
The PR is ready for normal workflow-managed completion. |
blocked |
A concrete remaining blocker prevents readiness. |
finalized |
The workflow completed its permitted finalization path. |
Do not override a blocked result by manually merging. Rerun or remediate
through workflow context.