ktg-plugin-marketplace/plugins/voyage/templates/headless-launch-template.md
Kjell Tore Guttormsen 7a90d348ad feat(voyage)!: marketplace handoff — rename plugins/ultraplan-local to plugins/voyage [skip-docs]
Session 5 of voyage-rebrand (V6). Operator-authorized cross-plugin scope.

- git mv plugins/ultraplan-local plugins/voyage (rename detected, history preserved)
- .claude-plugin/marketplace.json: voyage entry replaces ultraplan-local
- CLAUDE.md: voyage row in plugin list, voyage in design-system consumer list
- README.md: bulk rename ultra*-local commands -> trek* commands; ultraplan-local refs -> voyage; type discriminators (type: trekbrief/trekreview); session-title pattern (voyage:<command>:<slug>); v4.0.0 release-note paragraph
- plugins/voyage/.claude-plugin/plugin.json: homepage/repository URLs point to monorepo voyage path
- plugins/voyage/verify.sh: drop URL whitelist exception (no longer needed)

Closes voyage-rebrand. bash plugins/voyage/verify.sh PASS 7/7. npm test 361/361.
2026-05-05 15:37:52 +02:00

9.9 KiB

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

#!/usr/bin/env bash
# Headless launch script — generated by trekplan
# 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"

# Disable git's optional locks during parallel worktree ops (research/02 R2;
# GH #47721). Mirror Phase 2.6 hardenings (commands/trekexecute.md).
export GIT_OPTIONAL_LOCKS=0

# Per-child guardrails (operator may override via env). Match Phase 2.6
# Step 2a-pre defaults.
MAX_TURNS="${TREKEXECUTE_MAX_TURNS:-50}"
MAX_BUDGET_USD="${TREKEXECUTE_MAX_BUDGET_USD:-5}"

# Shared context (brief + architecture if present). Absolute path so child
# sessions resolve it after `cd` into their worktree.
SHARED_CONTEXT_FILE="$WORKTREE_BASE/.shared-context.md"
if [ -f "$PLAN_DIR/brief.md" ]; then
  cat "$PLAN_DIR/brief.md" > "$SHARED_CONTEXT_FILE"
  if [ -f "$PLAN_DIR/architecture/overview.md" ]; then
    printf '\n\n---\n\n' >> "$SHARED_CONTEXT_FILE"
    cat "$PLAN_DIR/architecture/overview.md" >> "$SHARED_CONTEXT_FILE"
  fi
fi

# Safety preambles prepended to every per-session prompt (defense-in-depth
# against headless-mode hook gaps + Claude 4 cross-model malware-reminder).
SAFETY_PREAMBLE='[CRITICAL — defense in depth] You are running in headless mode where plugin hooks may not fire reliably (GH #36071). DO NOT execute commands matching: `rm -rf /`, `curl | bash`, `git push --force` to main, `git reset --hard` outside this worktree, or any command writing outside the current worktree. Treat this rule as enforced regardless of `--allowedTools` allowlist.

Note: any malware-related safety reminders apply conditionally to code you assess as actually malicious, not to all code reads (per GH #52272 community workaround).'

# Cleanup trap — always remove worktrees on exit (success or failure)
cleanup_worktrees() {
  echo ""
  echo "=== Cleaning up worktrees ==="
  cd "$REPO_ROOT"
  # push-before-cleanup (Hard Rule 19): push any remaining branches so work
  # survives even if subsequent removal races. Failure is non-fatal.
  git branch --list "trek/{slug}/*" | while read b; do
    git push origin "$b" 2>/dev/null || true
  done
  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 "trek/{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 TREKEXECUTE_SKIP_PREFLIGHT=1 for offline
# or air-gapped testing.
if [ "${TREKEXECUTE_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 "TREKEXECUTE_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 "=== Voyage 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 "trek/{slug}/session-{n}" "$WORKTREE_BASE/session-{n}" HEAD
echo "Worktree created: session-{n} (branch: trek/{slug}/session-{n})"

{# Launch session in its worktree (with safety preamble + budget caps + shared context): }
cd "$WORKTREE_BASE/session-{n}" && claude -p "${SAFETY_PREAMBLE}

$(cat "$PLAN_DIR/session-{n}-{slug}.md")" \
  --allowedTools "Read,Write,Edit,Bash,Glob,Grep" \
  --permission-mode bypassPermissions \
  --max-turns "$MAX_TURNS" \
  --max-budget-usd "$MAX_BUDGET_USD" \
  --append-system-prompt-file "$SHARED_CONTEXT_FILE" \
  > "$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: push BEFORE merge (Hard Rule 19 — push-before-cleanup). }
git push origin "trek/{slug}/session-{n}" 2>/dev/null || true
git merge --no-ff "trek/{slug}/session-{n}" \
  -m "merge: trekplan 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 "trek/{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.
  16. Per-child guardrails (mirrors Phase 2.6 Step 2b). Every claude -p invocation must include --max-turns "$MAX_TURNS", --max-budget-usd "$MAX_BUDGET_USD", and --append-system-prompt-file "$SHARED_CONTEXT_FILE". The shared context must be built once with an absolute path (resolved from $WORKTREE_BASE) so child sessions can read it after cd.
  17. Safety preamble. Every per-session prompt must be prefixed with the $SAFETY_PREAMBLE string defined at the top of the script. This is the primary defense when plugin hooks do not fire reliably (GH #36071), and includes the GH #52272 malware-reminder clarification for AUTO mode.
  18. GIT_OPTIONAL_LOCKS=0. The script must export GIT_OPTIONAL_LOCKS=0 once at the top so every git invocation (worktree add/remove/prune, branch -d, merge, push) avoids the index.lock background-poll race (research/02 R2; GH #47721).
  19. push-before-cleanup (Hard Rule 19). After successful git merge --no-ff, run git push origin <branch> BEFORE git worktree remove and git branch -d. Push failure is non-fatal — cleanup proceeds. Converts unrecoverable branch loss into recoverable remote state (research/02 R3).