Issue Work Command
Automate GitHub issue workflow with project name as argument.
Usage
/issue-work # Batch: all repos, all open issues
/issue-work vi_slam # Batch: all open issues in vi_slam
/issue-work vi_slam 21 # Single: work on issue #21
/issue-work vi_slam 21 --org mycompany # Explicit organization
/issue-work mycompany/vi_slam 21 # Full repo path format
/issue-work vi_slam 21 --solo # Force solo mode (sequential)
/issue-work vi_slam 21 --team # Force team mode (implementer + tester)
/issue-work --org mycompany # Batch: all repos in org
/issue-work vi_slam --limit 5 # Batch: top 5 priority issues
/issue-work vi_slam --dry-run # Preview batch plan only
/issue-work vi_slam --inline # Batch: process items in the parent context (legacy)
Arguments
[project-name]: Project name or full repository path (optional)- Format 1:
<project-name>- auto-detect organization from git remote - Format 2:
<project-name> --org <organization>- explicit organization - Format 3:
<organization>/<project-name>- full repository path - If omitted: Batch mode — discover all user repos and process all open issues
- Format 1:
[issue-number]: GitHub issue number (optional)- If provided: Work on the specified issue (single-item mode)
- If omitted with project: Batch mode — process all open issues in the project
- If omitted without project: Batch mode — process all open issues across all repos
[--solo|--team]: Execution mode override (optional)--solo— Force solo mode for all items (single agent, sequential workflow)--team— Force team mode for all items (implementer + tester agents in parallel)- If omitted in single-item mode: auto-recommend based on issue size, then ask user
- If omitted in batch mode: auto-decide per item using weighted scoring (no per-item prompt)
[--limit N]: Maximum number of items to process in batch mode (default: 5, max: 10)- Values above 10 require
--force-largeto acknowledge rule drift risk. Empirically, drift becomes visible around items 15-25 in long batches; the conservative default keeps batches inside the safe zone.
- Values above 10 require
[--force-large]: Allow--limit > 10. Required to bypass the safe-batch cap.[--no-confirm]: Skip the chunked confirmation gate fired every 5 items in batch mode. Intended for CI-driven or fully unattended batches; interactive sessions should leave it off so the gate can serve as both a user-control checkpoint and an attention refresh for accumulated context.[--auto-restart]: Force a session restart everyCONFIRM_INTERVALitems instead of showing the interactive chunked gate. The batch writes.claude/resume.mdusing the Batch Workflow Resume Format and exits cleanly; a freshclaudesession picks up the next item from the resume file. Use for long unattended batches where a full process-level attention reset per chunk matters more than human confirmation. Ignored in single-item mode.[--no-restart]: Suppress the forced restart. When combined with--auto-restart, the batch falls back to the interactive chunked gate. Meaningful primarily as a defensive flag in scripts that want to guarantee no session exit even if--auto-restartis set elsewhere (aliases, wrappers, or a future default change).[--dry-run]: Show batch plan only, do not execute[--inline]: Process each batch item in the parent conversation context instead of delegating to a fresh subagent.- Default (omitted): Each batch item is handled by a fresh
general-purposeAgent. The parent keeps only the queue state and a short per-item summary; gh outputs, build logs, and file reads live inside the subagent and are discarded on completion. This is the preferred mode for batches >3 items because rule compliance at item 30 looks like item 1. - With
--inline: The parent executes Solo/Team workflow directly for every item. Lower token overhead (~10-15% savings) but accumulated tool results cause rule drift around items 15-25. Use for tiny batches (≤3 items) or when inter-item context is actually useful (e.g., fixing related regressions). - Ignored in single-item mode.
- Default (omitted): Each batch item is handled by a fresh
[--priority <level>]: Filter batch to this priority level and above- Levels:
critical,high,medium,low,all(default:all)
- Levels:
[--wait-on-timeout]: On the CI-polling timeout (Step 9), ask the user whether to keep waiting instead of taking the default action. Default (omitted): when the 10-minute polling limit is reached with runs still pending, leave the PR open and print a resume command (pr-work <PR_NUM>) rather than blocking on a prompt. Pass this flag only when you want the interactive "wait longer?" question back.
Argument Parsing
Parse $ARGUMENTS and extract project, organization, issue number, and batch flags:
ARGS="$ARGUMENTS"
ISSUE_NUMBER="" PROJECT="" ORG="" EXEC_MODE=""
BATCH_MODE="single" BATCH_LIMIT=5 DRY_RUN=false PRIORITY_FILTER="all" FORCE_LARGE=false NO_CONFIRM=false INLINE_MODE=false AUTO_RESTART=false NO_RESTART=false WAIT_ON_TIMEOUT=false
MAX_LIMIT=10
CONFIRM_INTERVAL=5
# Extract flags
if [[ "$ARGS" == *"--solo"* ]]; then EXEC_MODE="solo"; ARGS=$(echo "$ARGS" | sed 's/--solo//g'); fi
if [[ "$ARGS" == *"--team"* ]]; then EXEC_MODE="team"; ARGS=$(echo "$ARGS" | sed 's/--team//g'); fi
if [[ "$ARGS" == *"--dry-run"* ]]; then DRY_RUN=true; ARGS=$(echo "$ARGS" | sed 's/--dry-run//g'); fi
if [[ "$ARGS" == *"--force-large"* ]]; then FORCE_LARGE=true; ARGS=$(echo "$ARGS" | sed 's/--force-large//g'); fi
if [[ "$ARGS" == *"--no-confirm"* ]]; then NO_CONFIRM=true; ARGS=$(echo "$ARGS" | sed 's/--no-confirm//g'); fi
if [[ "$ARGS" == *"--no-restart"* ]]; then NO_RESTART=true; ARGS=$(echo "$ARGS" | sed 's/--no-restart//g'); fi
if [[ "$ARGS" == *"--auto-restart"* ]]; then AUTO_RESTART=true; ARGS=$(echo "$ARGS" | sed 's/--auto-restart//g'); fi
if [[ "$ARGS" == *"--inline"* ]]; then INLINE_MODE=true; ARGS=$(echo "$ARGS" | sed 's/--inline//g'); fi
if [[ "$ARGS" == *"--wait-on-timeout"* ]]; then WAIT_ON_TIMEOUT=true; ARGS=$(echo "$ARGS" | sed 's/--wait-on-timeout//g'); fi
if [[ "$ARGS" =~ --limit[[:space:]]+([0-9]+) ]]; then BATCH_LIMIT="${BASH_REMATCH[1]}"; ARGS=$(echo "$ARGS" | sed -E 's/--limit[[:space:]]+[0-9]+//g'); fi
if [[ "$ARGS" =~ --priority[[:space:]]+(critical|high|medium|low|all) ]]; then PRIORITY_FILTER="${BASH_REMATCH[1]}"; ARGS=$(echo "$ARGS" | sed -E 's/--priority[[:space:]]+\w+//g'); fi
# Hard cap on batch size to mitigate rule drift in long batches.
# Drift becomes empirically visible around items 15-25; default 5 keeps the
# operator inside the safe zone, and bypassing requires explicit acknowledgment.
if (( BATCH_LIMIT > MAX_LIMIT )) && [[ "$FORCE_LARGE" != "true" ]]; then
echo "Error: --limit ${BATCH_LIMIT} exceeds safe cap of ${MAX_LIMIT}." >&2
echo "Long batches risk rule drift around items 15-25." >&2
echo "Either split the batch into smaller runs or pass --force-large to override." >&2
exit 1
fi
# Extract issue number if present (numeric argument)
if [[ "$ARGS" =~ [[:space:]]([0-9]+)([[:space:]]|$) ]]; then
ISSUE_NUMBER="${BASH_REMATCH[1]}"
ARGS=$(echo "$ARGS" | sed -E "s/[[:space:]]+${ISSUE_NUMBER}([[:space:]]|$)/ /g")
fi
ARGS=$(echo "$ARGS" | xargs)
# Helper: resolve ORG/PROJECT from remaining ARGS
resolve_org_project() {
if [[ "$ARGS" == *"--org"* ]]; then
PROJECT=$(echo "$ARGS" | awk '{print $1}')
ORG=$(echo "$ARGS" | sed -n 's/.*--org[[:space:]]*\([^[:space:]]*\).*/\1/p')
elif [[ "$ARGS" == *"/"* ]]; then
ORG=$(echo "$ARGS" | cut -d'/' -f1 | xargs)
PROJECT=$(echo "$ARGS" | cut -d'/' -f2 | xargs)
else
PROJECT="$ARGS"
cd "$PROJECT" 2>/dev/null || { echo "Error: Project directory not found: $PROJECT"; exit 1; }
ORG=$(git remote get-url origin 2>/dev/null | sed -E 's|.*[:/]([^/]+)/[^/]+\.git$|\1|' | sed -E 's|.*[:/]([^/]+)/[^/]+$|\1|')
fi
}
# Determine batch mode and resolve org/project
if [[ -z "$ARGS" && -z "$ISSUE_NUMBER" ]]; then
BATCH_MODE="cross-repo"
if [[ "$ARGS" == *"--org"* ]]; then
ORG=$(echo "$ARGS" | sed -n 's/.*--org[[:space:]]*\([^[:space:]]*\).*/\1/p')
fi
elif [[ -n "$ARGS" && -z "$ISSUE_NUMBER" ]]; then
BATCH_MODE="single-repo"
resolve_org_project
else
BATCH_MODE="single"
resolve_org_project
fi
- Repository:
https://github.com/$ORG/$PROJECT(single/single-repo modes) - Source path:
./$PROJECT - Issue Number:
$ISSUE_NUMBER(empty for batch modes) - Batch Mode:
$BATCH_MODE(single,single-repo, orcross-repo)
Instructions
Gates are tier-independent. The triage gate (Step 1 / T-1), the isolated
workspace lifecycle (Step 3 clone and the Step 13 teardown), and the pre-PR
readiness gate (Step 7.5) are MANDATORY and run in every tier, including
light. The frontmatter tiers.<name>.deep_checks and ref_docs only
control which OPTIONAL reference docs auto-load and whether the extra deep
checks run — they never disable these three gates.
Mode Routing
- If
$BATCH_MODE == "single-repo"or$BATCH_MODE == "cross-repo"→ Execute Batch Mode Instructions below - If
$BATCH_MODE == "single"→ Execute Phase 0: Execution Mode Selection (skip Batch Mode)
Batch Mode Instructions
See reference/batch-mode.md for the complete batch mode workflow including discovery, priority sorting, plan approval, and sequential execution.
Batch-only behaviors (do not apply in single-item mode):
- Subagent delegation by default (B-4): each item is dispatched to a fresh
general-purposeAgent so it starts with an unpolluted attention pool. The parent retains only{item_id, status, requested, root, active, pr_url, ci_conclusion}per item. Pass--inlineto fall back to the legacy single-context loop. - Per-item rule reminder (B-4.0): a 5-line invariant block is emitted as a fresh tool result before each item so language/CI/attribution rules stay in the recent attention window. In delegated mode this reminder is embedded in the subagent prompt; in
--inlinemode it is emitted directly in the parent context. - No
@load: reference/...inside the per-item loop: keep the inline reminder as the most recent context anchor. - Chunked confirmation gate (B-4.1): user confirmation prompt every 5 items, bypassable with
--no-confirm. When--auto-restartis set (and--no-restartis not), the gate is replaced by a forced session restart that writes.claude/resume.mdand exits; a freshclaudesession resumes from the next item.
Phase 0: Execution Mode Selection (Single-Item Mode)
Determine whether to run in Solo mode (single agent, sequential) or Team mode (implementer + tester agents in parallel).
0-1. If --solo or --team flag was provided
Skip mode selection — use $EXEC_MODE directly.
0-2. If no flag was provided (interactive selection)
First, fetch issue information for size estimation (after the Issue Triage Gate
in Step 1 resolves and claims the active issue via $ISSUE_NUMBER):
ISSUE_INFO=$(gh issue view $ISSUE_NUMBER --repo $ORG/$PROJECT \
--json title,body,labels -q '{title: .title, body: .body, labels: [.labels[].name]}')
Auto-recommend based on issue size:
| Signal | Solo (Recommended) | Team (Recommended) |
|---|---|---|
| Size label | size/XS, size/S |
size/M, size/L, size/XL |
| Description length | < 500 chars | > 500 chars |
| Acceptance criteria | < 3 items | 4+ items |
| Subtask references | None | "Part of", checklist items |
Decisive signals → apply silently. When the signals are unambiguous (all point the same direction — e.g. size/XS/size/S → solo, size/L/size/XL → team), set $EXEC_MODE to the recommended mode WITHOUT asking and print a one-line notice, e.g. [Mode: solo — XS issue; pass --team to override] (or [Mode: team — L issue; pass --solo to override]). The user can still override with the --solo/--team flags handled in 0-1.
Conflicting signals → ask. Only when signals genuinely point in different directions (e.g. size/S label but 4+ acceptance criteria and "Part of" references) use AskUserQuestion to present the choice:
- Question: "Issue #$ISSUE_NUMBER — (, mixed signals). Which execution mode?"
- Header: "Mode"
- Options:
- Recommended mode with "(Recommended)" suffix
- The other mode
- Description for Solo: "Sequential execution by a single agent. Lower token cost. Best for XS-S issues."
- Description for Team: "3-team parallel: dev + reviewer + doc-writer with review feedback loop. Higher quality for M+ issues. ~2x token cost."
Store the result in $EXEC_MODE (solo | team).
0-3. Mode Routing
- If
$EXEC_MODE == "solo"→ Execute Solo Mode Instructions (Steps 1-13 below) - If
$EXEC_MODE == "team"→ Execute Team Mode Instructions (after Solo Mode section)
Solo Mode Instructions
Execute the following workflow for the specified project:
1. Issue Triage Gate (state machine)
Issue selection and size evaluation are handled by the shared triage state
machine — a single deterministic, idempotent gate that solo, team, and batch
modes all route through before any repository is cloned or branch created.
The contract (states, outcome schema, comment fingerprint rule, eligibility
predicate, sort key) lives in reference/triage-state-machine.md; the reference
implementation is scripts/triage.sh.
Run the gate, then branch on its structured outcome:
TRIAGE_JSON=$(bash ~/.claude/skills/_internal/issue-work/scripts/triage.sh \
--repo "$ORG/$PROJECT" ${ISSUE_NUMBER:+--issue "$ISSUE_NUMBER"} ${PLAN_FILE:+--plan-file "$PLAN_FILE"})
OUTCOME=$(printf '%s' "$TRIAGE_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["outcome"])')
REQUESTED_ISSUE=$(printf '%s' "$TRIAGE_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["requested"])')
ROOT_ISSUE=$(printf '%s' "$TRIAGE_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["root"])')
ISSUE_NUMBER=$(printf '%s' "$TRIAGE_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["active"])')
Outcome routing (the JSON outcome field is authoritative):
outcome |
Action |
|---|---|
proceed |
An eligible issue was selected and claimed. active is the issue to work — continue to Step 3. |
decomposed |
The issue was oversized and had no eligible open child; children were created and one parent summary was posted. Stop — no clone, no branch. Report the decomposition. |
blocked |
The issue has an unresolved blocker (comment posted only if the blocker state changed), or the issue fetch failed identically three times. Stop — no clone, no branch. |
skipped |
The issue was closed, reassigned, or every child lost a claim race. Stop. |
failed |
Cycle/depth guard tripped, or needs_plan (oversized issue, no children, no --plan-file). A needs_plan reason means "design a plan and re-invoke with --plan-file", not a hard error. Stop and report. |
Every Stop row above still ends the invocation by printing the
ISSUE_WORK_RESULT: marker (see Output) as its last line — status matches
the outcome value, requested/root come from $REQUESTED_ISSUE/
$ROOT_ISSUE above, and pr_url is null.
Only proceed continues into code work. The other four outcomes are terminal
for this invocation and perform no repository side effects — this is what lets a
blocked or decomposed result be produced from a bare gh session (issue
#829 AC9). In batch mode, decomposed/blocked/skipped/failed are not
merge successes (see reference/batch-mode.md B-5).
Decomposition plan: when an oversized issue has no eligible open child, the
gate creates children from a plan file (one child title per line) supplied via
--plan-file. Design the sub-issue titles per the 5W1H template and the issue
splitting rules, write them to a temp file, and set $PLAN_FILE before invoking
the gate. Reconciliation is idempotent: a re-run creates only the missing
children and never posts a second summary (AC6).
3. Git Environment Setup
Git setup clones through the isolated-workspace stage (scripts/workspace.sh)
instead of an in-place checkout of $PROJECT — every run gets a private,
identity-verified clone rather than mutating a shared working copy. See
reference/workspace-lifecycle.md for the full contract (run-root layout,
manifest schema, identity-verification rule); this step only invokes it.
WORKSPACE_JSON=$(bash ~/.claude/skills/_internal/issue-work/scripts/workspace.sh \
--repo "$ORG/$PROJECT" --base "${TMPDIR:-/tmp}" --issue "$ISSUE_NUMBER")
WS_STATE=$(printf '%s' "$WORKSPACE_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["state"])')
Outcome routing (the JSON state field is authoritative):
state |
Action |
|---|---|
READY |
Capture repo_dir, baseline, manifest below and continue. |
REJECTED |
Stop. Report the reason (clone failure or origin identity mismatch). No branch, no code work. |
REPO_DIR=$(printf '%s' "$WORKSPACE_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["repo_dir"])')
BASELINE=$(printf '%s' "$WORKSPACE_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["baseline"])')
MANIFEST=$(printf '%s' "$WORKSPACE_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["manifest"])')
cd "$REPO_DIR"
Every step from here on (implementation, build, commit, push, PR) runs inside
$REPO_DIR, not the shared $PROJECT checkout. Branch-name derivation is
unchanged:
# Extract issue title for branch name
ISSUE_TITLE=$(gh issue view $ISSUE_NUMBER --repo $ORG/$PROJECT --json title -q '.title')
# Convert to kebab-case (lowercase, replace spaces with hyphens)
SHORT_DESC=$(echo "$ISSUE_TITLE" | tr '[:upper:]' '[:lower:]' | sed -E 's/[^a-z0-9]+/-/g' | sed -E 's/^-+|-+$//g' | cut -c1-50)
# Determine branch type from issue labels
LABELS=$(gh issue view $ISSUE_NUMBER --repo $ORG/$PROJECT --json labels -q '.labels[].name')
if echo "$LABELS" | grep -q "type/feature"; then
BRANCH_TYPE="feat"
elif echo "$LABELS" | grep -q "type/bug"; then
BRANCH_TYPE="fix"
elif echo "$LABELS" | grep -q "type/refactor"; then
BRANCH_TYPE="refactor"
elif echo "$LABELS" | grep -q "type/docs"; then
BRANCH_TYPE="docs"
else
BRANCH_TYPE="feat" # Default
fi
BRANCH_NAME="${BRANCH_TYPE}/issue-${ISSUE_NUMBER}-${SHORT_DESC}"
git checkout -b "$BRANCH_NAME"
4. Issue Assignment
gh issue edit <NUMBER> --repo $ORG/$PROJECT --add-assignee @me
5. Code Implementation
Priority: Start implementation immediately. Minimize upfront planning — analyze code as you implement, not in a separate planning phase.
Analyze existing code style:
- Check
.clang-format,.editorconfigif present - Review existing file patterns and conventions
- Check
Implement changes:
- Follow existing code style strictly
- Keep changes minimal and focused
- Validate incrementally: Build/test after each logical change, not after all changes
Header file review (C/C++ projects):
- Verify all used symbols have corresponding #include
- Add missing headers
Commit per logical unit:
- Format:
type(scope): description - Language: Follow the active
CLAUDE_CONTENT_LANGUAGEpolicy resolved fromcommit-settings.md(defaultenglish; supportskorean_plus_english,exclusive_bilingual,any). Do not assume English-only. - Forbidden (every policy): Claude/AI references, emojis, Co-Authored-By
- Format:
6. Build and Test Verification
Follow the build verification workflow rule (build-verification.md) to select the
appropriate strategy based on expected build duration.
Toolchain Availability Check
Before running local builds, verify required toolchains are installed:
# Check availability — do NOT install without asking the user
command -v go &>/dev/null # Go
command -v cargo &>/dev/null # Rust
command -v cmake &>/dev/null # C++
command -v npm &>/dev/null # Node.js
If toolchain is unavailable: Skip local build verification and rely on CI. Do NOT attempt to install toolchains without asking the user first. Report what was verified locally vs what needs CI.
Strategy Selection
| Build System | Typical Duration | Strategy |
|---|---|---|
go build / cargo check |
< 30s | Inline (synchronous) |
cmake --build / gradle build |
30s - 5min | Background + log polling |
ctest / pytest (large suites) |
1 - 10min | Background + log polling |
CI pipeline (gh workflow run) |
5min+ | CI log check |
Inline Strategy (short builds)
For builds expected under 30 seconds:
Bash(command="go build ./...", timeout=60000)
Bash(command="cargo check", timeout=60000)
Background + Log Polling Strategy (long builds)
For builds expected over 30 seconds:
- Launch in background:
Bash(command="cmake --build build/ ...", run_in_background=true) - Poll output every 10-15s:
TaskOutput(task_id="<id>", block=false, timeout=10000) - Detect outcome:
Built target/Finished= success,error:/FAILED= fix needed - Run tests with same pattern:
Bash(command="ctest --test-dir build/ ...", run_in_background=true)
On Build/Test Failure
- Read the error output from build logs
- Categorize failure (compile error, linker error, test assertion, missing dependency)
- Apply fix based on error pattern
- Re-run build/test to verify fix
- If persistent failure: create draft PR with failure log (see Error Handling)
Do NOT retry the same build without changes -- diagnose first.
7. Documentation Update
Update relevant documentation if applicable:
- README.md
- CHANGELOG.md
- API documentation
- Code comments for complex logic
Commit separately:
docs(scope): update documentation for <feature>
7.5. Pre-PR Readiness Gate and Documentation-to-Issue Gap Audit
Mandatory. This gate runs after the implementation and documentation are
committed to the feature branch and before any push or PR. It has two
halves — a deterministic git-state gate (scripts/pre-pr-gate.sh) and an
agent-driven documentation-to-issue gap audit. The full contract (outcome table,
develop-refresh rules, conflict rule, base-movement retry rule, gap-ledger
schema, dispositions, Korean-PR requirement) lives in
reference/pre-pr-readiness.md; do not duplicate it here.
Run the git-state gate from the feature-branch checkout (the $PROJECT directory
entered in Step 3), then branch on its structured outcome:
PREPR_JSON=$(bash ~/.claude/skills/_internal/issue-work/scripts/pre-pr-gate.sh \
--repo "$ORG/$PROJECT" --base develop --branch "$BRANCH_NAME")
OUTCOME=$(printf '%s' "$PREPR_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["outcome"])')
REASON=$(printf '%s' "$PREPR_JSON" | python3 -c 'import json,sys;print(json.load(sys.stdin)["reason"])')
Outcome routing (the JSON outcome field is authoritative):
outcome |
Action |
|---|---|
ready |
The base was refreshed (fast-forwarded or already current) and the feature was integrated cleanly onto a stable base. Run the gap audit below, then continue to Step 8. |
blocked |
STOP. Report the reason (dirty_worktree, base_ahead, base_diverged, conflict, base_unstable, fetch_failed, ...). Do NOT push or open a PR. Resolve the cause per reference/pre-pr-readiness.md (e.g. commit impl+docs for dirty_worktree; hand-resolve only an unambiguous conflict, never guess), then rerun the gate. |
The gate only ever fast-forwards the local develop when it is strictly
behind the remote; an ahead or diverged base is left untouched and blocks —
it is never reset. Integration defaults to rebase (private branch); pass
--integrate merge for a shared branch. On any conflict the script aborts and
blocks, leaving the feature branch exactly as it was — the agent may hand-resolve
only verifiably-unambiguous conflicts and must rerun the affected
verification and the gate before proceeding.
On a ready outcome, perform the documentation-to-issue gap audit per
reference/pre-pr-readiness.md: index the active issue plus its parent/child and
linked issues, reconcile each required behavior against implementation, test, and
documentation evidence, and record a gap ledger. Each row's disposition is
exactly one of fix-in-pr (in scope — fix before pushing), followup-issue
(out of scope — file a deduplicated follow-up), already-satisfied, or
blocked. Never report "no gap" when issue or documentation retrieval was
incomplete — treat those rows as blocked. Resolve every fix-in-pr row and
rerun the gate before continuing to Step 8.
The PR created in Step 8 must target develop, be written in Korean per
reference/pre-pr-readiness.md (machine tokens — identifiers, paths, URLs, and
the Closes #N keyword — stay ASCII), and close the active issue.
8. Push and Create PR
git push -u origin "$BRANCH_NAME"
gh pr create --repo $ORG/$PROJECT \
--title "${BRANCH_TYPE}(scope): description" \
--body "Closes #${ISSUE_NUMBER}
## Summary
- Brief description of changes
## Test Plan
- How to verify the changes"
Required:
Closes #<NUMBER>keyword to link issue- Language: Follow the active
CLAUDE_CONTENT_LANGUAGEpolicy (seecommit-settings.md). PR title and description must each comply with the per-artifact rule of the resolved policy; underexclusive_bilingual, each artifact must be wholly English-only or wholly Korean-only. - No Claude/AI references, emojis, or Co-Authored-By regardless of policy (see
commit-settings.md)
After PR creation, capture the PR URL from gh pr create output for the summary.
9. Monitor CI
After PR creation, monitor CI with non-blocking polling:
# Wait briefly for workflows to register
sleep 8
Poll all PR checks every 30 seconds, max 10 minutes:
gh run list --repo $ORG/$PROJECT --branch "$BRANCH_NAME" \
--json databaseId,name,status,conclusion
Decision table — apply per run, evaluate ALL runs each poll cycle:
| All runs status | Any conclusion=failure | Action |
|---|---|---|
All completed |
No | All pass → proceed to merge |
All completed |
Yes | Diagnose, fix, push, re-poll |
Any in_progress or queued |
— | Poll again after 30s |
Merge gate: Do NOT merge until every run shows status: completed.
A run that is in_progress or queued is NOT a passing run — wait for it.
Timeout: If 10-minute polling limit is reached and any run is still
in_progress or queued, stop polling, report the current status table
to the user, and do NOT merge.
Default (no --wait-on-timeout): leave the PR open and print a resume
command so CI can finish unattended:
[CI still running after 10 min — leaving PR open. Resume with: pr-work $PR_NUMBER]
Do not block on a prompt. Only when --wait-on-timeout was passed, ask the
user whether to wait longer or leave the PR open for manual merge. This
mirrors the ci-fix timeout behavior of defaulting to a resumable hand-off
rather than an interactive stall.
Do NOT use gh run watch — it blocks the entire session.
On CI failure, fix the issue and push. Repeat up to 3 attempts. After 3 failures, convert the PR to draft, report the status, and do NOT proceed to merge or produce a completion summary. The task is NOT complete until all CI checks pass and the PR is merged.
10. Squash Merge
ABSOLUTE CI GATE — MANDATORY PRE-MERGE VERIFICATION:
Before executing gh pr merge, you MUST run gh pr checks and verify every single check:
gh pr checks $PR_NUMBER --repo $ORG/$PROJECT
Do NOT merge if ANY check shows:
failorfailureconclusion (regardless of perceived cause)pending,queued, orin_progressstatuscancelled,timed_out, orstartup_failureconclusion
ALL checks must show pass or neutral to proceed. No exceptions. No rationalization.
Never judge a failure as "unrelated", "pre-existing", or "infrastructure-only" — all failures block merge.
If any check is not passing, STOP. Do NOT proceed to merge. Instead:
- Report the full
gh pr checksoutput to the user - Either fix the failure and re-poll, or let the user decide
Only when ALL checks pass:
gh pr merge $PR_NUMBER --repo $ORG/$PROJECT --squash --delete-branch
If merge fails (e.g., review required), report the status and skip merge.
11. Close Related Issues and Epics
After merge:
# Verify the linked issue was closed by the merge
STATE=$(gh issue view $ISSUE_NUMBER --repo $ORG/$PROJECT --json state -q '.state')
if [[ "$STATE" != "CLOSED" ]]; then
gh issue close $ISSUE_NUMBER --repo $ORG/$PROJECT
fi
Epic closure: If the issue references a parent epic (e.g., Part of #N),
check if all sub-issues of that epic are now closed. If so, close the epic
with a summary comment.
12. Update Original Issue
IMPORTANT: Issue comments must comply with the active CLAUDE_CONTENT_LANGUAGE policy (resolved from commit-settings.md; default english). Do not hard-code "English only" — under exclusive_bilingual a Korean-only comment is valid, and under korean_plus_english mixed inline is valid.
gh issue comment <NUMBER> --repo $ORG/$PROJECT \
--body "Implementation PR: #<PR_NUMBER>"
13. Workspace Teardown
Mandatory, all tiers. After the merge (Step 10) and issue/epic closure
(Steps 11-12), tear down the isolated workspace claimed in Step 3. See
reference/workspace-lifecycle.md (the #840 sections) for the full contract
(resume-reconciliation rule, preservation predicate, remotely-recoverable
rule, 3-fail preservation policy).
bash ~/.claude/skills/_internal/issue-work/scripts/cleanup-workspace.sh \
--phase reconcile --repo-dir "$REPO_DIR" --manifest "$MANIFEST" --pr "$PR_NUMBER"
bash ~/.claude/skills/_internal/issue-work/scripts/cleanup-workspace.sh \
--phase cleanup --run-root "$(dirname "$REPO_DIR")" --repo-dir "$REPO_DIR" \
--manifest "$MANIFEST" --base "${TMPDIR:-/tmp}" --issue "$ISSUE_NUMBER" \
--pr "$PR_NUMBER" ${MERGE_COMMIT:+--merge-commit "$MERGE_COMMIT"}
reconcile re-reads the live branch/PR state before cleanup decides whether
to remove the run root. A PRESERVED result (e.g. uncommitted work, an
unmerged PR, or a still-held agent lease) is not a failure — report the
reason and leave the run root on disk. Only a CLEANED result removes it.
Team Mode Instructions
See reference/team-mode.md for the complete team mode workflow with 3-team architecture (dev, reviewer, doc-writer), feedback loops, and cleanup.
Policies
See _policy.md for common rules, including the Atomic Multi-Phase Execution rule — when the user specifies multiple phases (e.g., "Phase 1/2/3"), complete all phases without pausing between them for confirmation.
Command-Specific Rules
| Item | Rule |
|---|---|
| Language | All issue comments, PR titles, PR descriptions, and commit messages follow the active CLAUDE_CONTENT_LANGUAGE policy (see commit-settings.md) |
| Issue linking | Closes #NUM required in PR |
| Build verification | Must pass before PR creation |
Output
CRITICAL: Do NOT produce a completion summary if CI has any failing, pending, or incomplete checks. A task is only complete when the PR is merged with all CI checks passing.
Result Marker
Every exit path of this workflow — a terminal triage outcome (Step 1), a
pre-PR gate block (Step 7.5), a CI failure (Step 9-10), or a full merge — ends
by printing exactly one ISSUE_WORK_RESULT: line as the last line of its
output:
ISSUE_WORK_RESULT: {"status":"merged|decomposed|blocked|skipped|failed","requested":"<n>","root":"<n>","active":"<n>","pr_url":"<url-or-null>","reason":"<short>"}
Fields mirror the batch subagent JSON contract (reference/batch-mode.md
B-4.a): the same status vocabulary and the same requested/root/active
triple from the triage outcome (reference/triage-state-machine.md). status
is derived from the triage outcome when work did not proceed, else from the
final merge/CI state. External orchestrators (scripts/batch-issue-work.sh /
.ps1) parse this line to branch on the structured outcome instead of the
process exit code alone. Both Work Summary formats below end with this
marker, as the trailing line after the table and any following sections.
After successful merge, provide summary:
## Work Summary
| Item | Value |
|------|-------|
| Repository | $ORG/$PROJECT |
| Issue | #$ISSUE_NUMBER - Title |
| Branch | $BRANCH_NAME |
| Execution mode | Solo / Team |
| PR | [#PR_NUMBER](https://github.com/$ORG/$PROJECT/pull/PR_NUMBER) |
| CI Status | All checks passed |
| Merged | Yes |
| Commits | N commits |
### Changes Made
- List of changes
### Files Modified
- file1.cpp
- file2.h
### Next Steps
- Any follow-up items
ISSUE_WORK_RESULT: {"status":"merged","requested":"$REQUESTED_ISSUE","root":"$ROOT_ISSUE","active":"$ISSUE_NUMBER","pr_url":"https://github.com/$ORG/$PROJECT/pull/$PR_NUMBER","reason":""}
If CI failed or the PR was not merged, use this format instead:
## Work Summary (INCOMPLETE)
| Item | Value |
|------|-------|
| Repository | $ORG/$PROJECT |
| Issue | #$ISSUE_NUMBER - Title |
| Branch | $BRANCH_NAME |
| PR | [#PR_NUMBER](https://github.com/$ORG/$PROJECT/pull/PR_NUMBER) |
| CI Status | FAILING — [list failed checks] |
| Merged | No |
| Reason | [CI failure / Max retries exceeded / Timeout] |
### Action Required
- User must resolve CI failures before merge
ISSUE_WORK_RESULT: {"status":"failed","requested":"$REQUESTED_ISSUE","root":"$ROOT_ISSUE","active":"$ISSUE_NUMBER","pr_url":"<url-or-null>","reason":"<short reason>"}
IMPORTANT: Always include the full PR URL in the output (e.g., https://github.com/org/repo/pull/123).
Batch Mode Output
In batch mode, use the summary format from Phase B-5 instead. Include per-item results and the overall success/failure count.
Error Handling
See reference/error-handling.md for prerequisite checks, runtime errors, and batch mode errors.
Side Effects and Loop-Safety
This skill is loop_safe: false. Each invocation creates branches, opens or advances pull requests, and posts comments against live GitHub issues. Wrapping it in /loop would re-trigger work on already-handled issues and create duplicate branches/PRs. The exit contract is "one resolved issue (or batch) per invocation," not a no-side-effect retry — resume an interrupted run from the documented session-resume state rather than re-running blindly.