# Headless Launch Script Template This template is used by the session-decomposer agent to generate a launch script for headless execution of decomposed sessions. ## Template ```bash #!/usr/bin/env bash # Headless launch script — generated by ultraplan-local # Master plan: {plan_path} # Generated: {date} # Sessions: {total_sessions} ({parallel_count} parallel, {sequential_count} sequential) set -euo pipefail # Prevent accidental API billing — remove this line if you intend to use API credits unset ANTHROPIC_API_KEY REPO_ROOT="$(git rev-parse --show-toplevel)" PLAN_DIR="{session_dir}" LOG_DIR="{session_dir}/logs" WORKTREE_BASE="{session_dir}/worktrees" mkdir -p "$LOG_DIR" "$WORKTREE_BASE" # Cleanup trap — always remove worktrees on exit (success or failure) cleanup_worktrees() { echo "" echo "=== Cleaning up worktrees ===" cd "$REPO_ROOT" for wt in "$WORKTREE_BASE"/session-*; do [ -d "$wt" ] && git worktree remove "$wt" --force 2>/dev/null && echo "Removed: $wt" done git worktree prune git branch --list "ultraplan/{slug}/*" | while read b; do git branch -D "$b" 2>/dev/null done rmdir "$WORKTREE_BASE" 2>/dev/null echo "Cleanup complete." } trap cleanup_worktrees EXIT # Pre-flight: verify clean working tree if [ -n "$(git status --porcelain)" ]; then echo "ERROR: Working tree is not clean. Commit or stash changes before parallel execution." git status --short exit 1 fi # Pre-flight: verify remote push permissions (catches credential/auth issues # BEFORE spawning sessions). Sub-agent bash sandbox may have different # credentials than the launching shell — Step 0 in each session spec handles # the sandbox-side detection. Set ULTRAEXECUTE_SKIP_PREFLIGHT=1 for offline # or air-gapped testing. if [ "${ULTRAEXECUTE_SKIP_PREFLIGHT:-0}" != "1" ]; then if ! git push --dry-run origin HEAD >/tmp/push-dryrun-launch.log 2>&1; then echo "ERROR: git push --dry-run failed. Sessions will be unable to push." cat /tmp/push-dryrun-launch.log echo "" echo "Fix remote credentials before running parallel execution, or set" echo "ULTRAEXECUTE_SKIP_PREFLIGHT=1 to bypass (offline/air-gapped only)." exit 1 fi if grep -qE "(rejected|denied|forbidden|permission)" /tmp/push-dryrun-launch.log; then echo "ERROR: git push --dry-run reports rejection. Sessions will fail at commit time." cat /tmp/push-dryrun-launch.log exit 1 fi fi echo "=== Ultraplan Headless Execution (Worktree-Isolated) ===" echo "Plan: {plan_path}" echo "Sessions: {total_sessions}" echo "Repo root: $REPO_ROOT" echo "" # --- Wave {N}: Parallel sessions (no dependencies) --- echo "--- Wave {N}: {description} ---" {# For each parallel session in this wave, create worktree: } git worktree add -b "ultraplan/{slug}/session-{n}" "$WORKTREE_BASE/session-{n}" HEAD echo "Worktree created: session-{n} (branch: ultraplan/{slug}/session-{n})" {# Launch session in its worktree: } cd "$WORKTREE_BASE/session-{n}" && claude -p "$(cat "$PLAN_DIR/session-{n}-{slug}.md")" \ --allowedTools "Read,Write,Edit,Bash,Glob,Grep" \ --permission-mode bypassPermissions \ > "$LOG_DIR/session-{n}.log" 2>&1 & PID_{n}=$! cd "$REPO_ROOT" echo "Started session {n}: {title} (PID $PID_{n})" {# After all parallel sessions in this wave: } echo "Waiting for Wave {N} to complete..." wait $PID_{n1} $PID_{n2} echo "Wave {N} complete." echo "" # --- Merge wave results (sequential) --- echo "--- Merging Wave {N} ---" cd "$REPO_ROOT" {# For each session in the wave, merge its branch: } git merge --no-ff "ultraplan/{slug}/session-{n}" \ -m "merge: ultraplan session {n} — {title}" if [ $? -ne 0 ]; then echo "MERGE CONFLICT: session {n}. Conflicting files:" git diff --name-only --diff-filter=U git merge --abort echo "Aborting. Earlier sessions in this wave are already merged." exit 1 fi git worktree remove "$WORKTREE_BASE/session-{n}" --force git branch -d "ultraplan/{slug}/session-{n}" echo "Merged and cleaned: session {n}" git worktree prune # --- Verify wave results --- echo "--- Verifying Wave {N} ---" {# For each session in the wave, run its exit condition commands } {verify_commands} # --- Wave {N+1}: Sequential sessions (depends on previous wave) --- {# Repeat wave pattern for dependent sessions } echo "" echo "=== All sessions complete ===" echo "Review logs in $LOG_DIR/" echo "Run final verification: {final_verify_command}" ``` ## Rules for the session-decomposer When generating a launch script from this template: 1. **Group sessions into waves** by dependency. Sessions with no dependencies or whose dependencies are all in earlier waves can run in the same wave. 2. **Each wave waits for completion** before the next wave starts. 3. **Verification runs after each wave** — if verification fails, the script stops and reports which session failed. 4. **Log each session** to a separate file for debugging. 5. **Use `claude -p`** with the session spec file as the prompt. 6. **Use `--allowedTools "Read,Write,Edit,Bash,Glob,Grep"`** with `--permission-mode bypassPermissions` for child sessions. This limits the tool surface to what the executor needs and prevents agent spawning, MCP access, and external web requests in headless sessions. 7. **Final verification** at the end runs the master plan's verification section. 8. **Never include secrets** in the generated script. 9. **Wave verification must be independent.** After each wave completes, run verification commands fresh via Bash — never parse session log files as proof of success. Log files contain executor self-reporting, not ground truth. The command's exit code is the only authoritative verification signal. 10. **Billing preamble.** Prepend `unset ANTHROPIC_API_KEY` with a comment at the top of the script to prevent accidental API billing. Users who intend to use API credits can remove this line. 11. **Worktree isolation is mandatory.** Every parallel wave MUST use git worktrees. Each session gets its own worktree and branch. Never launch parallel `claude -p` sessions in the same working directory. 12. **Cleanup trap on EXIT.** The generated script MUST include a `trap` on EXIT that removes all worktrees (`git worktree remove --force`) and prunes branches, even if the script fails or is interrupted. 13. **Sequential merge after each wave.** After all sessions in a wave complete, merge their branches back to the main branch one at a time. Abort on merge conflict — do not force-resolve. 14. **Clean working tree before worktrees.** Add a `git status --porcelain` check at the top of the script. Fail if the working tree is dirty. 15. **Absolute paths for logs.** Log file paths must be absolute (resolved from `$REPO_ROOT`), not relative to any worktree.