Instruction file imported from kurone-kito/setup.windows (
.github/instructions/idd-merge.instructions.md). Copyright stays with the author.
IDD — Merge Execution Phase (F3–F5)
Read only after idd-merge-handoff.instructions.md routes the current
claim to the autonomous merge path. Covers executing the merge (F3),
cleanup (F4), and looping back to discover (F5).
The final merge-gate timing defaults are named in IDD policy constants; the merge logic itself stays here.
Before any mutating action in F3, apply the shared claim revalidation gate.
F3 — Merge
-
Confirm the claim is still yours: the active claim must still use your current
{claim-id}. If it is missing, released, or held by a different{claim-id}(even under the same agent ID), the claim was lost — report and stop. -
Defensive route check: re-read the repository's recorded merge policy (missing → treat as
fully_autonomous_merge, the distributed default). Then apply:fully_autonomous_merge: continue.separate_merge_agent: continue only when repository documentation explicitly records the current session as the designated merge-capable actor and the documented resume condition is satisfied; otherwise route toidd-merge-handoff.instructions.mdand stop.human_mergeor unknown policy: route toidd-merge-handoff.instructions.mdand stop.
-
Immediately before executing the merge command, do one final live fetch using the exact same activity-universe scope as E1 Step 1 (all review threads, review bodies, and regular PR comments, excluding trusted agent operational marker comments), and compare it against the F2 snapshot carried forward from
idd-pre-merge.instructions.md. When helper runtime is enabled, prefer the documented merge-gate helper reference indocs/idd-helper-scripts.mdto collect the snapshot tuple and broaderpre-merge-readinessJSON report. Both helpers remain read-only evidence collectors only: if execution fails, output is invalid JSON, required sections are missing, or live GitHub state disagrees with it, discard helper output and run the live fetch directly — the written gate rules remain canonical. Return to E1 if any of F2's Review-currency return-to-E1 triggers apply, substituting the carried F2-snapshot fields for F2's own stored watermark fields:{f2-head-SHA}for{head-SHA},{f2-max-activity-updatedAt}for{max-activity-updatedAt},{f2-total-item-count}for{total-item-count}, and{f2-latest-ci-completed-at}for{latest-ci-completed-at}— this final fetch is the live side of each comparison, exactly as F2's own live snapshot was.The structural ack-only carve-out from F2 applies here verbatim: newer activity/count growth that helper evidence proves is post-disposition advisory-bot acknowledgement only (
ack-only-post-disposition) does not force the return to E1; all other triggers above are unaffected.From that same final fetch, compute
F3_UNRESOLVED_ACTIONABLE_COUNTusing the exact F2 unresolved-thread rule and exceptions (non-awaiting-reviewer unresolved threads only; awaiting-reviewer classification must follow F2 verbatim, including AMD exclusion and conversation-resolution exception handling). IfF3_UNRESOLVED_ACTIONABLE_COUNT > 0, stop and return to E1 — do not executegh pr mergein this pass.If the carried F2 evidence includes helper-side
dispositionEvidence, requireroute == "proceed"andblockingCount == 0before merge, except the F2 override whensoleCauseAckOnlyPostDispositionis true. Any other missing item still returns to E1/E4. Use only the carriedpre-merge-readinessdispositionEvidenceshape here; E7 verifier fields (passed,items[]) are not merge-gate substitutes.Execute the merge immediately after this final fetch and the claim re-validation and advisory state revalidation below, with no other actions in between. Re-validate claim: re-read the issue and confirm the active claim still uses your current
{claim-id}— if not, the claim was lost, report and stop.Advisory state revalidation (blocking): the AW1 check just below is an instant state read, not itself a wait. If it escalates to a genuine wait, return to the F2 advisory bot wait check (backgrounds only if the topology-safety condition holds — confirmed to route completion back to this turn — otherwise waits synchronously): no single
ghcommand blocks on Copilot review state, so run the AW poll loop as a foreground wait, never viarun_in_backgroundabsent the confirmed condition — see idd-ci.instructions.md's Wake-up discipline. Re-fetch the HEAD SHA:PR_HEAD_SHA_F3=$(gh pr view {pr-number} --json headRefOid --jq '.headRefOid')Use
PR_HEAD_SHA_F3asPR_HEAD_SHA. Run AW1 (idd-advisory-wait.instructions.md):- If SATISFIED (
LAST_COPILOT_COMMIT == PR_HEAD_SHA_F3) → proceed with the merge. - If
COPILOT_PENDINGis"false"(review completed or cancelled) → satisfied; proceed with the merge. - Otherwise (
COPILOT_PENDINGis"true", not yet reviewed): run AW2 and apply AW3 — do not skip even if F2 already ran them, since F3 is a self-contained blocking gate:- SATISFIED → proceed with the merge.
- HOLD → post the hold comment from AW4 and stop.
- RECOVERY_NEEDED → post the recovery marker from AW3-R and return to the F2 advisory bot wait check; do not merge in the same F3 pass that creates a recovery marker.
- CAP_EXHAUSTED → post the cap-exhausted hold comment from AW4 and stop.
- REQUEST_NEEDED → return to E14 to refresh/request Copilot review and post a request marker; do not merge.
- WAIT → do NOT execute the merge; return to the F2 advisory
bot wait check in
idd-pre-merge.instructions.md(go back to the first condition in F2), which reuses the existing same-HEAD marker — do not post a new one.
If the optional helper output disagrees with the live fetch above, follow the live fetch and the written gate rules.
- If SATISFIED (
-
Merge the PR using a merge commit, binding to the validated SHA to prevent a race where a new push lands between the F3 freshness check and the merge itself.
Preferred path (helper runtime enabled): run the F3 merge helper documented in
docs/idd-helper-scripts.md. First run it in dry-run (no--apply) and confirmready: truewith an emptyblockers[]— it wraps the read-onlypre-merge-readinessgate and adds no new authority. Then re-run with--apply: whenready, it re-fetches the head SHA and re-validates the claim immediately before merging, fails closed (no merge) on head drift or lost claim, and runs the merge commit bound to the validated head (never squash/rebase). On a plain-merge failure it also applies step 5's solo-CODEOWNER--adminfallback decision itself (recorded inadminFallbackUsed) — the gate checklist and decision table below stay canonical: if the helper is unavailable, its output is invalid, or its evidence conflicts with live GitHub state, discard it and use the manual gate + merge steps in this section.Gate checklist — confirm every field before merging; all must hold, and any unmet or unknown field is a NO-GO (fail closed — stop, do not merge):
- current HEAD SHA equals the carried F2-snapshot head
(
{f2-head-SHA}); - review-currency route is
proceed; F3_UNRESOLVED_ACTIONABLE_COUNTis0;- advisory
f3OutcomeisSATISFIED(the authoritative advisory gate — do not add stricter sub-conditions; e.g. a pending-windowSATISFIEDcan keepcopilotPendingtrue andLAST_COPILOT_COMMIToff the head); - no unwaived
copilot-terminal-unavailablein the helper'sblockers[]— separate fromf3Outcome, not a stricter sub-condition on it (Terminal routing); - all required CI checks pass for the current head;
- claim ownership still uses your
{claim-id}.
For the head-SHA field, use this copy-paste-safe, fail-closed check — both operands fully quoted, no glob, abort on mismatch — rather than re-deriving it ad hoc (a stray glob or unquoted operand can silently mis-gate this safety-sensitive step).
F2_HEAD_SHAis the carried{f2-head-SHA};PR_HEAD_SHA_F3is step 3's re-fetch:F2_HEAD_SHA="{f2-head-SHA}" # the head recorded in the F2 snapshot if [ "$PR_HEAD_SHA_F3" != "$F2_HEAD_SHA" ]; then echo "F3 abort: head moved ${F2_HEAD_SHA} -> ${PR_HEAD_SHA_F3}" >&2 exit 1 # do not merge — return to E1 per the freshness rules above fiThen merge, binding
--match-head-committo the freshly validated${PR_HEAD_SHA_F3}(never a stale, hardcoded, or unbound SHA), never squash or rebase:gh pr merge {pr-number} --merge --match-head-commit "${PR_HEAD_SHA_F3}"After the merge succeeds and claim ownership is re-validated, upsert the digest with
Phase: F3 merged,Open blockers: none,Next action: F4 cleanup then F5 discover, andAuthoritative bypointing to the merge commit and matched head SHA — not a merge gate, and must not happen before the successful merge command. - current HEAD SHA equals the carried F2-snapshot head
(
-
If merge fails:
gh pr merge --mergefails with "the base branch policy prohibits the merge" despite a passing Gate checklist and a configured pull-request-only bypass actor → that scoped bypass alone may not clear a solo-maintainer self-approval deadlock (seedocs/permissions.md's "Pull-request-only ruleset bypass"). CheckmergeGate.soloCodeownerAdminFallbackin.github/idd/config.json:-
"hold-and-report"(opt-in) → keep the pre-#1521 behavior: do not retry the plain command or add--admin; post a hold comment with the GitHub error text and stop for a maintainer decision (kurone-kito/idd-skill#1493). -
Anything else, including the key absent (distributed default
"auto-admin-retry") → retry exactly once with--admin, bound to the same validated head, only when every field in the Solo-CODEOWNER--adminfallback field contract holds: the Gate checklist (step 4) was fully green; the merge command's only reported failure is this exact GitHub error against a configured pull-request-only (or wider) bypass actor; and the report'sreviewerStates.codeownerSelfApprovalproves the PR author is the sole eligible codeowner (status: "clear"with a bypass-availablereason,prAuthorIsSoleEligibleCodeowner: true,codeownerEligibilityUnreadable: false) — re-checked a second time immediately before the--admincall itself (real time passes between the plain merge's failure and the retry, and--adminbypasses the entire ruleset), with a fresh GitHub merge state ofmergeable: "MERGEABLE"andmergeStateStatussettled to"CLEAN"or"BEHIND"also required.isSafeSoloCodeownerAdminMergeStatestill refusesmergeStateStatus: "BLOCKED". On kurone-kito/idd-skill's currentmainruleset (require_code_owner_review: false), thestatus: "clear"trigger never matches, observed"BLOCKED"states have not been a confirmed CODEOWNER deadlock, and the remaining escalation on this topology is a human--admin(orhold-and-report). Distributedauto-admin-retryis unchanged whenstatus: "clear"with a bypass-availablereason,prAuthorIsSoleEligibleCodeowner: true, andcodeownerEligibilityUnreadable: falsehold. Seedocs/permissions.md(kurone-kito/idd-skill#1663).idd-merge-execute.mjs --applyapplies this automatically and records the outcome in the verdict'sadminFallbackUsedfield.gh pr merge {pr-number} --merge --match-head-commit "${PR_HEAD_SHA_F3}" --adminOn success, continue the normal post-merge digest update exactly as after a successful plain merge (step 4). If any condition above does not hold, or the
--adminretry also fails, post a hold comment with the GitHub error text(s) and stop for a maintainer decision (kurone-kito/idd-skill#1493, #1494) — the same hold-and-report outcome as the opt-in tier.
-
- Base branch updated or conflict → return to
idd-pre-merge.instructions.mdF1 - CI condition no longer met → return to
idd-pr-submit.instructions.mdD4 (CI wait) - Review condition no longer met → return to
idd-review-snapshot.instructions.mdE1 - Conversation resolution required and unresolved threads remain →
for each: (a) new reviewer activity (not awaiting-reviewer) →
return to E1; (b) awaiting-reviewer thread whose latest reply
is from an IDD agent without
**Awaiting maintainer decision**→ resolve it directly, then restartidd-pre-merge.instructions.mdF2 (to re-run the final freshness fetch); (c) awaiting-reviewer thread whose latest reply is from the PR author (not IDD agent) → post a brief acknowledgement reply, resolve it directly, then restartidd-pre-merge.instructions.mdF2; (d) thread with**Awaiting maintainer decision**reply → post a hold comment and stop. Cases (b)-(c) together are the F3 awaiting-reviewer restart-F2 path cited elsewhere.
When a merge failure routes to F1, D4, E1, or a hold, update the digest after recording the failure evidence:
PhasetoF3 blocked, the GitHub merge error or unresolved-thread class inOpen blockers,Next actionto the routed phase or maintainer action. If the path instead resolves/acknowledges awaiting-reviewer threads and restarts F2, do not update the digest before restarting — that activity would invalidate the restart and force an E1 snapshot even though E1 intentionally has no actionable awaiting-reviewer item; let the restarted F2 pass record blockers if it finds one.
F4 — Cleanup
-
Confirm the post-merge digest update above exists or repair it after re-validating the claim. Do not minimize the digest as an operational marker unless a future cleanup policy explicitly supports digest retirement.
-
Run merged-PR comment cleanup (must not run before F3 succeeds). Re-validate the active claim before each GitHub minimization mutation.
Apply the following cleanup policy rules when evaluating candidates:
- Feedback or review parent comments may be minimized as
RESOLVEDonly after every actionable child review comment/thread under that parent is accepted or rejected, replied to as required, and resolved. - Known review-bot regular PR comments may be minimized only after merge, with a clear completed-review or stale-notification signal (a CodeRabbit no-action summary, a summary/review-trigger acknowledgement with a matching later IDD disposition, or — for CodeRabbit summaries specifically — once all its review threads are resolved with fresh IDD dispositions).
- Bot review parent bodies without associated review threads (including Copilot error review bodies) are skipped by default unless a future policy narrows a safe cleanup class for them.
- Trusted IDD operational marker comments may be minimized as
OUTDATEDonly after merge, once the marker is no longer needed for resume, advisory wait, or review-currency checks. Candidate prefixes:<!-- review-watermark:,<!-- review-baseline:,advisory-wait:,advisory-wait-recovery:,<!-- advisory-wait:,advisory-reroll:. - Do not minimize comments with unresolved maintainer decisions, active holds, failed-CI context maintainers still need, non-operational human discussion, or content still in active F2/F3 gates.
Mandatory apply decision tree — follow this sequence; no path may exit without a recorded reason when cleanup candidates exist. In the idd-skill source repository, run the helper in dry-run mode first; in adopter repositories, skip to the GraphQL fallback below unless the helper scripts were explicitly installed.
node scripts/audit-pr-cleanup.mjs --pr <pr-number> --dry-run --format tableDuplicate-success-record skip rule: before posting any evidence comment below, skip it if the PR already carries a
<!-- idd-cleanup-evidence:comment recording a successful outcome (appliedorclean) whose author is a trusted marker actor — the current session actor, a configuredtrustedMarkerActorslogin, or another configured trusted bot/GitHub App login for IDD automation (see the shared Trusted marker actors rule; the identitypost-merge-cleanup.ymlposts under, commonlygithub-actions[bot], only qualifies when the repository has actually configured it as trusted) — to avoid a duplicate success record. An untrusted commenter's marker-prefixed comment never counts as evidence and must not suppress this post — the same trust-scoping every other IDD operational marker already applies. Otherwise post (a fresh success record, or a correction of an existingfailed/incomplete/permission-blockedrecord, or a correction of an untrusted-author record).Evaluate the dry-run
statusfield (this is a dry-run status; apply mode emits different values and is never invoked unless dry-run showsneeds-apply):-
clean: no candidates and no permission-blocked items. Proceed to step 3. -
needs-apply: eligible candidates exist and the viewer can minimize them. Apply is mandatory. Re-validate the active claim, then run:node scripts/audit-pr-cleanup.mjs --pr <pr-number> --apply \ --claim-issue <issue-number> --claim-id <claim-id> --format tableAfter apply, record the outcome by the apply
status. Seedocs/idd-comment-minimization.mdfor the exact formats:If the apply
statusisapplied(residual candidates minimized) orclean(no-op, nothing left to minimize): apply the duplicate-success-record skip rule above; otherwise post the evidence comment (status,applied,failed,skipped,viewer-cannot-minimizecounts forapplied, or a convergedcleanrecord) so this run's work is recorded. Proceed to step 3.The helper internally retries a whole scan-and-minimize pass, bounded, when a fresh rescan still reports candidates after applying (a candidate that only became eligible after the previous pass, e.g. GraphQL read-after-write lag) — the common case still converges to
applied/cleanwithin this one invocation. If the output also reportsretryBoundExhausted: true(visible asretryBoundExhausted=truein table format), the retry bound was reached while a rescan still found candidates. Route by the applystatusexactly as above, even then: ifstatusis stillapplied/clean, follow that evidence-comment path and note theretryAttemptscount as an informational, non-blocking residual-lag signal rather than a defect; ifstatuscame backincomplete(the fresh rescan found a genuine permission-blocked remainder) orfailed, follow thefailed/incompletecleanup-failure path below instead —retryBoundExhausted: truenever overrides a non-successstatus.If the apply
statusisfailed,incomplete, orrescan-failed: post the cleanup-failure comment format instead, including theviewer-cannot-minimizecount when non-zero.rescan-failedmeans the confirming rescan itself errored after a mutation (already-applied work is preserved in the report but convergence was never confirmed) — note that distinction in the comment and re-run--applyto confirm convergence. Explicit evidence, not a merge gate — the merge already succeeded. Proceed to step 3. -
permission-blocked: skipped items exist withviewerCanMinimize: falseand no apply-eligible candidates found. Post a cleanup-permission-blocked comment listing the blocked candidates and the count, then proceed to step 3.
For the GraphQL fallback (helper unavailable): check
viewerCanMinimizeandisMinimizedbefore minimizing; skip already-minimized comments and ones the viewer cannot minimize. Re-validate the active claim before each mutation. Afterward, apply the duplicate-success-record skip rule above; otherwise post an evidence comment summarizing the outcome (status, applied/skipped counts with reasons). If the viewer cannot minimize any detected candidates, post a cleanup-permission-blocked comment instead of exiting silently.See
docs/idd-comment-minimization.mdfor the evidence comment format, cleanup-failure comment format, permission-blocked comment format, and fallback GraphQL commands. - Feedback or review parent comments may be minimized as
-
Run from the primary worktree, never from inside the worktree being removed. Any removal (plain or
--force) silently discards ignored files too, including inside a submodule. Scope Git commands to<path>. Inspect leftover files under a-submodule path directly (not a repo).Use
--untracked-files=normal(notall). A clean submodule worktree can still hide a stash or unpushed commit:git -C <path> status --porcelain --ignored --untracked-files=normalgit -C <path> submodule status --recursivegit -C <path> submodule foreach --recursive 'git status --porcelain --ignored --untracked-files=normal; git stash list; git rev-list --all --not --remotes --count'
Generated output is disposable only if a configured project command can reproduce it. Preserve anything else first. Copy secrets (e.g.
.env) out only — never commit or push them. Non-secret work may go to a different ref or be copied out — not to<branch-name>itself, which this step deletes next. Immediately before eachworktree remove, re-validate this session's claim and worktree lock (idd-claim.instructions.md); stop if either is no longer ours. Then delete the worktree, then the branch:git worktree remove <path>. If it fails withfatal: working trees containing submodules cannot be moved or removed, retry withgit worktree remove --force <path>. Use--forceonly after that review finds nothing worth preserving.git branch -d <branch-name>(the baseline permission profile denies-D; seedocs/permissions.md). If it fails witherror: the branch '<branch-name>' is not fully mergeddespite the PR being merged —fetch --prunecan drop the remote-tracking ref before localmainfast-forwards — run step 4 first, then retry.
-
Update the local
mainbranch (git fetch origin main && git merge --ff-only origin/main). -
If GitHub auto-delete is disabled: delete the remote branch too. (Worktrunk may be used for steps 3–5.)
F5 — Loop
Return to idd-discover.instructions.md and pick the next issue.
F4-complete/F5 is the safe session-exit boundary: under context
pressure, exit here for a fresh Discover session rather than looping
in-process — see the autopilot operating model in
docs/idd-workflow.md.