fin
Use this skill at the end of a task before the final user-facing report. Linear follow-up is best effort: attempt it when available, but never block finalization or mark it partial solely because Linear work could not be completed.
Context Selection
Run fin [context] [target].
gh: finalize from a GitHub PR context. Use this when the task should land by merging the current remote PR or when the matching PR already merged and only cleanup/final verification remains. The originalfinworkflow maps to this context.local: finalize from a local checkout. Use this when the task should land directly from local git state without depending on GitHub PR state.nocheck: close the task's local branch and linked worktree without checking or changing any remote state. Use explicitly after reviewing someone else's code when the local review checkout is no longer needed; no landing or merge proof is required.- Route explicit
nocheckdirectly to thenocheckworkflow below, before detached-HEAD handling, PR-target inference, or context auto-detection. Return after that workflow; every subsequent landing workflow, guardrail, and done checklist applies only togh/local. [target]: optional forghonly. Accept a PR number, PR URL, or branch name. Examples:fin gh 85117,fin gh https://github.com/owner/repo/pull/85117.- If the current checkout is detached
HEAD, treat that as a preflight issue, not a valid finalization state. Create a short-lived local branch from the current commit before auto-detecting context, checking mergeability, or attempting worktree cleanup. - If the argument is
gh,local, ornocheck, respect it throughout the flow. Do not silently switch later just because repo state would make the other path easier. Never auto-detectnocheck. - If
ghhas an explicit[target], lock that PR target before current-branch detection. Use the target PR as the source of truth for state, mergeability, comments, checks, spec matching, merge, and automation cleanup. - If the user omits
[target]but the immediately preceding active heartbeat or delayed-merge instruction names exactly one PR and the user asks to merge, finalize, ignore a waiting period, or ignore a proof gate, treat that PR as an explicitghtarget after one live PR-state check. Report the target source asheartbeat automation. - If the user omits
[target]but the immediately preceding task in the same thread completed or repaired exactly one PR, such as aftertrigger:fix-pr,trigger:fix-pr-conflict, or a PR-specific babysit/CI run, treat that PR as an explicitghtarget after one live PR-state check. Report the target source asactive task context. If the current checkout points at another branch or PR, mention the mismatch and ignore the unrelated checkout for PR state, spec archival, merge, automation, and cleanup decisions unless it blocks local cleanup. - If an explicit
ghtarget does not match the current branch, do not silently fall back to the current branch's PR. Either run the remote-PR finalization path for that explicit target, or stop with a target mismatch before any spec archival, branch cleanup, or merge. - If the argument is omitted, detect the context from the current branch before any archival or landing work:
- Choose
ghwhen the current branch has an open or already-merged PR that corresponds to the branch being finalized. - Choose
localwhen the current branch has no matching PR and the work should land directly from local git state.
- Choose
- Treat heartbeat-derived or active-task-derived PR targets as explicit
ghtargets, not as current-branch auto-detection. - If the argument is present but not one of
gh/local/nocheck, stop and ask the user which context to use. - Report whether the finalization context was explicitly provided or auto-detected.
- Before reporting any PR status or blocker, print one target identity line:
Target: PR #<number>, branch <headRefName>, source=<current checkout|explicit user PR|heartbeat automation|active task context>. When multiple PRs have been mentioned in the session, prefix every PR-specific state claim with the PR number.
nocheck Context Workflow
An explicit fin nocheck authorizes deletion of the exact disposable local
branch even when its commits are not merged. It does not authorize discarding
uncommitted work or deleting a remote branch. A request to add or explain this
context does not itself invoke cleanup.
- Lock the local target from the current task's recorded review checkout and
branch, or the current checkout when no separate task checkout exists. Read
local Git state to confirm the repository, absolute worktree path, branch,
and full HEAD OID. Report
Target: local branch <branch>, worktree <path>, context=nocheck (explicit). If the task's checkout differs from the shell's checkout, use the task's target and preserve the unrelated checkout. Stop on ambiguity, detached HEAD, or identity drift; do not create a temporary branch. For a branch checked out nowhere, inspect its local ref from a retained checkout and skip worktree-only checks. A missing path still registered as a worktree is a blocker; do not prune it to make cleanup pass. - Use local state only. Do not query GitHub, PRs, CI, reviews, downstream PRs,
mergeability, or the remote default branch. Do not fetch, pull, push, merge,
rebase, delete remote refs, or run
check_default_branch.pyor$dev.worktrees cleanup-landed. SetGIT_NO_LAZY_FETCH=1for Git commands so a partial clone cannot fetch missing objects implicitly; missing local data is a blocker. Do not run network-capable hooks or fall back to a landing flow. - Read repository preservation guidance and any matching
~/.fin.yamlentry using the normalized primary checkout root. Apply local file-preservation instructions before removal; skip landing, deployment, and remote hooks. Stop when malformed or ambiguous config could hide preservation requirements. Inspect tracked, untracked, and ignored files withgit -C <target> status --short --untracked-files=all --ignored. Preserve review notes, specs, credentials, and other wanted artifacts outside the target without overwriting existing files or exposing secrets, and verify the copies. A dirty tracked or untracked state blocks removal until separately resolved; do not stash, reset, clean, or force-remove it. Ignored files may be removed with the worktree only when known to be disposable generated output; preserve unknown files instead. - Protect the primary checkout directory, locked worktrees, unrelated branches,
and local trunk branches (
main,master, and any other default/trunk branch identified by local repository guidance or cachedrefs/remotes/origin/HEAD). Cached refs are local evidence only; do not refresh them or require an origin remote to exist. If the target might be a trunk branch, stop for clarification. Record a retained checkout outside the target before removing a linked worktree. If the target is in the primary checkout, keep that directory and switch to an existing, locally identified safe branch without force or remote guessing (git switch --no-guess <retained-branch>). Stop if a safe switch would require fetching or changing unrelated work. - Immediately recheck the target branch's full OID and, for a linked worktree,
its branch, HEAD OID, registration, lock state, and file-preservation
conditions. From the retained checkout,
remove only the clean linked target using the ordinary removal path in
$dev.worktrees:git -C <retained> worktree remove <absolute-target-path>. Never use--force, recursive path deletion, or global prune. Skip removal for a primary checkout or a branch already checked out nowhere. - Recheck the local branch OID and confirm no worktree has it checked out.
Delete only that branch with
git -C <retained> branch -D -- <branch>. This explicitnocheckauthorization replaces the merged-branch requirement for this step only; do not fabricate a landed commit or weaken thegh/localcleanup gates. If the branch moved, stop and preserve it. - Verify the branch is absent and, for linked-worktree cleanup, both the target
path and its worktree registration are absent. If only removal succeeded,
report partial local cleanup and preserve the recorded branch/OID for a retry.
Report the local cleanup result, retained checkout, and preserved artifact
paths, followed by
Remote state was not checked or changed.Do not claim a PR landed or a remote branch was deleted. Skip spec archival, issue completion, automation changes, retrospective publishing, and the landing checklist below.
Shared Workflow (gh / local only)
- Confirm finalization preconditions
- Use this flow only when the requested scope is complete.
- If work is partial or blocked, do not archive specs and do not present the task as finished.
- Resolve the repository's origin main branch before any spec archival, landing, merge, or cleanup. In this skill,
mainmeans the default branch of the repository addressed byorigin; do not assume the literal branch name ismain.For
gh, query the target repository's authoritative default branch and the locked PR'sbaseRefName.For
local, resolve and verify the default branch associated withorigin, using the remote provider when available orrefs/remotes/origin/HEADas a fallback. If the origin main branch cannot be determined unambiguously, stop.Compare canonical short branch names. When a local fallback returns
refs/remotes/origin/<name>ororigin/<name>, retain the raw result as evidence and pass only<name>to the gate.Pass the two resolved names through the mandatory machine gate before any spec archival, landing, merge, cleanup, or full-finalization wording:
python3 ./scripts/check_default_branch.py \ --context gh \ --repository-default-branch "<resolved-origin-main>" \ --target-base-ref "<locked-target-base-ref>"Resolve
./scripts/check_default_branch.pyrelative to thisSKILL.md, replace the quoted placeholders with the resolved values, and use--context localfor local finalization. Do not replace this command with a manual comparison or a same-named repository script.Continue only when the command exits
0and its JSON reportsstatus: "pass",matches: true, and everyallowfield astrue. Retain that JSON as finalization evidence. Treat a nonzero exit, malformed output, or any other result as a hard blocker.Rerun the gate if the locked target, repository default branch, or target base ref changes. The latest passing record is the only valid authorization for the finishing actions.
A mismatch blocks intermediate, stacked, release, and feature branches. Use the script's retarget-or-create-PR message in the report.
If the PR already merged into a non-main branch, the gate still fails: stop before archival or cleanup, report that it did not land in origin's main branch, and do not describe it as fully finalized.
- Before archiving any spec or attempting to land the change, determine the current branch, whether it is attached to a linked worktree, and where the non-worktree
maincheckout lives. - Read repository guidance for ignored-file preservation and verification
boundaries even when no
~/.fin.yamlexists. Treat checked-in product specs as deliverables, not disposable task artifacts, unless their own workflow explicitly requires archival. - Check
~/.fin.yamlfor repo-specific finalization instructions before landing or cleanup. If the file exists, parse entries shaped asworkspace: [{path: ..., instructions: ...}]. - If
~/.fin.yamlexists but cannot be parsed, do not treat the parse failure as "no hooks". Report the parser error separately and inspect the raw file. Continue only when the raw content is clearly non-executable, unambiguous context; if it might contain commands, hooks, destructive instructions, or ambiguous cleanup requirements, stop before spec archival, landing cleanup, or linked-worktree removal and report the malformed config as the blocker. - Match
workspace[].pathagainst the normalized non-worktree checkout root for the branch's repository, not against a transient linked worktree path. Resolve symlinks and trailing slashes before comparing. - If a matching
~/.fin.yamlworkspace entry exists, record itsinstructionsand apply them after the PR merge / already-merged confirmation or local landing succeeds, but before removing any linked worktree. This preserves ignored or untracked files that may need to be copied out of the soon-to-be-removed worktree. - Also treat matching
~/.fin.yamlinstructions as routing context for active spec discovery. If the instructions mention a$specy,$mem, or.memartifact root, derive candidate docs roots from that instruction before falling back to the default docs root. - If
~/.fin.yamlis missing or no workspace entry matches the non-worktree checkout root, state that no repo-specific final hooks applied and continue. - If the checkout is detached
HEAD, create a temporary local branch from the current commit first. Prefer the repo's normal task-branch prefix when one exists, otherwise use a shortcodex/branch name derived from the task. - After converting detached
HEADinto a named branch, lock that branch identity for the rest of the run. Do not continue finalization from anonymous detached state. - When the user omitted the context, lock the detected context once and use it for the rest of the run. Do not re-detect after archiving or mid-landing.
- When the user provided, or the heartbeat handoff or active task context implied, an explicit PR target, lock that PR number, head branch, and target source once. Do not replace it with the current checkout's PR because the current checkout is different or easier to operate from.
- In
ghmode, apply the downstream pull request protection below to the locked repository and exact PR head branch before merging, enabling auto-merge, or deleting its remote branch. - Do not silently switch contexts after selection. If the user requested
gh, do not fall back to local-only landing. If the user requestedlocal, do not silently land via PR merge just because a PR exists. - For
ghwithout an explicit target, identify the current PR and check its state before testing mergeability or attempting any merge command. - For
ghwith an explicit target, identify that target PR directly with GitHub before consulting current-branch PR state. If the current checkout points at another PR, report the mismatch in the target identity line and ignore the other PR unless it blocks local cleanup. - For
gh, identify any active PR babysit/watch automation for the target PR or branch when automation state is visible. Record its id so it can be updated or deleted after the PR lands. - For
gh, enteringauto-merge pendingitself authorizes the monitoring needed to finish$fin. Do not require a second user request before creating a heartbeat or falling back to a foreground watch. Monitoring never waives the locked target identity, checks, reviews, conflicts, or any other blocker. - If the task includes a requested external notification after green CI, such as Slack or another chat notice, track that notification as a separate finalization gate from PR mergeability and CI. Check notification prerequisites when feasible, such as required local credential files or configured CLIs, and record any missing prerequisite as a notification blocker, not as a CI failure.
- If the target PR is already merged into the verified origin main branch, treat the PR landing precondition as satisfied and skip mergeability repair. Continue with any matching spec archival, worktree cleanup, local
mainrefresh, and retrospective. - For
gh, when the target PR is still open, confirm it is mergeable against the verified origin main branch.- If GraphQL or
gh pr viewreportsUNKNOWN/indeterminate mergeability while checks and reviews otherwise look green, poll the REST pull-request endpoint once or twice formergeableandmergeable_statebefore invoking conflict repair. Treat RESTmergeable: truewithmergeable_state: cleanas the mergeability confirmation. If repeated REST results remainnull/unknown, record the state as bounded-indeterminate and allow the merge attempt to continue when the exact PR head identity is known and there is no positive evidence of conflicts or unmergeability. Treat the repository merge operation as the authoritative final mergeability check.
- If GraphQL or
- For
local, confirm the current branch is mergeable into the verified local origin main branch. - If the
ghflow is blocked only by base-branch conflicts, runtrigger:fix-pr-conflictagainst the locked target PR and let it try to restore a clean merge state. - If the
ghflow is blocked by broader PR issues, or conflict repair needs a fuller pass, runtrigger:fix-pragainst the locked target PR. - If the
localflow is blocked only by trunk drift, runtrigger:sync-branchor otherwise rebase the current branch onto the merge target before retrying the check. - Continue after mergeability is confirmed, the matching PR is already merged, or a
ghtarget meets the bounded-indeterminate conditions above. If repair cannot clear positive evidence of conflicts or unmergeability, stop and report the blockage instead of archiving the spec or landing the change.
Automatic Required-Review Bypass
- In
ghcontext, automatically use a repository-supported administrator or maintainer bypass when missing required approvals (including code-owner or last-push approval) are the only remaining merge blocker. Invokingfinauthorizes this narrow bypass; do not ask for additional confirmation. - Before bypassing, verify the exact PR head, a passing default-branch gate, no merge conflicts, and successful required checks for that head. If required checks cannot be determined, are missing, pending, or failing, do not automatically bypass. Confirm that no unresolved review threads, changes-requested reviews, or other merge blockers remain; a generic
BLOCKEDstatus alone is insufficient evidence. - Preserve all normal completion, spec, downstream-PR, permission, file-preservation, and cleanup gates. Honor explicit user or repository instructions prohibiting bypass, including any RIPP auto-merge-only path. Do not change repository rules, bypass lists, or permissions to make this operation succeed.
- Report the locked target and missing approvals being waived, then use
gh pr merge <number> --repo <owner/repo> --admin --match-head-commit <full-head-sha>with the supported merge method. If GitHub rejects the bypass, report the exact remaining restriction; do not broaden the override. Verify the actual merged state before cleanup. - Record the automatic required-review bypass, exact head, waived approvals, and merge method in the final report. Other blocker overrides still require the explicit authorization below.
Explicit Blocker Override
- Outside Automatic Required-Review Bypass, when finalization stops on named non-conflict blockers and the user explicitly says to merge or land while ignoring those blockers, treat that response as an auditable override limited to the blockers already reported for the locked target.
- Restate the target identity and the exact waived blockers before proceeding. Do not infer an override from a generic approval, an earlier broad permission, silence, or a request that does not clearly authorize landing.
- An override may waive failing or pending checks, review/proof/approval gates, waiting periods, and incomplete-spec landing gates. It does not waive a missing or failed default-branch gate, a non-main merge target, an unmergeable/conflicting target, a target mismatch, unknown commit identity, dirty-worktree preservation, malformed or failed final hooks, missing repository permission, or post-merge verification and cleanup.
- Keep incomplete specs and milestones active and unarchived. Do not mark them complete merely to satisfy the normal archival-before-landing order. Record the spec exception in the final report.
- In
ghcontext, re-confirm the exact PR head identity and require no positive evidence of conflicts or unmergeability, then use a repository-supported administrator or maintainer override merge. If merge commits are disallowed, retry once with the supported squash method. An indeterminate mergeability value alone does not prohibit the override; never use the override to merge a conflicting or explicitly unmergeable head. - Preserve the waived state in the final report: who authorized it, which blockers were ignored, whether an administrator merge was used, and which spec artifacts intentionally remained active.
Downstream Pull Request Protection
- Before merging or enabling auto-merge, lock the target PR's exact repository, head branch, and base branch. Discover open PRs that depend on that head branch with
gh pr list --repo <owner/repo> --state open --base <head-branch> --limit 1000 --json number,url,headRefName,baseRefName. - Read the repository's automatic branch deletion policy with
gh api repos/<owner/repo> --jq '.delete_branch_on_merge'. Treat a failed, incomplete, or ambiguous dependent-PR or deletion-policy lookup as a blocker; never assume a branch is safe to delete. - If no open PR uses the locked head branch as its base, proceed with normal merge and remote branch cleanup.
- If dependent PRs exist and
delete_branch_on_mergeisfalse, merge without--delete-branch, preserve the remote head branch, and report the dependent PR numbers and retained branch. The parent's local branch or worktree may still be cleaned up after normal landing proof. - If dependent PRs exist and
delete_branch_on_mergeistrue, stop before merging or enabling auto-merge: repository policy would remove their base even without--delete-branch. Ask whether to retarget every dependent PR to the parent's verified base, or leave the parent unmerged. - Retarget a downstream PR only with explicit user authorization. Run
gh pr edit <dependent-number> --repo <owner/repo> --base <parent-base>for each authorized dependent, then repeat the exact-base discovery and require zero remaining open dependents before merging or enabling auto-merge. - Immediately before any later explicit remote branch deletion, repeat the exact-base dependent-PR query. Delete only when it succeeds and returns zero open dependents; otherwise retain the remote branch and report the open PRs or lookup blocker.
- Do not change repository settings, close a downstream PR, or infer retargeting approval from a general request to merge, finalize, or clean up.
PR Automation Lookup
- Resolve matching automation by exact automation id first, then by the locked repository, PR number, and head branch. Classify ordinary babysit/watch automation separately from a
fin: auto-merge ...heartbeat; the latter is owned exclusively by the auto-merge lifecycle in step 4. - Bound the automation service lookup to one request and at most 30 seconds of waiting. Do not let an unavailable automation service stall finalization indefinitely.
- If the service lookup errors or times out, search
${CODEX_HOME:-$HOME/.codex}/automationsfor the exact automation id, PR number, or head branch. Treat this as read-only fallback evidence; never delete automation files directly. - When the fallback finds a persisted ordinary babysit/watch automation, retry one service lookup/delete operation using its exact id. For a
fin: auto-merge ...heartbeat, retry lookup or update only; never delete it through this generic cleanup path. If the allowed operation still fails, report automation cleanup as blocked with the id and error. - When neither the bounded service lookup nor the exact registry search finds a match, report that no matching persisted automation was present and name the evidence used. Distinguish this from a service-only lookup failure.
Linear Issue Lookup (Best Effort)
- Read
~/.agents/profiledirectly. Treat the profile as work only when it contains a trimmed, non-comment line exactly equal toname=work. For a non-work profile, skip Linear unless the user or current task context explicitly identifies a Linear issue to finish. - If the profile or active thread id is unavailable, skip Linear with a brief reason and continue finalization.
- For a work profile, resolve the active Codex thread id from the current app/session context, using
dev.llm-sessionwhen needed. Use the installedlinear:linearconnector skill and its connected Linear tools; do not uselinear-clior a browser fallback. - Let
thread_linkbe the exact deep linkcodex://threads/<thread-id>. Search withlist_issues(query=thread_link, assignee="me", includeArchived=true)first. If that produces no exact match, run one unscopedlist_issues(query=thread_link, includeArchived=true)search and follow its pagination until exhausted. If the connector cannot exhaust the candidate set, treat lookup as ambiguous instead of guessing. - Treat search results only as candidates because Linear search is fuzzy. Keep bulk connector payloads out of the transcript: parse them in the tool-composition layer and emit only candidate ids needed for verification. For every candidate whose complete description is unavailable or marked truncated, call
get_issuebefore comparison. Retain only issues whose complete description containsthread_linkexactly, and expose only each exact match's id, team, status, status type, and URL. - Accept only an exact
thread_linkmatch or an issue the user explicitly identified as the ticket for this task. Do not infer linkage from a search hit, similar title, branch name, PR number, assignee, or project. - Treat statuses with type
backlog,unstarted, orstartedas pending. Treatcompletedandcanceledissues as terminal and leave them unchanged. - When exactly one linked pending issue exists, lock its issue id and team for the rest of the run. When no linked pending issue exists, record that Linear completion is not applicable and continue.
- When more than one linked pending issue exists, do not update any of them. Record the matching issue ids as a non-blocking ambiguity note and continue finalization without guessing.
- If the connector is unavailable, unauthenticated, errors, times out, or returns an ambiguous result, skip the remaining Linear work and report the reason briefly. Do not wait for reauthentication, require the user to repair Linear, or retry through another client. Continue landing, cleanup, retrospective, and completion reporting.
- Resolve the active spec
- Build an ordered list of candidate docs roots:
- First, use
DOCS_ROOTwhen it is configured. - Next, if the matching
~/.fin.yamlinstructions mention an absolute artifact root for$specy,$mem, or notes, use that configured root directly. Do not infer or append/main; aggregate$memroots already name the complete managed root. - Finally, default to
./docs.
- First, use
- Only use candidate roots that exist or whose parent exists and is clearly the intended workspace artifact root. Report the selected spec root when a matching spec is found.
- Follow
specy's layout rule for each candidate root: active specs live directly under$DOCS_ROOT/specs/. - Only treat files directly under a candidate
$DOCS_ROOT/specs/as active specs. Ignore files already under$DOCS_ROOT/specs/.archive/. - Also support folder specs when the workspace uses a folder schema such as
specs: an active spec folder lives directly under a candidate$DOCS_ROOT/specs/<spec-slug>/, with optional sidecars such asmilestones/,flows/,reports/,cook/,checklist.md, ordata/. Treat the folder as the active spec unit. - If the completed work is a milestone or sidecar inside an active folder spec, do not archive the parent folder unless the parent spec itself is complete. If sibling milestones remain, record that the milestone landed and leave the parent folder spec active.
- If multiple active specs exist across candidate roots, archive only the one that directly matches the completed task and leave unrelated active specs untouched.
- If no active spec exists for this task, state that explicitly and continue.
- Mark the spec complete and archive it
- Update the active spec so its status clearly reflects completion before moving it. For folder specs, update the folder's
spec.md. - For a completed milestone or sidecar inside an otherwise active folder spec,
mark or report only that milestone as complete when the file has a clear
status field or checklist. Do not move the parent folder into
.archive/until the whole folder spec is complete. - Preserve the existing filename for single-file specs and the existing folder name for folder specs.
- Move the completed spec to
$DOCS_ROOT/specs/.archive/, creating the directory if needed. For folder specs, move the whole folder so sidecars such aschecklist.mdanddata/stay with the completed spec. - Follow
specy's convention exactly: when a single-file spec is complete, move it to$DOCS_ROOT/specs/.archive/and keep the same filename. - Do not delete completed specs.
Deterministic Local Cleanup
- Use
$dev.worktrees cleanup-landedas the only executor for destructive local worktree and local-branch cleanup duringfin gh/fin local. The explicitnocheckworkflow owns its separate ordinary removal path. Do not reproduce the landed executor's reset, clean, removal, orphan recovery, or branch deletion steps manually. - Before invoking it:
- confirm the PR or local change actually landed;
- run every matching
~/.fin.yamlfinal hook; - refresh or verify the local base ref and prove it contains the landed commit;
- inventory ignored or untracked files required by repository guidance,
including
.envfiles and root or nestednode_modules; preserve them in the retained checkout without exposing secrets, overwriting existing content, or installing dependencies; and - record the exact target worktree path, branch or detached state, and expected pre-cleanup HEAD.
- When checking preserved dependency graphs, invoke installed tools directly if package-manager scripts might reconcile workspace state or install packages. Stop before cleanup if required preservation or safe verification fails.
- Pass the exact full commit OIDs, refreshed local base branch, target identity, and actual merge mode to
$dev.worktrees cleanup-landed. - Require its dry run to report
readyornoop, then rerun the exact command in execute mode. - Treat
blocked,partial, a nonzero exit, identity drift, or failed postconditions as a finalization blocker. Preserve its journal and rerun the exact command after resolving the blocker. - Require final status
completeornoop, with the target path, worktree registration, and local branch absent and the base still containing the landed commit.
gh Context Workflow
- Merge the PR
- Before running a merge command, check the target PR state with GitHub. If the PR is already
MERGED, do not runtrigger:merge-pr; record the merge commit or merged-at details when available and proceed as an already-landed PR. - Before any merge command or auto-merge request for an open PR, complete Downstream Pull Request Protection using the locked repository and head branch. Pass
--delete-branchonly when its current exact-base query proves there are no open dependents. - If the PR is not already merged and the target PR belongs to the current branch, run
trigger:merge-primmediately after the matching spec has been marked complete and archived. - If the PR is not already merged and the explicit target PR does not belong to the current branch, use a target-aware remote merge for that PR, such as
gh pr merge <target>, after the matching spec has been marked complete and archived. Do not use current-branch merge shortcuts for a different PR. - Under an explicit blocker override, skip the archival prerequisite only for incomplete matching specs, leave them active, and use the target-aware override merge defined above.
- Treat the merge as part of finalization, not a follow-up option.
- If missing required approvals are the only remaining blocker, apply Automatic Required-Review Bypass before stopping for approval.
- If direct merge is rejected because repository policy requires auto-merge, and checks/reviews are otherwise green, enable repository-supported auto-merge for the locked target PR instead of treating the rejection as a terminal merge failure.
- After any successful auto-merge enablement, query the target PR for
autoMergeRequest,state,mergedAt,mergeCommit,mergeStateStatus, and status checks. - Treat
autoMergeRequestpresent with the PR stillOPENasauto-merge pending, not asblocked, while checks remain green and no explicit cancellation or failing required check is present. - Treat
auto-merge pendingas a nonterminal$finstate. Do not describe finalization as partial or complete, close or archive the task, send a terminal notification, run post-merge cleanup, or return a terminal final report while the locked PR remains in this state. - As soon as the first live query confirms
auto-merge pending, create or update exactly one dedicated thread heartbeat namedfin: auto-merge <owner>/<repo>#<number>before beginning any bounded foreground polling. Reuse an existing exact match instead of creating duplicates. Use the first-classautomation_updateapp tool: search the available tools for that exact capability when it is not already loaded. Do not infer that automation is unavailable merely because${CODEX_HOME:-$HOME/.codex}/automationsis absent or has no matching entry, and do not substitute a standalone cron automation. - Require the heartbeat to target the same
$fintask so it can resume the deferred workflow. Store the locked repository, PR number and URL, base and head branches, exact head SHA, merge method,autoMergeRequest.enabledAt, the deferredfinstage, and instructions to query live state and resume at step 5 only after verified merge. Schedule its next wake promptly and keep it active while the same head remains safely pending. - After heartbeat creation, the current wake may continue polling the locked PR for a bounded window. If the PR becomes
MERGED, verifymergedAtandmergeCommitand continue with normal post-merge work. Otherwise report only a nonterminal monitoring handoff with the heartbeat id and next wake; keep$finactive and do not call the handoff partial finalization. - On every heartbeat wake, query the locked PR live for
autoMergeRequest,state,mergedAt,mergeCommit,mergeStateStatus, head SHA, and required checks. While the same head remainsOPENwith auto-merge present and no terminal blocker, keep the heartbeat active, reportauto-merge pending, and leave post-merge cleanup, Linear completion, and full-finalization wording deferred. - When a heartbeat wake observes live
MERGEDstate with non-emptymergedAtandmergeCommit, mark the heartbeat as post-merge/resuming and resume this workflow at step 5 in the same wake. Do not delete the heartbeat yet. Delete it only after steps 5-9 reach a terminal post-merge outcome, including exact reporting of any post-merge blocker, so a merge cannot strand unfinished refresh, cleanup, Linear, or retrospective work. If the auto-merge request is removed or cancelled before merge, the head changes unexpectedly, a required check fails, or conflicts appear, pause the heartbeat as blocked without cleanup and report the exact evidence. - If the
automation_updatecapability is genuinely absent after tool discovery, or heartbeat creation/update fails, continue automatically with a foreground watch instead of ending the run. No additional user request is required. Report the automation error as monitoring evidence, but do not treat heartbeat unavailability as a terminal or partial-finalization condition. Retain the locked repository, PR number, head branch, and exact head SHA; poll the same PR live with no more than 60 seconds between checks; and publish one concise progress update at least every 60 seconds. - During a foreground watch, query
autoMergeRequest,state,mergedAt,mergeCommit,mergeStateStatus, head SHA, and required checks on every poll. Continue until the locked head isMERGEDwith non-emptymergedAtandmergeCommit, then resume at step 5. Stop only for a terminal blocker: the auto-merge request is removed or cancelled, the head identity changes, a required check fails, conflicts or unmergeability appear, or live polling can no longer continue. Report the exact blocker and preserve deferred cleanup. Never infer or use an administrator, approval, or blocker override from a request to watch. - Treat auto-merge as blocked only when GitHub reports the auto-merge request was removed/cancelled, a required check fails, conflicts appear, or the PR is otherwise no longer mergeable.
- If the merge command reports that the local branch cannot be deleted because it is still attached to a linked worktree, check whether the remote PR actually merged before treating the step as failed.
- If
gh pr merge --delete-branchfails only in its implicit post-merge branch switch and pull, such aserror: cannot pull with rebase: You have unstaged changeson a checkout with unrelated dirty files, do not treat that as a merge failure and do not stash or discard the dirty files. Confirm the PR isMERGEDremotely, then complete the step-5 refresh withgit fetch <remote> <base>followed bygit merge --ff-only <remote>/<base>; a fast-forward is safe alongside dirty files the landed commits do not touch. The pre-refresh working tree may transiently look as if the landed feature was reverted; that is the stale local base, not lost work. - When the PR merged remotely but local branch deletion failed only because of the linked worktree attachment, treat that as a successful merge followed by incomplete cleanup and continue with the deterministic cleanup script from a retained checkout.
- If there is no matching PR to merge, state that explicitly instead of claiming the task fully landed.
- For an explicit target PR that is not checked out locally, skip local branch/worktree cleanup unless the target branch is present in a local worktree or the user explicitly requested cleanup. It is acceptable to merge the remote PR, refresh local
main, delete matching ordinary babysit/watch automation, and report that no local target checkout cleanup was applicable. Retain anyfinauto-merge heartbeat until the terminal post-merge outcome.
- Refresh and verify the local base ref
- Before destructive cleanup, run any matching repo-specific final hooks from
~/.fin.yaml. If a hook fails, stop and preserve the worktree and branch. - After the PR merges, or after confirming it was already merged, update the local base ref before cleanup. Run this refresh from a retained checkout, never from the soon-to-be-removed worktree.
- If the target PR was explicit, heartbeat-derived, or active-task-derived and the non-worktree checkout is on an unrelated active branch, prefer a non-switching base-ref refresh before checking out
main: rungit fetch <remote> <base>:<base>only when<base>is not checked out in any worktree, then confirm containment withgit merge-base --is-ancestor <mergeCommit> <base>. - If the non-switching base-ref refresh is rejected because
<base>is checked out elsewhere or the ref can
…(truncated)