Complete Issue
Autonomous end-to-end pipeline: issue number in → merged PR out.
Before requesting decisions, entering a planning workflow, or delegating work,
read ${CLAUDE_PLUGIN_ROOT}/lib/driver-interaction.md and follow its
cross-platform capability-binding rules.
Read ${CLAUDE_PLUGIN_ROOT}/lib/decision-gates.md before resolving any workflow
choice.
source "${CLAUDE_PLUGIN_ROOT}/lib/github-rest.sh"
Chains the start-issue workflow, Codex review, and the e2e-verify
fix-and-ship workflow.
The component workflow skills are user-only. Do not call them with the Skill
tool. Load their SKILL.md files with Read and execute their instructions
directly with the arguments specified below.
The $go-workflow:start-issue phase owns subagent model tiering through agent prompt
frontmatter: Explore uses Haiku, Spec Review and Quality Review use Sonnet,
and Implementer inherits the parent session model. To override all subagent
models for a run, set CLAUDE_CODE_SUBAGENT_MODEL=<model> before invoking
$go-workflow:complete-issue; pass --no-agents through to run the start phase without
subagents.
Parse Arguments
ISSUE_NUM=""
FLAGS=""
SKIP_NEXT=false
for arg in $ARGUMENTS; do
if [ "$SKIP_NEXT" = "true" ]; then
FLAGS="$FLAGS $arg"
SKIP_NEXT=false
elif [ "$arg" = "--coverage-threshold" ]; then
FLAGS="$FLAGS $arg"
SKIP_NEXT=true
elif echo "$arg" | grep -qE '^--'; then
FLAGS="$FLAGS $arg"
elif [ -z "$ISSUE_NUM" ] && echo "$arg" | grep -qE '^[0-9]+$'; then
ISSUE_NUM="$arg"
else
FLAGS="$FLAGS $arg"
fi
done
if [ -z "$ISSUE_NUM" ]; then
echo "Claude Code: /go-workflow:complete-issue <issue-number> [--skip-coverage] [--coverage-threshold <n>] [--no-agents]"
echo "Codex: \$go-workflow:complete-issue <issue-number> [--skip-coverage] [--coverage-threshold <n>] [--no-agents]"
fi
echo "Issue: $ISSUE_NUM | Flags: $FLAGS"
If ISSUE_NUM is empty, this is a missing-intent gate. Request the issue
number through native structured input when available; otherwise ask in the
final response and stop before loop initialization or a completion claim.
Loop Initialization & Re-entry
Read loop-state.md and run the bootstrap block, persist arguments
block, and re-entry check. If PHASE is set, recover the owner phase and
the corresponding component phase before routing below; otherwise continue to
Phase 1.
Phase → step routing:
implementing→ Phase 1, using.components.start_issue.phasereviewing→ Phase 3; the earlier in-session review is void and must not be restartedverifying→ Phase 3, using.components.e2e_verify.phaseand its nested active componentincomplete→ display the persisted component-awarereason, output<done>INCOMPLETE</done>, and stop without entering Phase 3
Resolve child phases without replacing the owner phase used by this routing table:
OWNER_PHASE="$PHASE"
START_ISSUE_STATE_PATH=$(child_workflow_path "$WORKFLOW_STATE_PATH" "start_issue")
E2E_VERIFY_STATE_PATH=$(child_workflow_path "$WORKFLOW_STATE_PATH" "e2e_verify")
START_ISSUE_PHASE=$(get_loop_field "$STATE_FILE" "phase" "$START_ISSUE_STATE_PATH")
E2E_VERIFY_PHASE=$(get_loop_field "$STATE_FILE" "phase" "$E2E_VERIFY_STATE_PATH")
PHASE="$OWNER_PHASE"
Phase 1: Implement ($go-workflow:start-issue)
set_loop_phase "$STATE_FILE" "implementing" "$WORKFLOW_STATE_PATH"
START_ISSUE_STATE_PATH=$(child_workflow_path "$WORKFLOW_STATE_PATH" "start_issue")
initialize_workflow_state "$STATE_FILE" "$START_ISSUE_STATE_PATH"
Read ${CLAUDE_PLUGIN_ROOT}/skills/start-issue/SKILL.md and execute its workflow
directly, treating $ISSUE_NUM $FLAGS as its $ARGUMENTS. Do not call the
Skill tool. Read phases.md for the full sub-step list (fetch issue, create
worktree, detect type, explore, design, TDD, verify, coverage, security review,
commit/push/PR, watch CI).
Before executing the loaded workflow, set its explicit caller contract:
CALLER_LOOP_STATE_FILE="$STATE_FILE"
CALLER_WORKFLOW_STATE_PATH="$WORKFLOW_STATE_PATH"
After it returns, clear both caller variables and route its structured result:
WORKFLOW_STATE_PATH="$CALLER_WORKFLOW_STATE_PATH"
unset CALLER_LOOP_STATE_FILE CALLER_WORKFLOW_STATE_PATH
START_ISSUE_RESULT=$(get_loop_field "$STATE_FILE" "result" "$START_ISSUE_STATE_PATH")
START_ISSUE_REASON=$(get_loop_field "$STATE_FILE" "reason" "$START_ISSUE_STATE_PATH")
if [ "$START_ISSUE_RESULT" != "complete" ]; then
START_ISSUE_REASON="${START_ISSUE_REASON:-start-issue-incomplete}"
set_loop_terminal_result "$STATE_FILE" "incomplete" "$START_ISSUE_REASON" "incomplete" "INCOMPLETE"
echo "Complete-issue stopped during implementation: $START_ISSUE_REASON"
echo "<done>INCOMPLETE</done>"
exit 0
fi
After $go-workflow:start-issue completes, detect the PR number and worktree
context while retaining the already-normalized caller state path, then persist:
WORKTREE_PATH=$(get_loop_field "$STATE_FILE" "worktree_path" '[]')
REPO_SLUG=$(get_loop_field "$STATE_FILE" "repo_slug" '[]')
START_ORIGINAL_REPO_ROOT=$(get_loop_field "$STATE_FILE" "original_repo_root" '[]')
REGISTERED_WORKTREES=$(git -C "$ORIGINAL_REPO_ROOT" worktree list --porcelain | awk '/^worktree / { sub(/^worktree /, ""); print }')
if [ "$START_ORIGINAL_REPO_ROOT" != "$ORIGINAL_REPO_ROOT" ] ||
[ -z "$WORKTREE_PATH" ] ||
[ "${WORKTREE_PATH#/}" = "$WORKTREE_PATH" ] ||
[ ! -d "$WORKTREE_PATH" ] ||
! printf '%s\n' "$REGISTERED_WORKTREES" | awk -v expected="$WORKTREE_PATH" '$0 == expected { found = 1 } END { exit found ? 0 : 1 }' ||
[ -z "$REPO_SLUG" ]; then
WORKFLOW_REASON=start-issue-worktree-path-invalid
set_loop_terminal_result "$STATE_FILE" "incomplete" "$WORKFLOW_REASON" "incomplete" "INCOMPLETE"
echo "WORKFLOW_RESULT=INCOMPLETE"
echo "WORKFLOW_REASON=$WORKFLOW_REASON"
echo "<done>INCOMPLETE</done>"
exit 1
fi
PR_HEAD_BRANCH=$(git -C "$WORKTREE_PATH" branch --show-current)
HEAD_SHA=$(git -C "$WORKTREE_PATH" rev-parse HEAD)
PR_JSON=$(cd "$WORKTREE_PATH" && github_current_pr "$PR_HEAD_BRANCH" "$HEAD_SHA") || {
echo "Error: No open PR matches the persisted worktree branch and HEAD after start-issue"
exit 1
}
PR_NUM=$(jq -er '.number' <<< "$PR_JSON")
GIT_DIR_ABS=$(cd "$WORKTREE_PATH" && cd "$(git rev-parse --git-dir 2>/dev/null)" && pwd)
GIT_COMMON_ABS=$(cd "$WORKTREE_PATH" && cd "$(git rev-parse --git-common-dir 2>/dev/null)" && pwd)
if [ "$GIT_DIR_ABS" != "$GIT_COMMON_ABS" ]; then
echo "Running in worktree: $WORKTREE_PATH"
fi
set_loop_field "$STATE_FILE" "pr_number" "$PR_NUM" "$WORKFLOW_STATE_PATH"
set_loop_field "$STATE_FILE" "worktree_path" "${WORKTREE_PATH:-}" '[]'
echo "PR #$PR_NUM created"
Worktree invariant (decision-time, must stay in trunk): All subsequent phases MUST operate on
$WORKTREE_PATH. Prefergit -C "$WORKTREE_PATH",go -C "$WORKTREE_PATH", andgh ... --repo "$REPO_SLUG"; use an explicit worktree-scoped group only when no per-command option exists. Use$WORKTREE_PATHas the base for all file tools.STATE_FILEremains the one normalized caller-owned path established before Phase 1. Do not assume a pre-tool-use hook will correct or reject an ambient-directory command.
Phase 2: Self-Review (Codex)
set_loop_phase "$STATE_FILE" "reviewing" "$WORKFLOW_STATE_PATH"
Run an LLM review to catch issues before E2E verification. Never silently skip review. Resolve unpinned backend recovery from diagnostics; replacing a backend the user explicitly required is a missing-intent gate.
Delegated fallback reviews are session-local and must complete
synchronously. Never dispatch them in the background or persist them for a
successor session. If a successor re-enters with phase="reviewing", skip the
expired review and continue to Phase 3; the PR already created in Phase 1 and
its CI are the durable gate.
Detect codex availability:
CODEX_AVAILABLE=false
if command -v codex &>/dev/null; then
CODEX_CMD="codex"
CODEX_AVAILABLE=true
fi
- If codex is NOT available OR if codex exec fails at runtime → Read
codex-fallback.mdand follow its evidence-based recovery order. - If codex IS available → run codex review on the PR diff with an adaptive timeout, address findings, and commit fixes. See
phases.mdfor the full bash (diff sizing, timeout calculation, large-diff warning).
Address findings: for each valid finding, make the fix. Skip false positives or
cosmetic-only items. Maintain REVIEW_FILES as the exact list of files modified
in this review phase, including generated or updated tests. Before modifying an
existing path, confirm
git -C "$WORKTREE_PATH" status --porcelain -- "$TARGET_FILE" is empty so
pre-existing changes cannot enter the review-fix commit. Every GitHub and Git
operation in this phase runs against the persisted WORKTREE_PATH and
REPO_SLUG. Commit fixes if any changes were made:
if ! git -C "$WORKTREE_PATH" diff --cached --quiet; then
echo "Error: Pre-existing staged changes must be committed or unstaged before complete-issue can commit review fixes."
exit 1
fi
REVIEW_FILES=(
"path/to/reviewed-file.go"
"path/to/reviewed-file_test.go"
)
if [ "${#REVIEW_FILES[@]}" -gt 0 ]; then
git -C "$WORKTREE_PATH" add -- "${REVIEW_FILES[@]}"
if ! git -C "$WORKTREE_PATH" diff --cached --quiet; then
git -C "$WORKTREE_PATH" commit -m "fix: address codex review findings"
git -C "$WORKTREE_PATH" push
fi
fi
Phase 3: E2E Verify and Ship
set_loop_phase "$STATE_FILE" "verifying" "$WORKFLOW_STATE_PATH"
E2E_VERIFY_STATE_PATH=$(child_workflow_path "$WORKFLOW_STATE_PATH" "e2e_verify")
initialize_workflow_state "$STATE_FILE" "$E2E_VERIFY_STATE_PATH"
Read ${CLAUDE_PLUGIN_ROOT}/skills/e2e-verify/SKILL.md and execute its workflow
directly, treating $PR_NUM fix-and-ship as its $ARGUMENTS. Do not call the
Skill tool. This runs the full workflow in fix-and-ship mode (rebase, build,
address review, E2E browser tests, post results, add the run-full-ci label,
watch CI, and execute the ship workflow).
Before executing the loaded workflow, set its explicit caller contract:
CALLER_LOOP_STATE_FILE="$STATE_FILE"
CALLER_WORKFLOW_STATE_PATH="$WORKFLOW_STATE_PATH"
After it returns, clear both caller variables and translate its structured result into complete-issue's own terminal contract:
WORKFLOW_STATE_PATH="$CALLER_WORKFLOW_STATE_PATH"
unset CALLER_LOOP_STATE_FILE CALLER_WORKFLOW_STATE_PATH
E2E_VERIFY_RESULT=$(get_loop_field "$STATE_FILE" "result" "$E2E_VERIFY_STATE_PATH")
E2E_VERIFY_REASON=$(get_loop_field "$STATE_FILE" "reason" "$E2E_VERIFY_STATE_PATH")
if [ "$E2E_VERIFY_RESULT" != "verified" ]; then
E2E_VERIFY_REASON="${E2E_VERIFY_REASON:-e2e-verify-incomplete}"
set_loop_terminal_result "$STATE_FILE" "incomplete" "$E2E_VERIFY_REASON" "incomplete" "INCOMPLETE"
echo "Complete-issue stopped during verification: $E2E_VERIFY_REASON"
echo "<done>INCOMPLETE</done>"
exit 0
fi
Completion Criteria
Output <done>COMPLETE</done> when ALL of these are true:
- Issue implemented with tests
- PR created and pushed
- Codex review completed and findings addressed, or an expired session-local review is durably recorded as void and the exact current head passes the downstream CI gates. An ordinary timeout or fallback failure does not count.
- E2E verification completed
- Results posted to PR
- CI passes
- PR merged (via the ship workflow)
When all criteria are met:
set_loop_terminal_result "$STATE_FILE" "complete" "" "completed" "COMPLETE"
echo "<done>COMPLETE</done>"
Safety: If 15+ iterations complete without success, document the blocking evidence and stop incomplete. Do not bypass a completion criterion.
Further Reading
phases.md— full sub-step lists for Phase 1 ($go-workflow:start-issue) and the codex run for Phase 2loop-state.md— bootstrap, re-entry, and persist blockscodex-fallback.md— evidence-based recovery for codex unavailable / runtime failure / timeout