Fleet Ops (experimental) Manage how committed work from isolated lanes lands on . Anything before "committed" or after "landed" is somebody else's problem. Status: experimental. Dogfooding phase. API may change. Not yet in Recent Updates. Core abstraction A lane = one branch (or worktree), one Claude session, one logical unit of work. Lane status: . The skill doesn't care if there are 2 lanes or 20, doesn't care about branch names, doesn't care if you use worktrees or separate clones. CLI surface Daemon lifecycle When Claude invokes via , the daemon: 1. Writes its PID to 2. Traps and removes…

|| true)\n if [[ -n \"$other_changes\" ]]; then\n log \"ACTION REQUIRED: .gitignore updated for fleet-ops runtime paths,\"\n log \" but other uncommitted changes exist on $BASE_BRANCH.\"\n log \" Commit .gitignore yourself before 'fleet start' or\"\n log \" the daemon will refuse to land. Suggested:\"\n log \" git add .gitignore && git commit -m 'chore: gitignore fleet-ops runtime state'\"\n return 0\n fi\n git add .gitignore 2>/dev/null || { log \"WARN: git add .gitignore failed\"; return 0; }\n if git commit -m \"chore: gitignore fleet-ops runtime state\" -- .gitignore >/dev/null 2>&1; then\n log \"auto-committed .gitignore (fleet-ops runtime paths: .claude/fleet/, .fleet-worktrees/)\"\n else\n log \"WARN: auto-commit of .gitignore failed — commit it manually before 'fleet start'\"\n fi\n}\n\nensure_fleet_dir() {\n mkdir -p \"$LANES_DIR\"\n [[ -f \"$FLEET_DIR/signal.sh\" ]] || cp \"$SCRIPT_DIR/signal.sh\" \"$FLEET_DIR/signal.sh\"\n chmod +x \"$FLEET_DIR/signal.sh\" 2>/dev/null || true\n # Auto-ignore fleet-ops runtime state in git so it doesn't show as \"dirty\"\n # or get committed. Two paths:\n # .claude/fleet/ — lanes/, daemon.pid, activity.log, signal.sh, config\n # .fleet-worktrees/ — default worktree root (top-level so headless\n # Claude lane sessions can write there)\n if git rev-parse --git-dir >/dev/null 2>&1; then\n [[ -f .gitignore ]] || touch .gitignore\n local appended=0\n if ! grep -qxF '.claude/fleet/' .gitignore 2>/dev/null; then\n echo '.claude/fleet/' >> .gitignore\n appended=1\n fi\n if ! grep -qxF '.fleet-worktrees/' .gitignore 2>/dev/null; then\n echo '.fleet-worktrees/' >> .gitignore\n appended=1\n fi\n [[ $appended -eq 1 ]] && maybe_commit_gitignore\n fi\n}\n\nis_dirty_tracked() {\n # True only if tracked files have uncommitted changes (ignores untracked files)\n ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null\n}\n\nlane_state() { [[ -f \"$LANES_DIR/$1\" ]] && head -n1 \"$LANES_DIR/$1\" || echo \"MISSING\"; }\nset_lane_state() {\n local l=$1 s=$2\n shift 2\n if [[ $# -gt 0 ]]; then\n printf '%s\\n%s\\n' \"$s\" \"$*\" > \"$LANES_DIR/$l\"\n else\n printf '%s\\n' \"$s\" > \"$LANES_DIR/$l\"\n fi\n}\n\nscrub_diff() {\n # echoes hits (one per line) for given branch's diff vs base. Empty = clean.\n local branch=$1\n git diff \"$BASE_BRANCH\"...\"$branch\" 2>/dev/null | grep -nE \"$FORBIDDEN_PATTERN\" || true\n}\n\nrefuse_if_shared_tree() {\n local trees lane_count\n trees=$(git worktree list --porcelain 2>/dev/null | awk '/^worktree /{print $2}' | sort -u | wc -l)\n lane_count=$(ls -1 \"$LANES_DIR\" 2>/dev/null | wc -l)\n if [[ \"$lane_count\" -gt 1 && \"$trees\" -le 1 && \"$MODE\" != \"branch\" ]]; then\n log \"ERROR: $lane_count lanes but only $trees worktree — sessions will collide\"\n log \" Use worktrees, separate clones, or set mode=branch in $CONFIG to override\"\n return 1\n fi\n}\n\ncmd_init() {\n ensure_fleet_dir\n [[ $# -eq 0 ]] && { echo \"usage: fleet init \u003cname>...\" >&2; exit 1; }\n\n local mode=\"$MODE\"\n [[ \"$mode\" == \"auto\" ]] && mode=\"worktree\" # default: worktree if git allows it\n\n for name in \"$@\"; do\n if git rev-parse --verify \"$name\" >/dev/null 2>&1; then\n log \"skip branch (exists): $name\"\n else\n git branch \"$name\" \"$BASE_BRANCH\"\n log \"created branch: $name\"\n fi\n if [[ \"$mode\" == \"worktree\" ]]; then\n local wt=\"$WORKTREE_ROOT/$name\"\n if [[ -d \"$wt\" ]]; then\n log \"skip worktree (exists): $wt\"\n else\n mkdir -p \"$WORKTREE_ROOT\"\n git worktree add \"$wt\" \"$name\"\n log \"created worktree: $wt\"\n fi\n fi\n set_lane_state \"$name\" \"RUNNING\"\n done\n\n echo \"\"\n echo \"Fleet initialized. Hand each session the prompt template:\"\n echo \" $SCRIPT_DIR/../references/session-prompt.md\"\n echo \"Then: bash $0 start\"\n}\n\nformat_age() {\n local secs=$1\n if [[ $secs -lt 60 ]]; then printf '%ds' \"$secs\"\n elif [[ $secs -lt 3600 ]]; then printf '%dm' \"$((secs/60))\"\n else printf '%dh%dm' \"$((secs/3600))\" \"$(( (secs%3600)/60 ))\"\n fi\n}\n\nicon_for_state() {\n case \"$1\" in\n RUNNING) echo \"$ICON_RUNNING\" ;;\n READY) echo \"$ICON_READY\" ;;\n LANDED) echo \"$ICON_LANDED\" ;;\n FAILED) echo \"$ICON_FAILED\" ;;\n CONFLICT) echo \"$ICON_CONFLICT\" ;;\n *) echo \"$ICON_UNKNOWN\" ;;\n esac\n}\n\n# Bucket lanes by state into parallel arrays. Sets:\n# total, active — globals\n# state_buckets[0..4] — newline-joined \"branch|age|meta\"\n# state_counts[0..4] — count per state\n# Order: 0=RUNNING 1=READY 2=CONFLICT 3=FAILED 4=LANDED\n__fleet_bucket() {\n total=0; active=0\n state_buckets=(\"\" \"\" \"\" \"\" \"\")\n state_counts=(0 0 0 0 0)\n local now=$(date +%s)\n for f in \"$LANES_DIR\"/*; do\n [[ -f \"$f\" ]] || continue\n total=$((total+1))\n local branch state meta mtime secs age idx\n branch=$(basename \"$f\")\n state=$(head -n1 \"$f\")\n meta=$(sed -n '2p' \"$f\")\n mtime=$(file_mtime \"$f\")\n secs=$((now - mtime))\n age=$(format_age \"$secs\")\n [[ \"$state\" != \"LANDED\" && \"$state\" != \"FAILED\" ]] && active=$((active+1))\n idx=-1\n case \"$state\" in\n RUNNING) idx=0 ;;\n READY) idx=1 ;;\n CONFLICT) idx=2 ;;\n FAILED) idx=3 ;;\n LANDED) idx=4 ;;\n esac\n [[ $idx -lt 0 ]] && continue\n state_counts[$idx]=$(( state_counts[idx] + 1 ))\n state_buckets[$idx]=\"${state_buckets[$idx]}${branch}|${age}|${meta}\"

Fleet Ops (experimental) Manage how committed work from isolated lanes lands on . Anything before "committed" or after "landed" is somebody else's problem. Status: experimental. Dogfooding phase. API may change. Not yet in Recent Updates. Core abstraction A lane = one branch (or worktree), one Claude session, one logical unit of work. Lane status: . The skill doesn't care if there are 2 lanes or 20, doesn't care about branch names, doesn't care if you use worktrees or separate clones. CLI surface Daemon lifecycle When Claude invokes via , the daemon: 1. Writes its PID to 2. Traps and removes…

\\n'\n done\n}\n\n# Daemon health → \"healthy\" or \"busted\"\n__fleet_daemon_state() {\n if [[ -f \"$PID_FILE\" ]]; then\n local pid\n pid=$(cat \"$PID_FILE\" 2>/dev/null || echo \"\")\n if [[ -n \"$pid\" ]] && kill -0 \"$pid\" 2>/dev/null; then\n printf 'healthy'\n return\n fi\n fi\n printf 'busted'\n}\n\n# Footer composition shared by all panel views.\n__fleet_footer() {\n local active=$1 daemon_state=$2\n local hotkeys\n hotkeys=\"$(term_hotkey R refresh) · $(term_hotkey L land) · $(term_hotkey '?' help)\"\n local healths\n healths=\"$(term_health \"$daemon_state\" \"daemon\")\"\n [[ \"$active\" -gt 0 ]] && healths=\"$healths $(term_health pending \"$active active\")\"\n term_panel_close \"$hotkeys\" \"$healths\"\n}\n\n# Default panel view — design-system grouped tree\nfleet_view_panel() {\n ensure_fleet_dir\n\n local order=(RUNNING READY CONFLICT FAILED LANDED)\n local total active\n local state_buckets state_counts\n __fleet_bucket\n local daemon_state\n daemon_state=$(__fleet_daemon_state)\n\n echo \"\"\n term_panel_open fleet fleet \"$TERM_GLYPH_BRANCH $BASE_BRANCH\"\n\n if [[ $total -eq 0 ]]; then\n term_panel_vert\n term_panel_vert\n printf '%s %s\\n' \"$(term_color dim \"$TERM_TREE_VERT\")\" \"no lanes yet\"\n term_panel_vert\n term_panel_vert\n printf '%s %s %s\\n' \"$(term_color dim \"$TERM_TREE_VERT\")\" \"$TERM_GLYPH_TIP\" \"to get started:\"\n term_panel_vert\n printf '%s 1. fleet init \u003cname>...\\n' \"$(term_color dim \"$TERM_TREE_VERT\")\"\n printf '%s 2. (work in each lane)\\n' \"$(term_color dim \"$TERM_TREE_VERT\")\"\n printf '%s 3. fleet start\\n' \"$(term_color dim \"$TERM_TREE_VERT\")\"\n term_panel_vert\n term_panel_vert\n term_panel_close \"$(term_hotkey '?' help)\" \"$(term_health unknown \"v2.4.9\")\"\n echo \"\"\n return\n fi\n\n term_panel_vert\n term_summary_line \"$total $([ \"$total\" -eq 1 ] && echo lane || echo lanes) · $active active\"\n term_panel_vert\n\n local i\n for i in 0 1 2 3 4; do\n local n=${state_counts[$i]}\n [[ $n -eq 0 ]] && continue\n local state=${order[$i]}\n\n term_section \"$state\" \"$state\" \"$n\"\n\n local lines=\"${state_buckets[$i]}\"\n local c_idx=0 c_last=$((n - 1))\n local branch age meta\n while IFS='|' read -r branch age meta; do\n [[ -z \"$branch\" ]] && continue\n local c_conn\n if [[ $c_idx -eq $c_last ]]; then c_conn=\"$TERM_TREE_LAST\"; else c_conn=\"$TERM_TREE_BRANCH\"; fi\n\n # Build the rail glyph from this lane's commits-ahead and state.\n local ahead head_kind rail\n ahead=$(git rev-list --count \"${BASE_BRANCH}..${branch}\" 2>/dev/null || echo 0)\n head_kind=\"HEAD\"\n [[ \"$state\" == \"CONFLICT\" || \"$state\" == \"FAILED\" ]] && head_kind=\"CONFLICT\"\n rail=$(term_rail \"$ahead\" \"$head_kind\")\n\n term_leaf_line \"$c_conn\" \"$branch\" \"$rail\" \"${meta:-}\" \"$age\"\n c_idx=$((c_idx+1))\n done \u003c\u003c\u003c \"$lines\"\n term_panel_vert\n done\n\n __fleet_footer \"$active\" \"$daemon_state\"\n echo \"\"\n}\n\n# Verbose view — per-lane detail blocks rendered in panel grammar.\n# Each lane gets a header row + sub-rows for worktree, commits, and note.\nfleet_view_verbose() {\n ensure_fleet_dir\n\n local total active\n local state_buckets state_counts\n __fleet_bucket\n local daemon_state\n daemon_state=$(__fleet_daemon_state)\n local now=$(date +%s)\n\n echo \"\"\n term_panel_open fleet \"fleet · verbose\" \"$TERM_GLYPH_BRANCH $BASE_BRANCH\"\n\n if [[ $total -eq 0 ]]; then\n term_panel_vert\n printf '%s no lanes yet\\n' \"$(term_color dim \"$TERM_TREE_VERT\")\"\n term_panel_vert\n term_panel_close \"$(term_hotkey '?' help)\" \"$(term_health unknown \"v2.4.9\")\"\n echo \"\"\n return\n fi\n\n term_panel_vert\n term_summary_line \"$total $([ \"$total\" -eq 1 ] && echo lane || echo lanes) · $active active\"\n term_panel_vert\n\n for f in \"$LANES_DIR\"/*; do\n [[ -f \"$f\" ]] || continue\n local branch state meta mtime age secs wt commits color label_state\n branch=$(basename \"$f\")\n state=$(head -n1 \"$f\")\n meta=$(sed -n '2p' \"$f\")\n mtime=$(file_mtime \"$f\")\n secs=$((now - mtime))\n age=$(format_age \"$secs\")\n wt=$(worktree_path_for \"$branch\" 2>/dev/null || echo \"\")\n commits=$(git rev-list --count \"$BASE_BRANCH..$branch\" 2>/dev/null || echo \"?\")\n\n color=\"\"\n case \"$state\" in\n RUNNING|PENDING|CONFLICT|WARN) color=\"yellow\" ;;\n READY|LANDED|DONE|OK) color=\"green\" ;;\n FAILED|ERROR) color=\"red\" ;;\n esac\n label_state=\"$state\"\n [[ -n \"$color\" ]] && label_state=$(term_color \"$color\" \"$state\")\n\n # Lane header row\n printf '%s%s %-30s %-10s %s\\n' \\\n \"$(term_color dim \"$TERM_TREE_VERT\")\" \\\n \"$(term_color dim \"$TERM_TREE_BRANCH$TERM_PANEL_HRULE\")\" \\\n \"$branch\" \\\n \"$label_state\" \\\n \"$(term_color dim \"$age\")\"\n\n # Detail sub-rows (under the lane's │ continuation)\n if [[ -n \"$wt\" ]]; then\n local wt_short=\"$wt\" repo_root=\"${REPO_ROOT:-}\"\n [[ -n \"$repo_root\" ]] && wt_short=\"${wt#$repo_root/}\"\n if [[ \"$wt_short\" == \"$wt\" && -n \"$repo_root\" ]]; then\n local repo_native\n repo_native=$(cygpath -m \"$repo_root\" 2>/dev/null || echo \"$repo_root\")\n wt_short=\"${wt#$repo_native/}\"\n fi\n printf '%s %s worktree: %s\\n' \\\n \"$(term_color dim \"$TERM_TREE_VERT\")\" \\\n \"$(term_color dim \"$TERM_TREE_VERT\")\" \\\n \"$(term_color dim \"$wt_short\")\"\n fi\n if [[ \"$commits\" != \"?\" && \"$commits\" != \"0\" ]]; then\n printf '%s %s commits: %s ahead of %s\\n' \\\n \"$(term_color dim \"$TERM_TREE_VERT\")\" \\\n \"$(term_color dim \"$TERM_TREE_VERT\")\" \\\n \"$(term_color dim \"$commits\")\" \\\n \"$(term_color dim \"$BASE_BRANCH\")\"\n fi\n if [[ -n \"$meta\" ]]; then\n printf '%s %s note: %s\\n' \\\n \"$(term_color dim \"$TERM_TREE_VERT\")\" \\\n \"$(term_color dim \"$TERM_TREE_VERT\")\" \\\n \"$(term_color dim \"$meta\")\"\n fi\n term_panel_vert\n done\n\n __fleet_footer \"$active\" \"$daemon_state\"\n echo \"\"\n}\n\ncmd_fleet() {\n local mode=\"panel\"\n while [[ $# -gt 0 ]]; do\n case \"$1\" in\n -v|--verbose) mode=\"verbose\"; shift ;;\n -g|--grouped) mode=\"panel\"; shift ;;\n *) shift ;;\n esac\n done\n case \"$mode\" in\n verbose) fleet_view_verbose ;;\n *) fleet_view_panel ;;\n esac\n}\n\ncmd_scrub_check() {\n local branch=${1:-}\n [[ -z \"$branch\" ]] && { echo \"usage: fleet scrub-check \u003cbranch>\" >&2; exit 1; }\n local hits\n hits=$(scrub_diff \"$branch\")\n if [[ -n \"$hits\" ]]; then\n echo \"FORBIDDEN PATTERNS in $branch:\"\n echo \"$hits\" | head -20\n return 1\n fi\n echo \"OK: $branch (no forbidden patterns)\"\n}\n\nland_one() {\n local branch=$1\n local hits\n hits=$(scrub_diff \"$branch\")\n if [[ -n \"$hits\" ]]; then\n log \"REFUSE LAND: $branch failed scrub-check\"\n echo \"$hits\" | head -10 | tee -a \"$LOG\"\n set_lane_state \"$branch\" \"CONFLICT\" \"scrub-check failed\"\n return 1\n fi\n if is_dirty_tracked; then\n log \"REFUSE LAND: $BASE_BRANCH has uncommitted tracked changes — clean before landing\"\n return 1\n fi\n\n log \"LANDING: $branch\"\n git checkout \"$BASE_BRANCH\"\n if git merge \"$branch\" --no-ff -m \"merge: $branch\"; then\n if [[ -n \"$TEST_CMD\" ]]; then\n log \"running tests: $TEST_CMD\"\n if eval \"$TEST_CMD\" >>\"$LOG\" 2>&1; then\n log \"PASS: $branch landed ✓\"\n else\n log \"FAIL: tests failed — reverting $branch\"\n git reset --hard HEAD^\n set_lane_state \"$branch\" \"FAILED\" \"tests failed post-merge\"\n return 1\n fi\n else\n log \"no test_cmd set — trusting signal.sh's log gate\"\n fi\n set_lane_state \"$branch\" \"LANDED\"\n git branch -d \"$branch\" 2>/dev/null || git branch -D \"$branch\" 2>/dev/null || true\n return 0\n else\n log \"MERGE CONFLICT: $branch\"\n git merge --abort 2>/dev/null || true\n set_lane_state \"$branch\" \"CONFLICT\" \"merge conflict with $BASE_BRANCH\"\n return 1\n fi\n}\n\nworktree_path_for() {\n # Echo the worktree path for branch $1, or empty if branch isn't in a worktree\n local branch=$1\n git worktree list --porcelain 2>/dev/null | awk -v want=\"refs/heads/$branch\" '\n /^worktree /{p=$2}\n /^branch /{ if ($2==want) print p }\n '\n}\n\nrebase_others() {\n local landed=$1\n for f in \"$LANES_DIR\"/*; do\n local b state wt\n b=$(basename \"$f\")\n [[ \"$b\" == \"$landed\" ]] && continue\n state=$(lane_state \"$b\")\n [[ \"$state\" == \"LANDED\" || \"$state\" == \"FAILED\" ]] && continue\n git rev-parse --verify \"$b\" >/dev/null 2>&1 || continue\n log \"rebase: $b onto $BASE_BRANCH\"\n\n wt=$(worktree_path_for \"$b\")\n if [[ -n \"$wt\" ]]; then\n # Branch is checked out in a worktree — run rebase from there\n if git -C \"$wt\" rebase \"$BASE_BRANCH\" 2>>\"$LOG\"; then\n log \"rebase OK: $b (in worktree $wt)\"\n else\n log \"rebase CONFLICT: $b\"\n git -C \"$wt\" rebase --abort 2>/dev/null || true\n set_lane_state \"$b\" \"CONFLICT\" \"rebase against $BASE_BRANCH failed\"\n fi\n else\n # Plain branch (no worktree) — rebase via the main repo\n if git rebase \"$BASE_BRANCH\" \"$b\" 2>>\"$LOG\"; then\n log \"rebase OK: $b\"\n else\n log \"rebase CONFLICT: $b\"\n git rebase --abort 2>/dev/null || true\n set_lane_state \"$b\" \"CONFLICT\" \"rebase against $BASE_BRANCH failed\"\n fi\n fi\n done\n git checkout \"$BASE_BRANCH\" 2>/dev/null || true\n}\n\ncmd_land() {\n local branch=${1:-}\n [[ -z \"$branch\" ]] && { echo \"usage: fleet land \u003cbranch>\" >&2; exit 1; }\n land_one \"$branch\" && rebase_others \"$branch\"\n}\n\ncmd_stop() {\n if [[ ! -f \"$PID_FILE\" ]]; then\n echo \"no daemon running (no $PID_FILE)\" >&2\n return 0\n fi\n local pid\n pid=$(cat \"$PID_FILE\")\n if ! kill -0 \"$pid\" 2>/dev/null; then\n log \"stale PID file (pid $pid not alive) — clearing\"\n rm -f \"$PID_FILE\"\n return 0\n fi\n log \"sending SIGTERM to daemon (pid $pid)\"\n kill -TERM \"$pid\" 2>/dev/null || true\n # Wait up to 5s for graceful exit\n local i\n for i in 1 2 3 4 5; do\n sleep 1\n kill -0 \"$pid\" 2>/dev/null || { log \"daemon stopped\"; return 0; }\n done\n log \"daemon didn't exit on SIGTERM, sending SIGKILL\"\n kill -KILL \"$pid\" 2>/dev/null || true\n rm -f \"$PID_FILE\"\n}\n\ncmd_revert() {\n local branch=${1:-}\n [[ -z \"$branch\" ]] && { echo \"usage: fleet revert \u003cbranch>\" >&2; exit 1; }\n local sha\n sha=$(git log \"$BASE_BRANCH\" --merges --grep=\"merge: $branch\" -n1 --format=%H)\n [[ -z \"$sha\" ]] && { log \"ERROR: no merge commit found for $branch on $BASE_BRANCH\"; exit 1; }\n log \"reverting merge $sha (was: $branch)\"\n git checkout \"$BASE_BRANCH\"\n git revert -m 1 \"$sha\" --no-edit\n log \"reverted: $branch\"\n}\n\ndaemon_cleanup() {\n log \"daemon stopping (pid $)\"\n rm -f \"$PID_FILE\"\n}\n\ncmd_start() {\n ensure_fleet_dir\n refuse_if_shared_tree || exit 1\n\n # Refuse if a daemon is already running\n if [[ -f \"$PID_FILE\" ]]; then\n local existing_pid\n existing_pid=$(cat \"$PID_FILE\" 2>/dev/null || echo \"\")\n if [[ -n \"$existing_pid\" ]] && kill -0 \"$existing_pid\" 2>/dev/null; then\n log \"ERROR: daemon already running (pid $existing_pid). Run: fleet stop\"\n exit 1\n else\n log \"stale PID file (pid $existing_pid not alive) — clearing\"\n rm -f \"$PID_FILE\"\n fi\n fi\n\n echo \"$\" > \"$PID_FILE\"\n trap daemon_cleanup EXIT INT TERM HUP\n log \"daemon start (pid $, poll: ${POLL_INTERVAL}s, test_cmd: ${TEST_CMD:-\u003cnone>})\"\n\n while true; do\n local ready=()\n for f in \"$LANES_DIR\"/*; do\n [[ -f \"$f\" && \"$(head -n1 \"$f\")\" == \"READY\" ]] && ready+=(\"$(basename \"$f\")\")\n done\n\n if [[ ${#ready[@]} -gt 0 ]]; then\n for branch in \"${ready[@]}\"; do\n if land_one \"$branch\"; then\n rebase_others \"$branch\"\n fi\n done\n cmd_fleet\n fi\n\n local active=0\n for f in \"$LANES_DIR\"/*; do\n [[ -f \"$f\" ]] || continue\n local s\n s=$(head -n1 \"$f\")\n [[ \"$s\" != \"LANDED\" && \"$s\" != \"FAILED\" ]] && active=$((active+1))\n done\n if [[ $active -eq 0 ]]; then\n log \"all lanes terminal — daemon exiting\"\n cmd_fleet\n break\n fi\n sleep \"$POLL_INTERVAL\"\n done\n}\n\ncase \"${1:-}\" in\n init) shift; cmd_init \"$@\" ;;\n start) shift; cmd_start \"$@\" ;;\n stop) cmd_stop ;;\n status|fleet) shift; cmd_fleet \"$@\" ;;\n land) shift; cmd_land \"$@\" ;;\n revert) shift; cmd_revert \"$@\" ;;\n scrub-check) shift; cmd_scrub_check \"$@\" ;;\n \"\"|-h|--help)\n cat \u003c\u003cEOF\nfleet-ops — landing queue for concurrent Claude sessions (experimental)\n\nUsage:\n fleet init \u003cname>... Create branch + worktree per name\n fleet start Run the daemon (writes pid to $PID_FILE)\n fleet stop Signal the running daemon to exit cleanly\n fleet status One-shot status view\n fleet land \u003cbranch> Manual land + rebase others\n fleet revert \u003cbranch> Revert merge commit on $BASE_BRANCH\n fleet scrub-check \u003cbranch> Dry-run forbidden-pattern check\n\nConfig (optional): $CONFIG\nEOF\n ;;\n *) echo \"unknown subcommand: $1\" >&2; exit 1 ;;\nesac\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":21402,"content_sha256":"acb8e21631b91aa24a865ca636b75c8e23904a264d65284823f551aaf807dcd1"},{"filename":"scripts/signal.sh","content":"#!/usr/bin/env bash\n# fleet-ops/signal.sh — called by Claude sessions to signal lane status\n# Auto-detects the current branch. Refuses dirty trees.\n# Resolves .fleet/ via git common dir, so it works from inside worktrees.\nset -euo pipefail\n\nGIT_COMMON_DIR=$(git rev-parse --git-common-dir 2>/dev/null || true)\n[[ -z \"$GIT_COMMON_DIR\" ]] && { echo \"signal.sh ERROR: not in a git repo\" >&2; exit 2; }\n# git-common-dir is .git/ at main repo root → parent is the main worktree\nMAIN_REPO_ROOT=$(cd \"$GIT_COMMON_DIR/..\" && pwd)\nLANES_DIR=\"$MAIN_REPO_ROOT/.claude/fleet/lanes\"\nBRANCH=$(git branch --show-current 2>/dev/null || true)\n\nif [[ -z \"$BRANCH\" ]]; then\n echo \"signal.sh ERROR: not on a branch (detached HEAD?)\" >&2\n exit 2\nfi\n\nif [[ ! -f \"$LANES_DIR/$BRANCH\" ]]; then\n echo \"signal.sh ERROR: branch '$BRANCH' is not a registered lane (run: fleet track $BRANCH)\" >&2\n exit 2\nfi\n\nSTATE=${1:-}\ncase \"$STATE\" in\n READY)\n if ! git diff --quiet 2>/dev/null || ! git diff --cached --quiet 2>/dev/null; then\n echo \"signal.sh REFUSE: '$BRANCH' has uncommitted tracked changes — commit or stash before signaling READY\" >&2\n git status --short >&2\n exit 1\n fi\n LOG=${2:-}\n if [[ -n \"$LOG\" ]]; then\n [[ -f \"$LOG\" ]] || { echo \"signal.sh ERROR: test log '$LOG' not found\" >&2; exit 1; }\n # crude pass detection — works for pytest, jest, go test, cargo test, mocha\n if grep -qiE \"(failed|error|fail:)\" \"$LOG\" && ! grep -qiE \"0 (failed|errors)\" \"$LOG\"; then\n echo \"signal.sh REFUSE: test log '$LOG' shows failures\" >&2\n grep -iE \"(failed|error)\" \"$LOG\" | head -5 >&2\n exit 1\n fi\n fi\n { echo \"READY\"; [[ -n \"$LOG\" ]] && echo \"log=$LOG\"; } > \"$LANES_DIR/$BRANCH\"\n echo \"signal: $BRANCH → READY\"\n ;;\n CONFLICT)\n REASON=${2:-\"unspecified\"}\n { echo \"CONFLICT\"; echo \"reason=$REASON\"; } > \"$LANES_DIR/$BRANCH\"\n echo \"signal: $BRANCH → CONFLICT ($REASON)\"\n ;;\n RUNNING)\n echo \"RUNNING\" > \"$LANES_DIR/$BRANCH\"\n echo \"signal: $BRANCH → RUNNING\"\n ;;\n *)\n echo \"usage: signal.sh READY [test-log] | CONFLICT [reason] | RUNNING\" >&2\n exit 1\n ;;\nesac\n","content_type":"application/x-sh; charset=utf-8","language":"bash","size":2164,"content_sha256":"2d304ed7762d82bedb9219ecd73cf38520927a950f2026c91680a0966b1f59f1"}],"content_json":{"type":"doc","content":[{"type":"heading","attrs":{"level":1},"content":[{"text":"Fleet Ops (experimental)","type":"text"}]},{"type":"paragraph","content":[{"text":"Manage how committed work from isolated lanes lands on ","type":"text"},{"text":"main","type":"text","marks":[{"type":"code_inline"}]},{"text":". Anything before \"committed\" or after \"landed\" is somebody else's problem.","type":"text"}]},{"type":"blockquote","content":[{"type":"paragraph","content":[{"text":"Status: experimental.","type":"text","marks":[{"type":"strong"}]},{"text":" Dogfooding phase. API may change. Not yet in ","type":"text"},{"text":"README.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" Recent Updates.","type":"text"}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Core abstraction","type":"text"}]},{"type":"paragraph","content":[{"text":"A ","type":"text"},{"text":"lane","type":"text","marks":[{"type":"strong"}]},{"text":" = one branch (or worktree), one Claude session, one logical unit of work. Lane status: ","type":"text"},{"text":"RUNNING | READY | CONFLICT | LANDED | FAILED","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"The skill doesn't care if there are 2 lanes or 20, doesn't care about branch names, doesn't care if you use worktrees or separate clones.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"CLI surface","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"fleet init \u003cname>... Create branch + worktree per name\nfleet start Run the daemon (writes pid to .claude/fleet/daemon.pid)\nfleet stop Signal the running daemon to exit cleanly\nfleet status One-shot status view\nfleet land \u003cbranch> Manual land + rebase others\nfleet revert \u003cbranch> Revert merge commit on main\nfleet scrub-check \u003cbranch> Dry-run forbidden-pattern check","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Daemon lifecycle","type":"text"}]},{"type":"paragraph","content":[{"text":"When Claude invokes ","type":"text"},{"text":"fleet start","type":"text","marks":[{"type":"code_inline"}]},{"text":" via ","type":"text"},{"text":"Bash(run_in_background: true)","type":"text","marks":[{"type":"code_inline"}]},{"text":", the daemon:","type":"text"}]},{"type":"ordered_list","attrs":{"order":1,"listStyle":"number"},"content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Writes its PID to ","type":"text"},{"text":".claude/fleet/daemon.pid","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Traps ","type":"text"},{"text":"SIGINT/SIGTERM/SIGHUP","type":"text","marks":[{"type":"code_inline"}]},{"text":" and removes the PID file on exit","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Refuses to start a second daemon if the PID file references a live process","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Exits naturally when all lanes are terminal (","type":"text"},{"text":"LANDED","type":"text","marks":[{"type":"code_inline"}]},{"text":" or ","type":"text"},{"text":"FAILED","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]}]},{"type":"paragraph","content":[{"text":"To stop early: ","type":"text"},{"text":"fleet stop","type":"text","marks":[{"type":"code_inline"}]},{"text":" reads the PID file, sends ","type":"text"},{"text":"SIGTERM","type":"text","marks":[{"type":"code_inline"}]},{"text":", waits up to 5s, escalates to ","type":"text"},{"text":"SIGKILL","type":"text","marks":[{"type":"code_inline"}]},{"text":" if needed.","type":"text"}]},{"type":"paragraph","content":[{"text":"If the Claude Code session ends abruptly while the daemon is running, the process is best-effort cleaned up by the OS (POSIX: child receives ","type":"text"},{"text":"SIGHUP","type":"text","marks":[{"type":"code_inline"}]},{"text":"; Windows: depends on harness). On next ","type":"text"},{"text":"fleet start","type":"text","marks":[{"type":"code_inline"}]},{"text":", a stale PID file is auto-detected and cleared.","type":"text"}]},{"type":"paragraph","content":[{"text":"signal.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" deploys to ","type":"text"},{"text":".claude/fleet/signal.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" on ","type":"text"},{"text":"init","type":"text","marks":[{"type":"code_inline"}]},{"text":". Sessions call:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"bash .claude/fleet/signal.sh READY \u003ctest-log>\nbash .claude/fleet/signal.sh CONFLICT \"\u003creason>\"","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Decision tree","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"N == 1 → use git-ops, not this\nN > 1, all on shared local working tree → REFUSE. Use worktrees or separate clones.\nN > 1, worktrees available → fleet init \u003cnames...>\nN > 1, separate clones / remote → use mode=branch, manual git branch + signal.sh","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"First-class user interaction (HARD RULE)","type":"text"}]},{"type":"paragraph","content":[{"text":"When this skill surfaces a decision point, ","type":"text"},{"text":"always use the ","type":"text","marks":[{"type":"strong"}]},{"text":"AskUserQuestion","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" tool","type":"text","marks":[{"type":"strong"}]},{"text":". Plain markdown numbered lists are not acceptable for these branches — they make the skill feel like a wrapped script instead of a native interaction.","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Trigger","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Question","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Options (≤4, ≤10 words each)","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"init","type":"text","marks":[{"type":"code_inline"}]},{"text":" — worktrees available, mode unset","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Worktree or branch-only mode?","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Worktrees / Branches only / Cancel","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Lane → ","type":"text"},{"text":"CONFLICT","type":"text","marks":[{"type":"code_inline"}]},{"text":" (rebase fail)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Lane ","type":"text"},{"text":"\u003cname>","type":"text","marks":[{"type":"code_inline"}]},{"text":" has rebase conflict","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Resolve in lane / Skip & continue / Revert lane / Untrack","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Lane → ","type":"text"},{"text":"FAILED","type":"text","marks":[{"type":"code_inline"}]},{"text":" (post-merge tests red)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Tests broke after ","type":"text"},{"text":"\u003cname>","type":"text","marks":[{"type":"code_inline"}]},{"text":" merged","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Auto-revert / Investigate first / Accept failure","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pre-land scrub hits","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Forbidden patterns in ","type":"text"},{"text":"\u003cname>","type":"text","marks":[{"type":"code_inline"}]},{"text":" diff","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Block landing / Override (note reason) / Open to edit","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"fleet","type":"text","marks":[{"type":"code_inline"}]},{"text":" shows mixed states","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"How to proceed with the fleet?","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Land all READY / Resolve CONFLICTs first / Just status","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Daemon exits with ","type":"text"},{"text":"FAILED","type":"text","marks":[{"type":"code_inline"}]},{"text":" lanes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"\u003cn>","type":"text","marks":[{"type":"code_inline"}]},{"text":" lanes failed — what next?","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Retry all / Revert and report / Leave as-is","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"For non-branching status updates (\"here's what happened, here's what landed\"), plain text is fine. The split matches the global ","type":"text"},{"text":"~/.claude/CLAUDE.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" \"Asking Questions\" rule.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"What it handles vs what it does not","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mode","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Status","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Worktrees on different branches","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"✅ Primary mode","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Branches in separate clones / machines","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"✅","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Mixed worktree + branch lanes","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"✅","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Recovery from dirty ","type":"text"},{"text":"main","type":"text","marks":[{"type":"code_inline"}]}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"✅ Refuses to merge, asks user to clean","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Test-gated landing","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"✅ Via ","type":"text"},{"text":"signal.sh READY \u003clog>","type":"text","marks":[{"type":"code_inline"}]}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Auto-rebase other lanes when one lands","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"✅","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Pre-land regex scrub (forbidden patterns)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"✅","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"One-shot revert","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"✅ ","type":"text"},{"text":"fleet revert \u003cbranch>","type":"text","marks":[{"type":"code_inline"}]}]}]}]}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Out of scope","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Why","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"5+ sessions on one local working tree","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Git limitation. Skill detects and refuses with worktree pointer.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Uncommitted work at signal time","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"signal.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" rejects dirty lanes. Daemon needs an immutable commit.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"External state (DB migrations, services)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Skill can't know lane B depends on lane A's migration. Order manually.","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Force-pushed lanes mid-flight","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Detected at land time, not prevented.","type":"text"}]}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Compatibility","type":"text"}]},{"type":"paragraph","content":[{"text":"Tested and working on:","type":"text"}]},{"type":"table","attrs":{"layout":null},"content":[{"type":"tr","content":[{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"OS","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Shell","type":"text"}]}]},{"type":"th","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Notes","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Linux","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"bash 4+","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Native","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"macOS","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"bash 3.2+ (default) or bash 4+ via brew","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"stat -f","type":"text","marks":[{"type":"code_inline"}]},{"text":" fallback used automatically","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Windows","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Git Bash (mintty)","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Forward-slash paths; Unicode icons render in mintty/Windows Terminal","type":"text"}]}]}]},{"type":"tr","content":[{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Windows","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"PowerShell 7 (calling ","type":"text"},{"text":"bash","type":"text","marks":[{"type":"code_inline"}]},{"text":")","type":"text"}]}]},{"type":"td","attrs":{"colspan":1,"rowspan":1,"colwidth":null,"alignment":""},"content":[{"type":"paragraph","content":[{"text":"Works if ","type":"text"},{"text":"bash","type":"text","marks":[{"type":"code_inline"}]},{"text":" is on PATH","type":"text"}]}]}]}]},{"type":"paragraph","content":[{"text":"Requirements: ","type":"text"},{"text":"bash 3.2+","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"git 2.5+","type":"text","marks":[{"type":"code_inline"}]},{"text":" (worktree support), ","type":"text"},{"text":"awk","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"grep","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"head","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"stat","type":"text","marks":[{"type":"code_inline"}]},{"text":". All standard.","type":"text"}]},{"type":"paragraph","content":[{"text":"If your terminal mojibakes the status icons (⏳ ✅ 🚀 ❌ ⚠️), fall back to ASCII:","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":"bash"},"content":[{"text":"export FLEET_ASCII=1\n# or in .claude/fleet/config:\nicons=ascii","type":"text"}]},{"type":"paragraph","content":[{"text":"Long-path warning (Windows only): worktrees nest under ","type":"text"},{"text":".fleet-worktrees/\u003cname>/","type":"text","marks":[{"type":"code_inline"}]},{"text":". If your repo lives deep in the filesystem, lane names should stay short to avoid Windows' 260-char path limit. Enable ","type":"text"},{"text":"core.longpaths=true","type":"text","marks":[{"type":"code_inline"}]},{"text":" in git if you hit it.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Headless agent compatibility","type":"text"}]},{"type":"paragraph","content":[{"text":"Don't put fleet worktrees under ","type":"text","marks":[{"type":"strong"}]},{"text":".claude/","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":".","type":"text","marks":[{"type":"strong"}]},{"text":" Claude Code applies a global sensitive-file guard to anything under ","type":"text"},{"text":".claude/","type":"text","marks":[{"type":"code_inline"}]},{"text":", and that guard runs ","type":"text"},{"text":"before","type":"text","marks":[{"type":"em"}]},{"text":" — and is not bypassed by — ","type":"text"},{"text":"--dangerously-skip-permissions","type":"text","marks":[{"type":"code_inline"}]},{"text":". Headless lane sessions (","type":"text"},{"text":"claude -p ... --dangerously-skip-permissions","type":"text","marks":[{"type":"code_inline"}]},{"text":") will fail every Write/Edit if their worktree lives at e.g. ","type":"text"},{"text":".claude/fleet/worktrees/\u003clane>","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"paragraph","content":[{"text":"That's why the default ","type":"text"},{"text":"worktree_root","type":"text","marks":[{"type":"code_inline"}]},{"text":" is ","type":"text"},{"text":".fleet-worktrees/","type":"text","marks":[{"type":"code_inline"}]},{"text":" at the repo top, not ","type":"text"},{"text":".claude/fleet/worktrees/","type":"text","marks":[{"type":"code_inline"}]},{"text":". If you override ","type":"text"},{"text":"worktree_root","type":"text","marks":[{"type":"code_inline"}]},{"text":" in config, keep it outside ","type":"text"},{"text":".claude/","type":"text","marks":[{"type":"code_inline"}]},{"text":" for the same reason. Runtime state (","type":"text"},{"text":"lanes/","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"daemon.pid","type":"text","marks":[{"type":"code_inline"}]},{"text":", ","type":"text"},{"text":"activity.log","type":"text","marks":[{"type":"code_inline"}]},{"text":") is read/write from the orchestrator only and stays under ","type":"text"},{"text":".claude/fleet/","type":"text","marks":[{"type":"code_inline"}]},{"text":" — it never needs lane-session writes.","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Configuration","type":"text"}]},{"type":"paragraph","content":[{"text":"Optional ","type":"text"},{"text":".claude/fleet/config","type":"text","marks":[{"type":"code_inline"}]},{"text":" (key=value, no quotes):","type":"text"}]},{"type":"code_block","attrs":{"wrap":false,"language":""},"content":[{"text":"mode=auto # auto | worktree | branch\nworktree_root=.fleet-worktrees # keep outside .claude/ — see \"Headless agent compatibility\"\ntest_cmd= # if set, daemon runs this; else trust signal log\nforbidden_pattern=TODO_SCRUB|XXX\nbase_branch=main\npoll_interval=5","type":"text"}]},{"type":"paragraph","content":[{"text":"Zero-config works for the common case.","type":"text"}]},{"type":"paragraph","content":[{"text":"fleet init","type":"text","marks":[{"type":"code_inline"}]},{"text":" appends ","type":"text"},{"text":".claude/fleet/","type":"text","marks":[{"type":"code_inline"}]},{"text":" and ","type":"text"},{"text":".fleet-worktrees/","type":"text","marks":[{"type":"code_inline"}]},{"text":" to ","type":"text"},{"text":".gitignore","type":"text","marks":[{"type":"code_inline"}]},{"text":" and auto-commits that change with ","type":"text"},{"text":"chore: gitignore fleet-ops runtime state","type":"text","marks":[{"type":"code_inline"}]},{"text":" when the tree is otherwise clean and you're on ","type":"text"},{"text":"BASE_BRANCH","type":"text","marks":[{"type":"code_inline"}]},{"text":". If either condition fails, it prints an ","type":"text"},{"text":"ACTION REQUIRED","type":"text","marks":[{"type":"code_inline"}]},{"text":" message — commit ","type":"text"},{"text":".gitignore","type":"text","marks":[{"type":"code_inline"}]},{"text":" yourself before ","type":"text"},{"text":"fleet start","type":"text","marks":[{"type":"code_inline"}]},{"text":", or the daemon will refuse to land with ","type":"text"},{"text":"uncommitted tracked changes","type":"text","marks":[{"type":"code_inline"}]},{"text":".","type":"text"}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Future work","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"JSONL activity log","type":"text","marks":[{"type":"strong"}]},{"text":" — currently plain text (","type":"text"},{"text":"[HH:MM:SS] event","type":"text","marks":[{"type":"code_inline"}]},{"text":"). Switch to JSONL when a TUI, ","type":"text"},{"text":"--json","type":"text","marks":[{"type":"code_inline"}]},{"text":" output, or ","type":"text"},{"text":"log-ops","type":"text","marks":[{"type":"code_inline"}]},{"text":" integration earns the cost. Migration is mechanical.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"--batch","type":"text","marks":[{"type":"code_inline"},{"type":"strong"}]},{"text":" mode","type":"text","marks":[{"type":"strong"}]},{"text":" — land all READY lanes in one go, test once at end. Add when dogfooding shows demand.","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"Cross-session daemon","type":"text","marks":[{"type":"strong"}]},{"text":" — currently dies with the Claude Code session. For overnight runs, a real detached process (","type":"text"},{"text":"nohup","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"systemd","type":"text","marks":[{"type":"code_inline"}]},{"text":"/","type":"text"},{"text":"tmux","type":"text","marks":[{"type":"code_inline"}]},{"text":") is needed.","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"References","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/session-prompt.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — copy-paste template for each Claude session","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"references/workflow.md","type":"text","marks":[{"type":"code_inline"}]},{"text":" — end-to-end walkthrough plus recovery scenarios","type":"text"}]}]}]},{"type":"heading","attrs":{"level":2},"content":[{"text":"Scripts","type":"text"}]},{"type":"bullet_list","content":[{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"scripts/fleet.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" — main CLI","type":"text"}]}]},{"type":"list_item","content":[{"type":"paragraph","content":[{"text":"scripts/signal.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" — branch-aware signaler (deployed to ","type":"text"},{"text":".claude/fleet/signal.sh","type":"text","marks":[{"type":"code_inline"}]},{"text":" on init)","type":"text"}]}]}]},{"type":"hr","attrs":{"markup":"---"}}]},"metadata":{"date":"2026-06-05","name":"fleet-ops","author":"@skillopedia","source":{"stars":21,"repo_name":"claude-mods","origin_url":"https://github.com/0xdarkmatter/claude-mods/blob/HEAD/skills/fleet-ops/SKILL.md","repo_owner":"0xdarkmatter","body_sha256":"6fa88dc979d3ab31d32427fed1518cd10f3db2f42cd2447ad8e028f9649341af","cluster_key":"f3211f90a96a751d14c52d13a127f92f89ea54cfed2d6bbf5a7f5f624dfdd266","clean_bundle":{"format":"clean-skill-bundle-v1","source":"0xdarkmatter/claude-mods/skills/fleet-ops/SKILL.md","attachments":[{"id":"e2e55d92-234a-5411-b913-caf4e7b7e013","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/e2e55d92-234a-5411-b913-caf4e7b7e013/attachment","path":"assets/.gitkeep","size":0,"sha256":"e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855","contentType":"text/plain; charset=utf-8"},{"id":"84060e8e-6f1d-59d8-af73-12c54d6ef737","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/84060e8e-6f1d-59d8-af73-12c54d6ef737/attachment.md","path":"references/session-prompt.md","size":2172,"sha256":"34cdd167326d71cab97e14e5db1df6caf1c75e710b23f3672eb1a607ef6c1c32","contentType":"text/markdown; charset=utf-8"},{"id":"80efc462-bb9b-5092-9d1e-dbb09c87eae4","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/80efc462-bb9b-5092-9d1e-dbb09c87eae4/attachment.md","path":"references/workflow.md","size":4972,"sha256":"ec5fc6cf2c39f20b0dea10e8e66058a6edbb3c937bc0941e21445869361fb9c4","contentType":"text/markdown; charset=utf-8"},{"id":"bc1c2ddc-dea9-5144-89fa-c9b943d98e16","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/bc1c2ddc-dea9-5144-89fa-c9b943d98e16/attachment.sh","path":"scripts/fleet.sh","size":21402,"sha256":"acb8e21631b91aa24a865ca636b75c8e23904a264d65284823f551aaf807dcd1","contentType":"application/x-sh; charset=utf-8"},{"id":"53f53321-56ce-5ffa-a064-fa3b40e0fd4d","key":"uploads/10433ee7-ad12-4ae0-b34e-97553e46c6c8/53f53321-56ce-5ffa-a064-fa3b40e0fd4d/attachment.sh","path":"scripts/signal.sh","size":2164,"sha256":"2d304ed7762d82bedb9219ecd73cf38520927a950f2026c91680a0966b1f59f1","contentType":"application/x-sh; charset=utf-8"}],"bundle_sha256":"ba4bc920478def4b34ea505f2ae476257a33fbdabccbe24da75c01cdcfd36a93","attachment_count":5,"text_attachments":4,"attachment_storage":"skillopedia-attachments-v1","binary_attachments":1,"excluded_attachments":[]},"cluster_size":1,"skill_md_path":"skills/fleet-ops/SKILL.md","import_metadata":{"date":"2026-06-05","author":"@skillopedia","version":"v1","category":"testing-qa","category_label":"Testing"},"exact_dupes_collapsed_into_this":0},"license":"MIT","version":"v1","category":"testing-qa","metadata":{"author":"claude-mods","status":"experimental","related-skills":"git-ops, push-gate"},"import_tag":"clean-skills-v1","description":"EXPERIMENTAL — manage a fleet of concurrent Claude sessions on parallel branches or worktrees. Landing queue with test gate, fleet status view, pre-land scrub, one-shot revert. Triggers on: multiple Claude sessions, parallel sessions, concurrent agents, 5 sessions, branch queue, landing queue, fleet of sessions, parallel feature work, merge multiple branches, parallel branches.","allowed-tools":"Read Bash Glob Grep AskUserQuestion"}},"renderedAt":1782979864381}

Fleet Ops (experimental) Manage how committed work from isolated lanes lands on . Anything before "committed" or after "landed" is somebody else's problem. Status: experimental. Dogfooding phase. API may change. Not yet in Recent Updates. Core abstraction A lane = one branch (or worktree), one Claude session, one logical unit of work. Lane status: . The skill doesn't care if there are 2 lanes or 20, doesn't care about branch names, doesn't care if you use worktrees or separate clones. CLI surface Daemon lifecycle When Claude invokes via , the daemon: 1. Writes its PID to 2. Traps and removes…