| name | solve-pipeline |
| description | Shared launcher pipeline for /uberdev:solve and /uberdev:turbo. Parses arguments, classifies tier, writes tier-appropriate prompt, dispatches a Claude agent into a fresh terminal session per issue. Invoked inline by both commands. Use when /solve or /turbo invokes this skill — never call directly. |
Solve Pipeline (shared body for /solve and /turbo)
This skill is invoked inline by commands/solve.md and commands/turbo.md. The caller exports AUTO_MODE (0 for /solve interactive; 1 for /turbo unattended) before invocation; this skill reads $AUTO_MODE and $ARGUMENTS from the caller's shell scope.
$ARGUMENTS may contain one or more issue numbers (e.g. 42 or 5 6 7). The skill validates every issue up front (Phase A) and then dispatches one autonomous agent per issue into its own terminal session (Phase B). Per-issue artifacts (/tmp/solve-prompt-N.txt, /tmp/solve-N.sh, .claude/worktrees/solve-issue-N/, worktree-solve-issue-N branch, #N <title> tab) are namespaced by $ISSUE_NUM, so concurrent spawns are collision-free. Override flags (--trivial|--small|--full, --auto, --terminal=...) apply batch-wide.
Triage heuristics (Phase A applies this table)
| Tier | Signals (any strong match) | Spawned workflow |
|---|
| trivial | Labels: typo, docs, documentation, chore, good-first-issue. Body <300 chars after stripping markdown. Title matches typo|rename|bump|version|readme. No stack trace. Single file named. | Read pre-collected research → minimal edit → test (if touched code is tested) → PR. No brainstorm, no multi-step plan. Phase 1 of /uberdev:review-pr runs the post-impl reviewer fanout and Phase 2 runs the simplify lenses after the PR opens. |
| small | Clear reproduction + error message. Localized to one module/package. Estimated ≤50 LOC. Labels: bug (scoped) or none. Not cross-cutting. | Read pre-collected research → lightweight TodoWrite plan (3–6 tasks) → TDD → PR. No brainstorm. Phase 1 of /uberdev:review-pr runs the post-impl reviewer fanout and Phase 2 runs the simplify lenses after the PR opens. |
| medium/large (default) | Labels: epic, needs-discussion, architectural, infrastructure (multi-service), refactor. ≥3 files/modules mentioned. Missing clear problem statement. Cross-package scope. | Full /uberdev:brainstorm → /uberdev:write-plan → /uberdev:subagent-driven-dev → /uberdev:review-pr pipeline. |
When in doubt, default to medium/large. Misclassification is recoverable.
Steps
1. Parse arguments
Extract one or more issue numbers + optional override flags. The parser scans every space-separated token in $ARGUMENTS and collects only those that are purely numeric (anchored regex ^[0-9]+$); deduplicates so /turbo 5 5 6 doesn't race two agents into the same worktree path; and rejects empty input.
ISSUE_NUMS=($(echo "$ARGUMENTS" | tr ' ' '\n' | grep -E '^[0-9]+$' | awk '!seen[$0]++'))
OVERRIDE=$(echo "$ARGUMENTS" | grep -oE '\-\-(trivial|small|full)' | head -1 | sed 's/--//')
TERMINAL_OVERRIDE=$(echo "$ARGUMENTS" | grep -oE '\-\-terminal=[a-z]+' | head -1 | sed 's/--terminal=//')
AUTO_FLAG=$(echo "$ARGUMENTS" | grep -oE '\-\-auto' | head -1)
if [[ ${#ISSUE_NUMS[@]} -eq 0 ]]; then
echo "Usage: /uberdev:solve|/uberdev:turbo <issue-number> [<issue-number>...] [--trivial|--small|--full] [--auto] [--terminal=cmux|ghostty|iterm|terminal|nohup]"
exit 1
fi
[[ "$OVERRIDE" == "full" ]] && OVERRIDE="medium"
if [[ -n "$AUTO_FLAG" ]]; then
AUTO_PERMISSIONS=1
elif [[ "$SOLVE_AUTO" == "1" ]]; then
AUTO_PERMISSIONS=1
elif [[ -f .claude/uberdev.local.md ]] && grep -qE '^solve_auto:[[:space:]]*true[[:space:]]*$' .claude/uberdev.local.md; then
AUTO_PERMISSIONS=1
else
AUTO_PERMISSIONS=0
fi
$OVERRIDE, $TERMINAL_OVERRIDE, $AUTO_FLAG, and $AUTO_PERMISSIONS apply batch-wide. There is no per-issue override syntax — run separate /turbo invocations if you need different flags per issue.
2. Detect repo
gh repo view --json nameWithOwner --jq .nameWithOwner
3. Detect terminal app
Pick a dispatcher once, before any spawn. Priority: explicit override > cmux (only when we're inside a live cmux session) > standalone Ghostty > iTerm2 > Terminal.app > nohup-fallback. Warp falls through to nohup — its CLI can't dispatch a command into a new window cleanly.
TERM_PROGRAM=ghostty is also set when you're inside cmux (cmux bundles Ghostty), so the cmux check MUST come first and MUST require a live cmux socket — otherwise we'd mis-classify a cmux session as plain Ghostty. Current cmux releases export the socket path as $CMUX_SOCKET_PATH; older releases used $CMUX_SOCKET (and recent cmux explicitly sets CMUX_SOCKET= to empty string, so a bare -n "$CMUX_SOCKET" check fails inside a live cmux session). Read both: prefer $CMUX_SOCKET_PATH, fall back to $CMUX_SOCKET.
if [[ -n "$TERMINAL_OVERRIDE" ]]; then
TERMINAL="$TERMINAL_OVERRIDE"
elif [[ -n "$SOLVE_TERMINAL" ]]; then
TERMINAL="$SOLVE_TERMINAL"
elif _CMUX_SOCK="${CMUX_SOCKET_PATH:-${CMUX_SOCKET:-}}"; [[ -n "$_CMUX_SOCK" && -S "$_CMUX_SOCK" ]] && command -v cmux >/dev/null 2>&1; then
TERMINAL="cmux"
elif [[ "$TERM_PROGRAM" == "ghostty" ]] && [[ -d /Applications/Ghostty.app ]]; then
TERMINAL="ghostty"
elif [[ "$TERM_PROGRAM" == "iTerm.app" ]]; then
TERMINAL="iterm"
elif [[ "$TERM_PROGRAM" == "Apple_Terminal" ]]; then
TERMINAL="terminal"
else
TERMINAL="nohup"
fi
case "$TERMINAL" in
cmux)
if ! command -v cmux >/dev/null 2>&1; then
echo "warning: TERMINAL=cmux requested but 'cmux' binary not on PATH." >&2
echo " Install: npm i -g @manaflow-ai/cmux (see cmux README for canonical install)" >&2
echo " Falling back to nohup." >&2
TERMINAL="nohup"
elif _CMUX_SOCK="${CMUX_SOCKET_PATH:-${CMUX_SOCKET:-}}"; [[ -z "$_CMUX_SOCK" || ! -S "$_CMUX_SOCK" ]]; then
echo "warning: TERMINAL=cmux requested but neither \$CMUX_SOCKET_PATH nor \$CMUX_SOCKET resolves to a live socket." >&2
echo " Run /solve or /turbo from inside an active cmux session, or unset SOLVE_TERMINAL/--terminal." >&2
echo " Falling back to nohup." >&2
TERMINAL="nohup"
fi
;;
ghostty)
[[ -d /Applications/Ghostty.app ]] || { echo "warning: ghostty selected but /Applications/Ghostty.app missing; falling back to nohup." >&2; TERMINAL="nohup"; }
;;
iterm)
[[ -d /Applications/iTerm.app ]] || { echo "warning: iterm selected but /Applications/iTerm.app missing; falling back to nohup." >&2; TERMINAL="nohup"; }
;;
terminal|nohup) ;;
*) echo "warning: unknown TERMINAL='$TERMINAL'; falling back to nohup." >&2; TERMINAL="nohup" ;;
esac
echo "Dispatching via: $TERMINAL"
if [[ "$AUTO_PERMISSIONS" == "1" ]]; then
PERM_DESC="auto (Claude Code AI classifier)"
else
PERM_DESC="default (manual per-tool gating)"
fi
echo "Permission mode: $PERM_DESC"
REAL_CLAUDE=$(WRAPPER_DIR=$(dirname "$(which claude)"); for d in ${(s/:/)PATH}; do [[ "$d" == "$WRAPPER_DIR" ]] && continue; [[ -x "$d/claude" ]] && echo "$d/claude" && break; done)
REAL_CLAUDE="${REAL_CLAUDE:-$(which claude)}"
4. Validate all issues (Phase A — validate-all-first)
For every issue in ISSUE_NUMS, fetch via gh issue view --json (read-only), confirm state == OPEN, and classify the tier per the triage table above. Capture the truncated title for the workspace tab. If any issue fails (closed, missing, gh error), print all errors and abort with no agents dispatched — partial dispatches are not allowed.
declare -A TITLES TIERS
ERRORS=()
for ISSUE_NUM in "${ISSUE_NUMS[@]}"; do
GH_ERR=$(mktemp)
ISSUE_JSON=$(gh issue view "$ISSUE_NUM" --json number,title,state,body,labels 2>"$GH_ERR") || {
ERRORS+=("#$ISSUE_NUM: gh fetch failed: $(<"$GH_ERR")")
rm -f "$GH_ERR"
continue
}
rm -f "$GH_ERR"
STATE=$(jq -r .state <<<"$ISSUE_JSON")
if [[ "$STATE" != "OPEN" ]]; then
ERRORS+=("#$ISSUE_NUM: state=$STATE (must be OPEN)")
continue
fi
TITLE_RAW=$(jq -r .title <<<"$ISSUE_JSON")
if [[ ${#TITLE_RAW} -gt 40 ]]; then
TITLE="${TITLE_RAW:0:40}…"
else
TITLE="$TITLE_RAW"
fi
TIER="${OVERRIDE:-medium}"
if [ -r "${CLAUDE_PLUGIN_ROOT}/lib/config-read.sh" ]; then
. "${CLAUDE_PLUGIN_ROOT}/lib/config-read.sh"
FLOOR="$(uberdev_read_enum solve_tier_floor SOLVE_TIER_FLOOR "trivial|small|medium|large" "")"
CEILING="$(uberdev_read_enum solve_tier_ceiling SOLVE_TIER_CEILING "trivial|small|medium|large" "")"
TIER="$(uberdev_clamp_tier "$TIER" "$FLOOR" "$CEILING")"
fi
TITLES[$ISSUE_NUM]="$TITLE"
TIERS[$ISSUE_NUM]="$TIER"
done
if [[ ${#ERRORS[@]} -gt 0 ]]; then
printf 'error: %s\n' "${ERRORS[@]}" >&2
echo "no agents dispatched" >&2
exit 1
fi
if [[ "$AUTO_MODE" == "1" ]]; then
for n in "${ISSUE_NUMS[@]}"; do
if [[ "${TIERS[$n]}" == "medium" ]]; then
echo "⚠️ TURBO MODE — brainstorm questions auto-answered with lead-agent recommendations." >&2
echo " Spec and plan are still written to disk before implementation; review the artifacts to course-correct." >&2
break
fi
done
fi
5. Per-issue dispatch (Phase B — spawn one agent per issue)
For each validated issue, write its prompt heredoc, write its launcher script, and spawn it into the chosen terminal. Each spawn is an independent OS process / cmux workspace; together they run in parallel. The for-loop opens at the top of this code block and closes after the dispatch case in Step 5c (done); sub-steps 5a/5b/5c are documentation breakdowns of the loop body — their bash blocks all execute inside the same loop iteration. Per-issue dispatch outcomes are tracked in SPAWNED (success) and DISPATCH_FAILED (failure) so the user sees exactly which issues spawned and which didn't — no silent partial-batch failures.
SPAWNED=()
DISPATCH_FAILED=()
for ISSUE_NUM in "${ISSUE_NUMS[@]}"; do
TIER="${TIERS[$ISSUE_NUM]}"
TITLE="${TITLES[$ISSUE_NUM]}"
5a. Write tier-appropriate prompt
The trivial/small heredocs no longer run the pre-push reviewer fanout — both AUTO_MODE branches push directly and chain into /uberdev:review-pr, whose Phase 1 now hosts the 5-reviewer fanout. The medium prompt branches on AUTO_MODE=1 to inject --turbo into the orchestrator dispatch.
The if/else/fi blocks below stay at column 0 (zsh and bash do not require physical indentation inside for ... done); tests/turbo-flow.test.sh anchors its differential-guard awk on ^if \[\[ "\$AUTO_MODE" == "1" \]\]; then$ and must keep matching unchanged. Do not indent these blocks when the loop wraps them.
trivial:
if [[ "$AUTO_MODE" != "1" ]]; then
cat > /tmp/solve-prompt-$ISSUE_NUM.txt << EOF
Solve GH issue #$ISSUE_NUM directly. Triaged as TRIVIAL.
Steps:
1. \`gh issue view $ISSUE_NUM\` — read the ask.
2. **Read pre-collected research (legacy cache)** — for each file in \`.uberdev/research/issue-$ISSUE_NUM/{constraints,prior-art,security}.md\` that exists, read the \`summary:\` block and inline its key findings into your working context. After issue #14 the cache is no longer written by \`/issue\`, so this step typically no-ops; left in place for legacy issues whose research was persisted under the previous fanout.
3. Make the minimal edit. No redesign, no surrounding refactor, no "while I'm here" cleanup.
4. Add/update a test ONLY if the touched code is already tested.
5. Run the relevant test file + lint for that package.
6. Commit with conventional message. Open PR with \`Closes #$ISSUE_NUM\` in the body.
7. **Capture the PR URL from \`gh pr create\` output and invoke the \`uberdev:review-pr\` skill via the Skill tool with that URL.** This is the canonical run site for the 3-lens simplify ceremony (Phase 2: reuse / quality / efficiency); it does NOT fire if you skip this step. Findings are advisory — do NOT block on REVISIONS_REQUIRED (the auto-fix loop is deferred).
Do NOT run /uberdev:simplify standalone before push — Phase 2 of /uberdev:review-pr runs it automatically on a strictly larger diff (full PR + review-fix commits).
Skip /uberdev:brainstorm. Skip multi-step planning. Escalate to /uberdev:brainstorm ONLY if the scope turns out to be materially larger than triaged.
EOF
else
cat > /tmp/solve-prompt-$ISSUE_NUM.txt << EOF
Solve GH issue #$ISSUE_NUM directly. Triaged as TRIVIAL.
Steps:
1. \`gh issue view $ISSUE_NUM\` — read the ask.
2. Make the minimal edit. No redesign, no surrounding refactor, no "while I'm here" cleanup.
3. Add/update a test ONLY if the touched code is already tested.
4. Run the relevant test file + lint for that package.
5. Commit with conventional message. Open PR with \`Closes #$ISSUE_NUM\` in the body.
6. **Capture the PR URL from \`gh pr create\` output and invoke the \`uberdev:review-pr --turbo\` skill via the Skill tool with that URL.** This is the canonical run site for the 3-lens simplify ceremony (Phase 2: reuse / quality / efficiency); it does NOT fire if you skip this step. Findings are advisory.
Do NOT run /uberdev:simplify standalone before push — Phase 2 of /uberdev:review-pr runs it automatically on a strictly larger diff (full PR + review-fix commits).
Skip /uberdev:brainstorm. Skip multi-step planning. Escalate to /uberdev:brainstorm ONLY if the scope turns out to be materially larger than triaged.
EOF
fi
small:
if [[ "$AUTO_MODE" != "1" ]]; then
cat > /tmp/solve-prompt-$ISSUE_NUM.txt << EOF
Solve GH issue #$ISSUE_NUM with a lightweight plan. Triaged as SMALL.
Steps:
1. \`gh issue view $ISSUE_NUM\` — read the ask.
2. **Read pre-collected research (legacy cache)** — for each file in \`.uberdev/research/issue-$ISSUE_NUM/{constraints,prior-art,security}.md\` that exists, read the \`summary:\` block and inline its key findings into your TodoWrite plan as constraints/considerations. After issue #14 the cache is no longer written by \`/issue\`, so this step typically no-ops; left in place for legacy issues.
3. Write 3–6 TodoWrite tasks. Skip /uberdev:brainstorm — scope is clear.
4. TDD: write the failing test first, then implement, then green.
5. Commit + PR with \`Closes #$ISSUE_NUM\`.
6. **Capture the PR URL from \`gh pr create\` output and invoke the \`uberdev:review-pr\` skill via the Skill tool with that URL.** This is the canonical run site for the 3-lens simplify ceremony (Phase 2: reuse / quality / efficiency); it does NOT fire if you skip this step. Findings are advisory — do NOT block on REVISIONS_REQUIRED (the auto-fix loop is deferred).
Do NOT run /uberdev:simplify standalone before push — Phase 2 of /uberdev:review-pr runs it automatically on a strictly larger diff (full PR + review-fix commits).
Escalate to /uberdev:brainstorm if the scope proves larger than triaged.
EOF
else
cat > /tmp/solve-prompt-$ISSUE_NUM.txt << EOF
Solve GH issue #$ISSUE_NUM with a lightweight plan. Triaged as SMALL.
Steps:
1. \`gh issue view $ISSUE_NUM\` — read the ask.
2. Write 3–6 TodoWrite tasks. Skip /uberdev:brainstorm — scope is clear.
3. TDD: write the failing test first, then implement, then green.
4. Commit + PR with \`Closes #$ISSUE_NUM\`.
5. **Capture the PR URL from \`gh pr create\` output and invoke the \`uberdev:review-pr --turbo\` skill via the Skill tool with that URL.** This is the canonical run site for the 3-lens simplify ceremony (Phase 2: reuse / quality / efficiency); it does NOT fire if you skip this step. Findings are advisory.
Do NOT run /uberdev:simplify standalone before push — Phase 2 of /uberdev:review-pr runs it automatically on a strictly larger diff (full PR + review-fix commits).
Escalate to /uberdev:brainstorm if the scope proves larger than triaged.
EOF
fi
medium (and --full):
if [[ "$AUTO_MODE" == "1" ]]; then
echo "/uberdev:orchestrator --turbo solve GH issue #$ISSUE_NUM" > /tmp/solve-prompt-$ISSUE_NUM.txt
else
echo "/uberdev:orchestrator solve GH issue #$ISSUE_NUM" > /tmp/solve-prompt-$ISSUE_NUM.txt
fi
5b. Write launcher script
The launcher cds to repo root, cleans stale worktree, logs errors, keeps terminal open on failure. REAL_CLAUDE was resolved once in Step 3 (same value across every spawn).
Wall-clock timeout. The launcher reads
command_timeouts.solve from .claude/uberdev.local.md (env override:
UBERDEV_SOLVE_TIMEOUT; default 3600s; range [60, 86400]) and wraps the
claude invocation in timeout(1) when the binary is on PATH. If
timeout(1) is unavailable (rare on bare macOS without coreutils) the
launcher emits one stderr warning and runs unwrapped — fail-open per
the aliases-sync.sh:14-23 jq-absent precedent. /merge and
/review-pr parse command_timeouts.{merge, review_pr} but only
surface the values in the run audit log; v1 does NOT enforce wall-clock
kill on those commands (the orchestrator turn loop runs inside an
existing Claude session and would need deeper changes to honour a
kill). v2 issue can extend.
cat > /tmp/solve-$ISSUE_NUM.sh << 'SCRIPT_EOF'
set -e
export TERMINAL="DETECTED_TERMINAL"
cd "REPO_ROOT"
if git worktree list | grep -q "solve-issue-ISSUE_NUM"; then
git worktree remove .claude/worktrees/solve-issue-ISSUE_NUM --force 2>/dev/null || true
fi
git branch -D worktree-solve-issue-ISSUE_NUM 2>/dev/null || true
PROMPT=$(cat /tmp/solve-prompt-ISSUE_NUM.txt)
echo "Starting claude agent for issue #ISSUE_NUM (tier: TIER)..."
PERM_FLAG="PERM_FLAG_VALUE"
if [ -r "CLAUDE_PLUGIN_ROOT_VAL/lib/config-read.sh" ]; then
. "CLAUDE_PLUGIN_ROOT_VAL/lib/config-read.sh"
SOLVE_TIMEOUT="$(uberdev_read_int_in_range command_timeouts.solve UBERDEV_SOLVE_TIMEOUT 60 86400 3600)"
else
echo "warning: config-read.sh not found at CLAUDE_PLUGIN_ROOT_VAL/lib/; uberdev.local.md timeout settings ignored" >&2
SOLVE_TIMEOUT=3600
fi
if command -v timeout >/dev/null 2>&1; then
timeout "${SOLVE_TIMEOUT}" CLAUDE_BIN --model 'claude-opus-4-7[1m]' --effort max --worktree solve-issue-ISSUE_NUM $PERM_FLAG "$PROMPT"
else
echo "warning: timeout(1) not on PATH; /solve will run unwrapped (no wall-clock kill)" >&2
CLAUDE_BIN --model 'claude-opus-4-7[1m]' --effort max --worktree solve-issue-ISSUE_NUM $PERM_FLAG "$PROMPT"
fi
SCRIPT_EOF
SED_INPLACE=(-i '')
[[ "$(uname)" == "Linux" ]] && SED_INPLACE=(-i)
PERM_FLAG_VAL=""
[[ "$AUTO_PERMISSIONS" == "1" ]] && PERM_FLAG_VAL="--permission-mode auto"
sed "${SED_INPLACE[@]}" \
-e "s|REPO_ROOT|$(pwd)|g" \
-e "s|CLAUDE_BIN|$REAL_CLAUDE|g" \
-e "s/ISSUE_NUM/$ISSUE_NUM/g" \
-e "s/TIER/$TIER/g" \
-e "s/DETECTED_TERMINAL/${TERMINAL:-cmux}/g" \
-e "s|PERM_FLAG_VALUE|$PERM_FLAG_VAL|g" \
-e "s|CLAUDE_PLUGIN_ROOT_VAL|${CLAUDE_PLUGIN_ROOT:-}|g" \
/tmp/solve-$ISSUE_NUM.sh
chmod +x /tmp/solve-$ISSUE_NUM.sh
5c. Spawn agent in new terminal session
Tab/window shows issue + title; description encodes the tier for audit. Each branch invokes zsh -l /tmp/solve-$ISSUE_NUM.sh. Use the literal title from Phase A (already truncated). The for-loop closes here (done).
TAB_NAME="#$ISSUE_NUM $TITLE"
DESCRIPTION="[$TIER] Solve GH issue #$ISSUE_NUM: $TITLE"
SCRIPT="/tmp/solve-$ISSUE_NUM.sh"
case "$TERMINAL" in
cmux)
cmux new-workspace --name "$TAB_NAME" --description "$DESCRIPTION" --command "zsh -l $SCRIPT"
;;
ghostty)
if [[ "$SOLVE_GHOSTTY_NEW_WINDOW" == "1" ]] || [[ "$TERM_PROGRAM" != "ghostty" ]]; then
GHOSTTY_SPAWN_KEY="n"
GHOSTTY_LAUNCH_DELAY="0.5"
else
GHOSTTY_SPAWN_KEY="t"
GHOSTTY_LAUNCH_DELAY="0.15"
fi
if osascript >/dev/null 2>&1 <<APPLESCRIPT
tell application "Ghostty" to activate
delay $GHOSTTY_LAUNCH_DELAY
tell application "System Events"
keystroke "$GHOSTTY_SPAWN_KEY" using command down
delay 0.25
keystroke "zsh -l $SCRIPT"
keystroke return
end tell
APPLESCRIPT
then
echo "Dispatched into Ghostty (Cmd+$GHOSTTY_SPAWN_KEY)."
else
GHOSTTY_LOG="/tmp/solve-$ISSUE_NUM.log"
nohup zsh -l "$SCRIPT" > "$GHOSTTY_LOG" 2>&1 &
echo "warning: ghostty AppleScript dispatch failed (Accessibility permission or non-default Cmd+T/N keybind?); spawned detached agent (PID $!). Logs: $GHOSTTY_LOG" >&2
fi
;;
iterm)
osascript <<APPLESCRIPT
tell application "iTerm"
activate
create window with default profile command "zsh -l $SCRIPT"
end tell
APPLESCRIPT
;;
terminal)
osascript <<APPLESCRIPT
tell application "Terminal"
activate
do script "zsh -l $SCRIPT"
end tell
APPLESCRIPT
;;
nohup|*)
LOG="/tmp/solve-$ISSUE_NUM.log"
nohup zsh -l "$SCRIPT" > "$LOG" 2>&1 &
echo "Spawned detached (PID $!). Logs: $LOG"
;;
esac
DISPATCH_RC=$?
if [[ $DISPATCH_RC -eq 0 ]]; then
SPAWNED+=("#$ISSUE_NUM ($TIER)")
else
DISPATCH_FAILED+=("#$ISSUE_NUM ($TERMINAL exit=$DISPATCH_RC)")
fi
if [[ "$TERMINAL" == "ghostty" && ${#ISSUE_NUMS[@]} -gt 1 ]]; then
sleep 0.6
fi
done
if [[ ${#DISPATCH_FAILED[@]} -gt 0 ]]; then
echo "warning: ${#DISPATCH_FAILED[@]} of ${#ISSUE_NUMS[@]} dispatch(es) failed:" >&2
printf ' - %s\n' "${DISPATCH_FAILED[@]}" >&2
fi
6. Brief delay and confirm — single summary notification
cmux's native notifier is preferred (matches existing UX); otherwise fall through to terminal-notifier then osascript display notification. One summary notification per /turbo invocation — N back-to-back notifications would be noisy. The body lists every successfully spawned issue and (if any) the count of dispatch failures.
sleep 1
NOTIFY_BODY="Spawned ${#SPAWNED[@]} agent$([[ ${#SPAWNED[@]} -ne 1 ]] && echo s): $(IFS=', '; echo "${SPAWNED[*]}") (terminal: $TERMINAL$([[ "$AUTO_PERMISSIONS" == "1" ]] && echo ', auto')$([[ "$AUTO_MODE" == "1" ]] && echo ', turbo'))$([[ ${#DISPATCH_FAILED[@]} -gt 0 ]] && echo " — ${#DISPATCH_FAILED[@]} dispatch failure(s), see stderr")"
if [[ "$TERMINAL" == "cmux" ]] && command -v cmux >/dev/null 2>&1; then
cmux notify --title "Orchestrator" --body "$NOTIFY_BODY"
elif command -v terminal-notifier >/dev/null 2>&1; then
terminal-notifier -title "Orchestrator" -message "$NOTIFY_BODY"
elif command -v osascript >/dev/null 2>&1; then
osascript -e "display notification \"$NOTIFY_BODY\" with title \"Orchestrator\""
fi
7. Post-action — rename tab to PR number once opened (optional)
After opening its PR, each spawned agent renames its own tab from #<issue> <title> to PR #<pr> <title>. cmux uses its workspace API; everything else uses OSC escape sequences (Ghostty, iTerm2, Terminal.app all honor OSC 1 for tab title and OSC 2 for window title). Apply the same ~40-char truncation rule if $PR_TITLE is long.
The spawned agent inherits $TERMINAL from its launcher (exported in Step 5b — adjust the launcher heredoc to write export TERMINAL='$TERMINAL' near the top if you want this to work cleanly; otherwise the agent re-detects via the same logic above).
PR_NUM=$(gh pr view --json number --jq .number)
PR_TITLE=$(gh pr view --json title --jq .title)
NEW_TITLE="PR #$PR_NUM $PR_TITLE"
case "${TERMINAL:-cmux}" in
cmux)
cmux workspace-action --action rename --title "$NEW_TITLE"
;;
ghostty|iterm|terminal)
printf '\e]2;%s\a' "$NEW_TITLE"
printf '\e]1;PR #%s\a' "$PR_NUM"
;;
*) ;;
esac