| name | org-pull-request |
| description | After the user approves a worker's completion report, the Lead handles push / PR creation / CI monitoring / review-feedback loop / final close after PR merge. Triggers: (1) immediately after a worker submits a completion report and the user gives explicit approval such as "OK" or "go ahead", (2) when review feedback / CI failure arrives on the GitHub PR and you need to send fix instructions back to the worker, (3) when the PR is merged and the final close conditions are met. The initial "delegate work to a worker" step is handled by org-delegate, not by this skill.
|
| effort | medium |
| allowed-tools | ["Read","Bash(git push:*)","Bash(git -C * worktree remove:*)","Bash(git worktree remove:*)","Bash(gh pr create:*)","Bash(gh pr view:*)","Bash(gh pr checks:*)","Bash(gh issue create:*)","Bash(gh issue edit:*)","Bash(bash tools/journal_append.sh:*)","Bash(py -3 tools/journal_append.py:*)","Bash(python tools/set_run_pr_open.py:*)","Bash(py -3 tools/set_run_pr_open.py:*)","Bash(python tools/run_complete_on_merge.py:*)","Bash(py -3 tools/run_complete_on_merge.py:*)","Bash(bash tools/pr-watch.sh:*)","Bash(pwsh tools/pr-watch.ps1:*)","Bash(powershell tools/pr-watch.ps1:*)","mcp__renga-peers__send_message","mcp__renga-peers__check_messages"] |
org-pull-request: PR creation, review, and post-merge close
Covers worker completion report -> user approval -> push / PR creation / CI monitoring / review-feedback loop / final close after PR merge. Lead only. The precondition for invoking this skill is that the worker has submitted a completion report and the user has given explicit approval. The pre-approval phase (ack issuance, transition to REVIEW, reporting to the user) is covered by .claude/skills/org-delegate/SKILL.md Step 5 (2a).
T5 contract: The canonical spec for the awaiting_review -> complete transition handled by this skill is
docs/contracts/delegation-lifecycle-contract.md §2 T5 / T6 / §1.5 close-condition.
That contract is the SoT that pins close-condition / pane discipline / no-respawn.
This SKILL owns the procedure; the contract owns the invariants.
ack != user approval: By the time this skill is invoked, ack has already been issued (.claude/skills/org-delegate/SKILL.md Step 5 step 1 / .claude/skills/org-delegate/references/ack-template.md). push / gh pr create / /pr-watch-pane are issued only after user approval.
Transport layer both systems (ORG_TRANSPORT: default renga / opt-in broker): this skill's mcp__renga-peers__* calls (fix-instruction send_message to the worker, etc.) and pr-watch's peer notifications are written for default renga and can be followed as-is when ORG_TRANSPORT is unset (default behavior unchanged). Under ORG_TRANSPORT=broker (opt-in, revertible), the fully qualified names get machine-substituted to mcp__renga-peers__* → mcp__org-broker__*, and the receipt of CI_COMPLETED / PR_MERGED etc. is also push-primary under broker (the channel sidecar server:org-broker-channel injects into idle via notifications/claude/channel; runtime push-first 0.1.24+, transport-lab docs/design/broker-native-roles.md §9). On push failure the fallback is the Lead actively check_messages at the start of each turn (see the receive-model note in 2b-i below — §9.6). Errors gain the broker-specific codes (see the broker section in .claude/skills/org-delegate/references/renga-error-codes.md). See CLAUDE.md "Transport layer (transport) both systems" and docs/contracts/backend-interface-contract.md Surface 8 (ratified 2026-06-14; the push-primary additive amendment S3 is ratified 2026-06-15, with existing ratified text unchanged) for details. The default-renga procedure is unchanged (broker is additive).
2b-i. PR creation phase (run immediately)
Triggered immediately after the user gives explicit approval such as "OK", "looks good", "no issues", or "go ahead":
- The Lead performs push / PR creation as needed (the worker does not have
git push / PR-creation permissions). The language convention for PR bodies follows feedback_pr_issue_english (PRs / Issues in English).
- As soon as the PR number is confirmed, immediately back-fill
runs.pr_url / runs.branch with tools/set_run_pr_open.py (Issue #323):
python tools/set_run_pr_open.py --task-id <task_id> --pr <PR>
This calls gh pr view <PR> --json url,headRefName once and overwrites runs.pr_url and runs.branch via StateWriter.set_run_pr. Re-invocation is idempotent (overwrites with the same values, no events appended). Without this, the later tools/run_complete_on_merge.py cannot look up runs.pr_url, fails with no_run (exit 3), and the -MergeWatch auto-completion breaks.
- Append events to the DB events table (push / PR open etc.,
bash tools/journal_append.sh ...).
- Once the PR number is confirmed, monitor CI with
/pr-watch-pane <PR> (add --repo OWNER/REPO for cross-repo). In a dedicated pane of the broker tmux session, tools/pr-watch.sh runs at ja-root cwd, outside the sandbox, and on completion ci_completed is automatically recorded in events. On CI completion / merge / timeout the pane auto-closes and does not synchronously occupy the Lead session (proceed to review-feedback loop 2c or manual close 2b-ii). Do not invoke tools/pr-watch.* directly via Claude Code's Bash background launch: a background task tracks only the spawning shell, and the self-detached monitoring body is orphaned and silently dies on /clear / session end (official spec; the old route actually had merge-watch die silently). The dedicated-pane route avoids this orphaning.
- Receiving CI completion / merge detection / 24h timeout is multi-layered (CI-watch zero-miss, Refs #653 #658). The terminal signals (
CI_COMPLETED / PR_MERGED / PR_MERGE_WATCH_TIMEOUT / PR_MERGED_NO_RUN / PR_MERGED_HEAD_UNCONFIRMED / PR_WATCH_ABORTED) all take the canonical event in the events table as the source of truth, and its delivery is received in the following order (the peer-only terminal signal has been abolished — a canonical event always precedes):
- (B) Dispatcher relay = the zero-miss main guarantee (primary receive path): in its
/loop 3m monitoring-cycle relay-scan step (.dispatcher/references/worker-monitoring.md Step 5.25), the dispatcher scans for undelivered terminal events by cross-checking against the event_deliveries delivery ledger, and relays them to the Lead via mcp__org-broker__send_message on a broker token. The Lead advances on arrival of this relay message (distinguishable from a direct push by the trailing [relay] in the body, e.g. CI_COMPLETED: PR #<n> (status=passed, head=<sha>) [relay]). Even if the pr-watch pane's direct push (A below) silently no-ops due to missing env, this relay reads events directly and therefore delivers reliably (the root fix for the PR #73 failure: it plugs the terminal case where a generic spawn pane put zero CI_COMPLETED into the broker queue and the Lead sat idle). at-least-once (duplicate suppression via the ledger idempotency key; double receipt is handled idempotently).
- (A) Direct push from pr-watch = the low-latency aid: in an
ORG_TRANSPORT=broker pane, pr-watch sends CI_COMPLETED etc. by broker channel sidecar push at the moment CI is decided (the per-pane channel sidecar server:org-broker-channel injects into the Lead's idle session via notifications/claude/channel; runtime push-first 0.1.24+, channel source is org-broker). If it arrives, you may proceed without waiting for the relay, but this is best-effort (tools/peer_notify.py decides on raw env, so there are paths where it silently falls through on missing env / broker daemon not running / sidecar unhealthy). On push failure the Lead actively pulls with mcp__org-broker__check_messages at the start of the turn (§9.6). When a push fails, pr-watch records a notify_failed event fail-loud, which itself also becomes a target of the (B) relay (silent no-op fully abolished).
- (Fallback) Direct poll of the events table: when neither relay nor push stands up (a plain shell / CI with the broker daemon not running, etc.), the Lead reads the events table directly as before (see the "canonical path for CI-completion detection" section below). Because the canonical event is identical at every layer, the decision material (
head + status) matches wherever it is received.
- Branching per terminal signal (invariant regardless of receive layer):
CI_COMPLETED received -> ask the user for merge approval -> user approval -> PR_MERGED received -> go to 2b-ii post-merge cleanup. PR_MERGED_NO_RUN is a failure case where merge was observed but no corresponding run row was found (the no_run terminal state of tools/run_complete_on_merge.py); do not proceed to post-merge cleanup — handle by human judgment. PR_MERGED_HEAD_UNCONFIRMED likewise does not proceed to post-merge cleanup and falls to the human-confirmation gate (the terminal case where a push and a merge slip in simultaneously between pr-watch's polls and the PR is merged at a CI-unconfirmed head — loopback is impossible so it is notified fail-closed with exit 9. The body form is PR_MERGED_HEAD_UNCONFIRMED: PR #<n> (head=<merged_short>, last CI-confirmed head=<baseline_short>). For the Lead's handling procedure / human-presentation text / journal kind / severity, see the "On receiving PR_MERGED_HEAD_UNCONFIRMED" section below). PR_WATCH_ABORTED is the terminal case where the watcher exited abnormally on an unexpected exception; notify the human of the monitoring break and prompt a manual poll / restart.
- Under
ORG_TRANSPORT=renga (opt-in): the (A) direct push becomes the in-band push of <channel source="renga-peers"> CI_COMPLETED: PR #<n> ..., and the (B) dispatcher relay goes via mcp__renga-peers__send_message (the relay layer stands up regardless of transport). The message body semantics / branching (including the PR_MERGED_HEAD_UNCONFIRMED human gate) are identical to broker. In plain shells / CI without RENGA_SOCKET set, the direct push is a silent noop and falls back to the (B) relay or the events-table poll.
- Right before "CI_COMPLETED received -> ask user for merge approval", emit an awaiting_user notification (Issue #28): this tells the attention watcher that the user is stopped while awaiting merge approval:
bash tools/journal_append.sh notify_sent kind=awaiting_user task_id=<task_id> gate=ci_green_merge_gate note="PR #<PR> CI green, awaiting merge approval"
The classifier in the parallel runtime PR picks this up as secretary_awaiting_user (default severity urgent). See the "notify when the secretary is waiting on a user decision" section of CLAUDE.md. Failure cases such as PR_MERGE_WATCH_TIMEOUT are out of scope (those go to human judgment via a different path, not awaiting_user).
- Only when you want to wait for merge, pass
-MergeWatch (PowerShell) / --merge-watch (POSIX). After CI passes, it polls gh pr view --json mergedAt for 24h and calls tools/run_complete_on_merge.py on the first observed merge (Issue #317). During merge-watch the pr-watch process stays alive; on merge observation it appends a pr_merged event to events and then returns.
- run.status stays at REVIEW (so that GitHub PR review feedback can be handled in the same pane; the transition to COMPLETED happens in 2b-ii via
update_run_status('<task_id>', 'completed')). Do not edit markdown directly.
- Do not close the pane yet: do not send
CLOSE_PANE immediately after PR creation. worktree removal and Worker Directory Registry updates are deferred until 2b-ii.
- If PR review feedback arrives, follow flow 2c and send a
send_message follow-up instruction to the same worker so they push fix commits in the same pane (avoid respawning a new worker — you would pay the cost of rebuilding Issue / diff / judgment boundaries).
- For dogfood-target PRs (Issue #338): in
registry/dogfood_pending.md, find the row for the relevant task_id with status=pending, then (a) fill in impl_pr=#<PR>, (b) create the paired follow-up issue with gh issue create --title "dogfood follow-up: <surface>" --body-file <rendered template> (template: .claude/skills/org-delegate/references/dogfood-issue-template.md), (c) fill in the resulting issue number as dogfood_issue=#<MMM> and transition status from pending -> open, (d) append Paired dogfood issue: #<MMM> to the end of the PR body. The SoT for the full protocol is .claude/skills/org-delegate/SKILL.md Step 1.8.
CI-completion detection, the canonical path: the events table is canonical, the dispatcher relay is the primary delivery, the events-DB poll is the last fallback (Issue #653 / Refs #658)
The canonical signal for CI completion is the ci_completed row in the events table (the payload_json matches the target PR, head matches the pushed SHA, status='passed'|'failed'), not the CI_COMPLETED peer direct push touched on in the 2b-i receive model above (the in-band push of <channel source="renga-peers"> / the broker channel sidecar's notifications/claude/channel injection). The direct push is a best-effort aid (path A): tools/peer_notify.py: notify_peer is a raw-env-decision helper, so there are paths where it becomes a silent no-op — with ORG_TRANSPORT unset (the operational default) and no RENGA_SOCKET on a plain shell / the broker daemon not started / the channel sidecar unhealthy, etc. (the same circumstances as the transport note at the top of the SKILL; in PR #73 the Lead actually dropped a CI-green).
The zero-miss main guarantee is the dispatcher relay (path B, Refs #658): in its /loop 3m monitoring cycle (.dispatcher/references/worker-monitoring.md Step 5.25), the dispatcher cross-checks events against the event_deliveries delivery ledger and send_messages undelivered ci_completed to the Lead with a [relay] marker via a broker token. In normal operation the Lead just proceeds on this relay arrival and does not need to actively poll the events table. The direct poll of the events DB remains, per the procedure below, as the last fallback for when neither relay nor direct push stands up (a plain shell / CI with the broker daemon not running, etc.) (because the canonical event is identical at every layer, the decision material head + status matches). Treating only "a push arrived" as ground truth drops CI green on a no-op path, so treat the direct push only as early notification and always confirm via the events row (relay receipt or direct poll).
Recommended poll procedure for ground truth:
- From ~60s after
git push origin <branch>, periodically poll the events table (recommended 60-90s interval; this repository's CI settles in roughly 60-80s). If the pr-watch pane is alive, the watcher writes the ci_completed row to events regardless of whether a push notification fired (the ci_completed write path of tools/pr-watch.sh / tools/pr-watch.ps1).
- Example decision query (
PR_NUMBER is the int value the Lead already holds locally; it does not arrive via a user-input path, so a literal f-string expansion is fine):
from tools.state_db import connect
conn = connect('.state/state.db')
row = conn.execute(
"SELECT payload_json FROM events WHERE kind='ci_completed' AND payload_json LIKE ? ORDER BY id DESC LIMIT 1",
(f'%\"pr\": {PR_NUMBER}%',)
).fetchone()
- How to treat a push notification when it arrives: treat it as a supplementary early notification, and when it arrives, query the events table to confirm the head match and status. Do not stop the events-DB poll on the assumption that a push may not arrive. Once both the peer push and the events row have landed, base the decision on the events DB's
head + status as ground truth (this makes primary the same stance as the receive-model note's "polling the events table is the last fallback").
- The human gates for
PR_MERGE_WATCH_TIMEOUT / PR_MERGED_NO_RUN / PR_MERGED_HEAD_UNCONFIRMED are unaffected by this section (these are on the merge-observation side / fail-closed gates, and the events-DB poll makes only CI-completion detection canonical). The canonical detection of the merge observation itself is still done, as before, by the combination of pr-watch --merge-watch's pr_merged event and tools/run_complete_on_merge.py.
Warning: cwd when launching pr-watch
tools/pr-watch.sh / tools/pr-watch.ps1 / tools/pr_watch.py open state.db via a relative path, so if the cwd at launch is not the ja root, writing the CI-completion event will crash and peer notifications (CI_COMPLETED / PR_MERGED etc.) will not fire. The canonical route /pr-watch-pane deterministically resolves the ja-root absolute path and spawns the pane, so it absorbs the cwd trap (this cwd resolution is one of the pane route's main purposes). This caution applies only to the emergency fallback where a human manually launches tools/pr-watch.sh via !; in that case, always use the form cd <ja-root> && bash tools/pr-watch.sh <PR> .... A root fix (cwd-independence) is in progress in Issue #398.
Warning: run monitoring in a dedicated pane (do not make Bash background launch canonical)
The canonical route for the Lead to start CI monitoring is /pr-watch-pane <PR>, not the route of invoking tools/pr-watch.* directly via a Claude Code Bash tool background launch. A Claude Code background task tracks only the spawning shell, and the self-detached monitoring body (including nohup ... & + disown) is orphaned (official spec), so in a fresh session right after /clear / /secretary-resume or at session end the monitoring dies silently along with it, and neither the CI-completion event nor the peer notification fires at all (the process disappears quietly and only an empty log file is left, which is hard to notice). A Bash wrapper's completion notification (with exit code) does not mean the monitoring body completed: even after the wrapper returns, the orphaned monitoring may have died silently, so do not treat the completion notification as a signal of CI-monitoring completion — always confirm via the ci_completed row in the events table. The dedicated-pane route runs outside the sandbox and independently of the Lead session, so it structurally avoids this orphaning.
On receiving PR_MERGED_HEAD_UNCONFIRMED (human-confirmation gate, Issue #639 / pr-watch #638)
When you receive PR_MERGED_HEAD_UNCONFIRMED: PR #<n> (head=<merged_short>, last CI-confirmed head=<baseline_short>) from pr-watch, do not proceed to post-merge cleanup (2b-ii); ask the human for confirmation. This is the terminal case where a push and a merge slip in simultaneously between pr-watch's polls and the PR is merged at a CI-unconfirmed head (a merge is irreversible so loopback is impossible; pr-watch emits it fail-closed with exit 9; design in PR #638). It is an independent signal parallel to PR_MERGED / PR_MERGED_NO_RUN — do not auto-advance on the PR_MERGED prefix.
2c. Review feedback / CI failure feedback loop
If a human provides feedback / fix instructions, or if CI fails and the user says "have them fix it":
- Release the dispatcher's monitoring flag before the re-instruction (Issue #658, T6 monitoring-resume contract): If the worker has already reported completion (at completion, the secretary sent
WORKER_COMPLETION_NOTED to the dispatcher per §2a, so completion_reported_at is set), then before sending the additional instruction, send WORKER_REOPENED to the dispatcher best-effort and non-blocking, clearing completion_reported_at to null. Because the re-instruction is a direct secretary→worker send with the dispatcher off the path, this explicit release is the fast-path release for the dispatcher's PANE_OUTPUT_WITHOUT_PEER_MSG detector (.dispatcher/references/worker-monitoring.md Step 5.2 — maintained in the upstream ja repository, not mirrored here; see CONTRIBUTING "Mirror scope"). Do not wait for a dispatcher reply (the dispatcher reflects it on its normal /loop 3m check_messages). Without the release the skip stays sticky and misses a real silent dead-lock during the awaiting_review→in_progress review fix. Even if WORKER_REOPENED is dropped, the run.status='in_use' transition immediately below (written deterministically by StateWriter) acts as a reliable backstop: the dispatcher observes runs.status == 'in_use' and self-heal-resumes monitoring (so the release does not depend on the best-effort notice alone and does not fail unsafe, Issue #658 P2). Include task_id and reopened_at (ISO-8601 UTC) in the body:
mcp__renga-peers__send_message(to_id="dispatcher", message="WORKER_REOPENED: worker-<task_id> (task_id=<task_id>, reopened_at=<ISO-8601 UTC>)")
- Send additional instructions to the worker via renga-peers (
to_id="worker-{task_id}").
- If the additional instructions are a trivial fix (CI output formatting / typo / comment fix etc.), explicitly state verification depth
minimal and tell them to reply with only a single line done: {short commit SHA} {changed file names} (the format follows .claude/skills/org-delegate/references/instruction-template.md / .claude/skills/org-delegate/references/worker-claude-template.md).
- Move the run back to IN_PROGRESS via the DB (
run.status='in_use', do not edit markdown directly. The post-commit hook regenerates .state/org-state.md):
python -c "
from pathlib import Path
from tools.state_db import connect
from tools.state_db.writer import StateWriter
conn = connect('.state/state.db')
with StateWriter(conn, claude_org_root=Path('.')).transaction() as w:
w.update_run_status('<task_id>', 'in_use')
"
- Append events to the DB events table (
bash tools/journal_append.sh ...) (tools/journal_append.py is already routed to the DB).
- The JSON snapshot is automatically regenerated by the StateWriter post-commit hook (Issue #284).
- (The pane is alive, so the worker simply continues working.)
- Do not respawn a new worker (T6 contract): Issue / diff / judgment boundaries would be lost. Only when the worker becomes unresponsive does the Lead make that call.
Once a new completion report arrives from the worker, proceed again through .claude/skills/org-delegate/SKILL.md Step 5 (2a) -> user approval -> 2b-i of this skill.
2b-ii. Final close phase (run once the close conditions are met)
Close conditions (same as contract §1.5; at least one must be satisfied):
- The PR has been merged (confirmed by
gh pr view {n} --json mergedAt etc., or the Lead receives a merge notification, or notified via the pr_merged event of pr-watch --merge-watch).
- The user explicitly says "you can close it", "close it", "merged", etc.
- 24-48 hours of long idle with no review activity (left to the Lead's operational discretion; not automated).
Actions to perform:
- DB-update the relevant run to COMPLETED (use the
update_run_status('<task_id>', 'completed') block described below). Do not edit markdown directly.
- Final-update the worker's state file (append the last Progress Log entry, etc.).
- The worker state file (
.state/workers/worker-{task_id}.md) is automatically moved to .state/workers/archive/ by StateWriter's post-commit on update_run_status('<task_id>', 'completed') (Issue #284. archive/ is lazily created if absent; re-invocation is idempotent. The dashboard does not treat files in this directory as live workers (Issue #264). They are not deleted, in case the journal / retro needs to reference history.)
- Append events to the DB events table (
bash tools/journal_append.sh ...).
- Ask the dispatcher to close the pane:
CLOSE_PANE: please close pane {pane_id}.
- Post-processing per directory pattern (do at the same time):
- Pattern A (project directory): keep the directory (reused for the next task).
- Pattern B (worktree): run
git -C {workers_dir}/{project_slug}/ worktree remove --force .worktrees/{task_id}. Keep the branch (do not delete the branch even after merge, for PR history).
--force is intentional (Issue #491): gen_delegate_payload.py's apply leaves send_plan.json directly under worker_dir, and worktree remove always refuses a worktree that contains an untracked file, so without --force the close phase is guaranteed to fail. Auto-deleting send_plan.json during the close phase is a separate Issue (this skill absorbs it by demonstrating --force).
- For self-edit (
pattern_variant='live_repo_worktree'): the worktree base is {claude_org_path}, so run git -C {claude_org_path} worktree remove --force .worktrees/{task_id} (Issue #289). The reason for --force is the same as normal Pattern B. Keep the branch likewise.
- Pattern C (ephemeral,
pattern_variant='ephemeral'): keep the directory (consider manual deletion only if capacity becomes an issue).
- Special cleanup for Pattern C (
gitignored_repo_root, claude-org self-edit) (Issue #478): worker_dir is the claude-org repo root itself, so neither worktree remove nor dir removal applies, and {claude_org_root}/CLAUDE.local.md (the Worker-instruction brief) lingers. If it remains, on the next /org-start the Lead loads a contradictory "Lead and Worker" role identity. At close, call cleanup_pattern_c_local_md() from tools/run_complete_on_merge.py to delete the brief (bundled into the StateWriter block below). Detection is runs.pattern == 'C' AND worker_dir == claude_org_root, and it is a no-op for ephemeral C / Patterns A and B. One pattern_c_cleanup row (payload: task / removed_path / mode) is appended to events. Idempotent (mode=skip when the file is absent). Issue #486: when the remove_worker_dir() in the block below DELETEs the worker_dirs row, runs.worker_dir_id becomes ON DELETE SET NULL and the join-based worker_dir resolution turns NULL, making the cleanup a no-op; pass the deleted abs path explicitly via worker_dir_abs= to make it order-independent. When a PR-driven close calls tools/run_complete_on_merge.py --pr <PR>, the same cleanup runs automatically at merge-record time, but gitignored tasks rarely produce a PR, so the explicit call from the StateWriter block below is the primary route. .claude/settings.local.json is out of scope (a separate Issue) because it needs worker-origin vs. Lead-origin discrimination.
- When closing a dogfood-target PR's paired issue (Issue #338): because the implementation PR's merge and the paired follow-up issue's close can have independent lifecycles, this skill does not guarantee "do
consumed -> closed on implementation-PR merge". The terminal transition consumed -> closed is the Lead's register-hygiene responsibility and is collected via .claude/skills/org-delegate/SKILL.md Step 1.8 §"consumed -> closed observation timing" (on register write + at /org-resume startup, check paired-issue state with gh issue view). If this skill happens to observe the relevant row at PR-merge time, it may invoke the hygiene step opportunistically.
- For PR-driven closes, call
tools/run_complete_on_merge.py (Issue #317. The merge-watch loop of pr-watch --merge-watch invokes it automatically, so manual execution is normally unnecessary; call it explicitly only when merge-watch was skipped or when the merge was observed manually):
python tools/run_complete_on_merge.py --pr <PR>
This calls gh pr view <PR> --json url,state,mergedAt,mergeCommit,headRefName once and, if the PR is merged, updates pr_state='merged' / commit_short / pr_url / completed_at via StateWriter.transaction() and appends a single pr_merged event (payload: task / pattern / auto_completed). Re-invocation is idempotent (does not write a duplicate event). task_id is auto-resolved from runs.pr_url / runs.branch (restricted to active runs); if resolution fails, pass --task-id explicitly.
- The helper does not touch runs.status: dispatcher-side pane close / worker_closed / worker-state final update are required (delegation-lifecycle-contract §T5). The helper only records the merge fact; the status flip and worker_dir removal are done by the Lead via the StateWriter below.
- CLI exit codes:
merged / already / not_yet are exit 0; no_run (no matching row in runs) is exit 3 and counted as failure. Check the exit code when running manually.
- Pattern B / C registry-entry deletion and final close are done by a separate StateWriter call (do not edit markdown directly. run_complete_on_merge has already written
pr_state='merged' and completed_at, so here we only do the status flip and worker_dir removal):
python -c "
from pathlib import Path
from tools.state_db import connect
from tools.state_db.writer import StateWriter
from tools.run_complete_on_merge import cleanup_pattern_c_local_md
conn = connect('.state/state.db')
abs_path = '<abs>' # absolute path of worker_dir (Pattern B / C)
with StateWriter(conn).transaction() as w:
w.update_run_status('<task_id>', 'completed') # post-commit hook archives worker-{task}.md
w.remove_worker_dir(abs_path) # Pattern B / C only
# Issue #478 / #486: delete the CLAUDE.local.md of a Pattern C gitignored_repo_root run
# (actual deletion only when runs.pattern=='C' AND worker_dir==root; no-op otherwise).
# remove_worker_dir() DELETEs the worker_dirs row and runs.worker_dir_id is
# ON DELETE SET NULL, so join-based detection resolves to NULL and becomes a no-op.
# Pass the deleted abs_path explicitly via worker_dir_abs= so detection works whether
# called before or after the row deletion (Issue #486).
cleanup_pattern_c_local_md(conn, task_id='<task_id>', claude_org_root=Path('.').resolve(), worker_dir_abs=abs_path)
"
The legacy hand-rolled completion script is preserved at docs/legacy/pr-merge-completion-manual.md. The standard route is tools/run_complete_on_merge.py above; reaching for the museum copy is allowed only after opening an issue and getting user judgment (same pattern as PR #315).
- Pattern A: lifecycle stays
active; with run.status='completed' the snapshotter renders it as available-equivalent.
- Pattern B / C: the physical dir is handled separately (worktree remove / dir retention). For registry-entry deletion, add
w.remove_worker_dir('<abs>') inside the with block above.
- The JSON snapshot is automatically regenerated by the StateWriter post-commit hook (Issue #284).
2b-iii. Post-merge next-task proposals (proactive next-dispatch)
Once the 2b-ii post-merge cleanup (run -> COMPLETED, pane close, worktree post-processing) is done, the Lead proactively presents next-work candidates to the user without waiting to be asked. Candidate generation does not improvise gh issue list on the spot; it consumes the /work-discovery skill (= the triage output of the deterministic tool tools/work_discovery_scan.py). This codifies the decision criteria (dependencies resolved / priority / effort) and gives the presentation reproducibility and auditability. The primary policy reference is CLAUDE.md "Next-task proposals after a PR merge (proactive next-dispatch)"; the primary design reference is docs/design/work-discovery-triage.md §8 (post-merge integration).
- Run with the post_merge trigger: launch
/work-discovery in the post-merge context (scan with --trigger post_merge, and --free-panes <n> if there are free panes). The candidate JSON carries generated_for: "post_merge", and in post-merge the unblocked_by_recent_merge axis — which surfaces "Issues unblocked by the most recent merge / natural follow-ups" near the top — has the strongest effect (design §4.2 / §8.1-3). If there are free slots, the rank of parallelizable candidates is raised so the free slots are filled.
- Presentation and the human gate stay compatible with current behavior: the Lead renders the triage result in design §5.2 format (N candidates + 1 recommendation, estimated axes marked
(estimated), with an excluded slot shown) for the human. The human picks by number → the chosen candidate enters the normal delegation flow from Step 0 of /org-delegate. Only the means of candidate generation changes from improvisation to triage; the human's operations and the outer shape of the human gate stay the same (design §8.1-4).
- Stop in propose-only: once the candidates are presented, stop. Do not auto-start rank 1 (the recommendation), auto-commit, or auto-PR (only the human decides what to start; design INV-1 / INV-2). This step and
/work-discovery are also forbidden from calling org-delegate or spawning.
- For manual / on-demand presentation (idle time, etc.), launch the same
/work-discovery with --trigger manual. The exit-code branches and rendering rules within the skill are documented as the primary reference in /work-discovery.