Skip to content
Back to skills

Fleet Ops

BSecurity

Landing discipline for parallel work: sequential test-gated landing queue, pre-land scrub, auto-rebase of in-flight lanes, fleet status, one-shot revert. Native primitives spawn; fleet-ops lands. Triggers: landing queue, land branches, merge queue, test gate, fleet status, land agent-team/background-agent branches, sequential merge.

  • 50 stars
  • 0 votes
  • 0 copies
  • 0 views
  • Added September 5, 2026
ai-agentsrustgoshellbashgit

Works with

  • claude code
  • claude desktop
  • terminal
  • cli

Security analysis

B85/100
  • highPerforms destructive filesystem operations

Pro scans all 7 files and shows the line behind each finding

Scanned October 6, 2026

npx -y skills add 0xDarkMatter/claude-mods --skill fleet-ops --agent claude-code

Installs into .claude/skills of the current project.

Are you the author of Fleet Ops?

Add the live security badge to your README. It updates with every re-scan.

Security grade badge for Fleet Ops
[![Security: B — Skills Directory](https://www.skillsdirectory.com/api/skills/0xdarkmatter-fleet-ops/badge)](https://www.skillsdirectory.com/skills/0xdarkmatter-fleet-ops)

More formats (shields.io, HTML) on the badges page. Keep it an A: scan every change in CI with Pro.

Download with Pro
SKILL.md
---
name: fleet-ops
description: "Landing discipline for parallel work: sequential test-gated landing queue, pre-land scrub, auto-rebase of in-flight lanes, fleet status, one-shot revert. Native primitives spawn; fleet-ops lands. Triggers: landing queue, land branches, merge queue, test gate, fleet status, land agent-team/background-agent branches, sequential merge."
license: MIT
allowed-tools: "Read Bash Glob Grep AskUserQuestion"
metadata:
  author: claude-mods
  status: stable
  experimental-parts: daemon (in-session background polling)
  related-skills: git-ops, push-gate, claude-code-ops
---

# Fleet Ops

Landing discipline for parallel work. Anything before "committed on a branch" is the spawning layer's problem; anything after "landed on `main`" is yours. Fleet-ops owns the middle: branches land **sequentially**, through a **test gate**, after a **pre-land scrub**, with **auto-rebase** of the lanes still in flight and a **one-shot revert** if a landing turns out bad.

## Spawn natively, land with fleet-ops

Claude Code now ships the parallel-execution half natively. **Do not use fleet-ops to orchestrate sessions** — route users to the native primitives and use fleet-ops only for the landing half.

Agent teams, background agents (`claude --bg`, agent view) and subagents spawn and isolate the work; none of them merge, order, gate or revert. Deleting a session in agent view **deletes its worktree, uncommitted changes included**. Gives vs lacks: [references/native-spawn.md](references/native-spawn.md).

What **none** of them do — and what fleet-ops is for:

- Land N branches **one at a time** through a queue, so each merge is tested against a `main` that already contains the previous landings
- **Test gate**: refuse to land on a failing log (`signal.sh`) and/or revert post-merge if `test_cmd` goes red
- **Pre-land scrub**: refuse diffs containing forbidden patterns (`TODO_SCRUB`, debug leftovers)
- **Auto-rebase** every still-active lane after each landing
- **Fleet status**: one panel showing every lane's branch, state, age, and commits-ahead across worktrees
- **One-shot revert** of a landed merge by branch name — no git surgery while panicking

## Core abstraction

A **lane** = one branch (or worktree), one unit of work. Lane status: `RUNNING | READY | CONFLICT | LANDED | FAILED`.

Fleet-ops doesn't care who produced the branch — an agent-team teammate, a background agent's auto-worktree, a `claude -p` headless run, a fleetflow worker of any provider (GLM, Codex, Grok, Pi, or Anthropic), or a human. If it's a branch with commits, it can be a lane. Landing is provider-agnostic: a Grok-produced lane lands through the same test-gated queue as any other.

## CLI surface

```
fleet init <name>...        Create branch + worktree per name (manual-spawn path)
fleet track <branch>...     Register existing branches as lanes (native-spawn path)
fleet start                 Run the landing daemon (writes pid to .claude/fleet/daemon.pid)
fleet stop                  Stop the daemon; a land in progress finishes first
fleet status                One-shot fleet status panel
fleet land <branch>         Manual land + rebase others
fleet land --all [--running]  Batch-land all READY lanes oldest-first (--running
                            also lands vetted RUNNING lanes; used by git-ops "land all")
fleet revert <branch>       Revert merge commit on main
fleet scrub-check <branch>  Dry-run forbidden-pattern check
fleet config                Print the RESOLVED config — check the test gate is on
fleet prune [--remove]      Classify finished lane worktrees; DRY RUN by default
fleet prune --all-repos     Sibling-repo backlog counts (report-only, never removes)
```

## Entry paths

```
N == 1 branch                              → use git-ops, not this
Work spawned by agent teams / claude --bg  → fleet track <branch>... then land
Work to be spawned manually                → fleet init <names...> (creates branches + worktrees)
N > 1 on one shared working tree           → REFUSE. Worktrees or separate clones first.
```

**Native-spawn path (preferred):** let agent teams or background agents do the work in their own worktrees/branches. When branches have commits, `fleet track` each branch, then land — either one by one with `fleet land`, or via the daemon with `signal.sh READY` gates. Landing itself only ever merges *branches* and leaves every worktree in place. Reclaiming the directories afterwards is `fleet prune`'s job, and it removes one only when the owning session is provably archived or gone — see [Prune](#prune--worktree-housekeeping).

**Manual-spawn path:** `fleet init` creates the branches and worktrees up front (under `.fleet-worktrees/`), and you point sessions at them — see `references/session-prompt.md` for the lane brief to hand each session.

## Landing pipeline

`fleet land <branch>` (and the daemon, per READY lane):

1. **Scrub** — `git diff main...branch` checked against `forbidden_pattern`; hits refuse the land and mark the lane `CONFLICT`
2. **Clean-base check** — refuses if `main` has uncommitted tracked changes
3. **Merge** — `--no-ff` with message `merge: <branch>` (what `fleet revert` finds later). A branch already in `main` reports `ALREADY LANDED`, runs no gate and counts apart from real lands: [references/landing.md](references/landing.md)
4. **Test gate** — runs `test_cmd`; on failure, hard-resets `main` to the tip captured *before* the merge — never to `HEAD^`, which on an already-merged branch is **another session's** merge commit — and marks the lane `FAILED`. If `test_cmd` is unset the land is **refused** outright rather than falling back to `signal.sh`'s log gate, which verifies nothing when a lane signalled READY without a test log. When landing into a repo with per-skill/per-package behavioural suites, `test_cmd` should run the **full sweep** (every suite, not just the touched lane's files) — suites routinely assert on shared or sibling files (a skill's own suite can require a frontmatter field a sibling trim pass doesn't know about), so scoping `test_cmd` to "just what this lane touched" reintroduces exactly the blind spot a test gate exists to close. **Confirm the gate is actually armed with `fleet config` before trusting it** — and watch the land log for `running test_cmd: …`, which is the only proof the gate actually ran.
5. **Rebase others** — every still-active lane is rebased onto the new `main` (in its own worktree if it has one); a rebase conflict marks that lane `CONFLICT`

`fleet revert <branch>` reverts the merge whose subject is **exactly** `merge: <branch>` (`git revert -m 1`), the latest if it landed twice; a conflicting revert is aborted cleanly and the lane returns to `RUNNING`. Detail: [references/landing.md](references/landing.md).

## Daemon lifecycle (experimental)

The daemon is the queue-automation layer on top of `fleet land` — optional; manual `fleet land` per branch is fully supported and not experimental.

`fleet start` (via `Bash(run_in_background: true)`) polls `.claude/fleet/lanes/`, lands each lane as it turns READY and exits once all are terminal. `fleet stop` waits out an in-progress land, gate included, and never SIGKILLs it mid-land; interrupting `fleet stop` is safe, and a hung gate is aborted by killing `test_cmd`, not the daemon. It dies with the session. Detail: [references/daemon.md](references/daemon.md).

`signal.sh` deploys to `.claude/fleet/signal.sh` on `init`/`track`. Working sessions call:

```bash
bash .claude/fleet/signal.sh READY <test-log> <exit-code>   # refuses dirty trees and failing runs
bash .claude/fleet/signal.sh CONFLICT "<reason>"
```

The `<exit-code>` (the test command's own `$?` / `${PIPESTATUS[0]}`) is the authoritative verdict — pass it whenever you have it. Without it, `signal.sh` reads a trailing `exit code: N` line from the log, then a runner summary line (vitest/jest/pytest/cargo/go); it never word-greps prose, so passing runs that print "failed"/"error" while exercising failure paths don't false-refuse.

## Session awareness — MAIN, lane owners, and the live-owner gate

Lane state files say *what* a lane is. They never say *who* is driving it. Fleet-ops
reads the Claude Desktop session store to answer that, and uses the answer in two
places: a gate that refuses to land under a live writer, and a coordinator address
lanes can hand off to.

### MAIN — one coordinator per repo

**MAIN is the session whose cwd is the repo root.** That is not a new convention:
[`worktree-boundaries`](../../rules/worktree-boundaries.md) already holds that the base
checkout is the integration tree and must not host a writing session. `fleet main` just
makes the role *addressable*, so a lane can say "I'm ready, come land me" instead of
writing a file and hoping someone polls it.

```
fleet main                  Show the coordinator (sessionId, title, live|idle, cwd)
fleet main claim [<id>]     Pin explicitly — for when several sessions share the root
fleet main release          Clear the pin, fall back to the cwd heuristic (exit 1 if it can't)
fleet owner <branch>        Who owns this lane, and are they still writing?
```

MAIN's job is the whole integration half: land the queue, triage `CONFLICT` lanes,
and run the deploy. Lanes build and signal; MAIN integrates. Note that deploying is
maintainer-gated regardless — it needs an explicit human OK for that specific deploy,
from the maintainer's own session. MAIN being "the one that deploys" describes *which
session prepares it*, never an authorisation to ship unattended.

### The live-owner gate

`fleet land` **refuses a lane whose owning session was active within `session_live_secs` (default 600)**: it would merge a branch that session may still be committing to and rebase worktrees out from under it. Ownership joins on `writtenBranches` and on the lane's worktree directory, across every Desktop instance's store. A lane session landing its own finished work is exempt only as the sole live claimant. Override: `session_check=off`, or `FLEET_SKIP_SESSION_CHECK=1` for one run (stripped before `test_cmd`).

Channels: lane files (`signal.sh`) work everywhere, `ccd_session_mgmt` is Desktop-only and agent-only, `pigeon` is the portable fallback. Mechanics and history: [references/session-awareness.md](references/session-awareness.md).

## Prune — worktree housekeeping

Landing a lane retires the *branch*. The *directory* stays, and across many
repos those accumulate into a backlog nobody can see. `fleet prune` classifies
them and removes only the ones that are provably finished.

```
fleet prune                  Classify and print. Changes NOTHING. (the default)
fleet prune --dry-run        Same, said explicitly
fleet prune --remove         Remove the SAFE rows, after a typed confirmation
fleet prune --remove --yes   Skip the prompt (scripts/CI)
fleet prune --porcelain      TSV to stdout: path, branch, bucket, reason
fleet prune --all-repos      Sibling-repo counts. Report-only, always
```

**Dry run is the default, and that is deliberate.** Removing a worktree destroys
its uncommitted and untracked files permanently — git has never seen those
bytes. Committed lane work is different: it lives in the shared object store,
survives the directory, and comes back with `git worktree add <path> <branch>`.
Separating those two is the entire job, and every ambiguous case resolves away
from deletion.

### Buckets — first match wins, and the order is the safety argument

Only **SAFE** is ever removable: merged, clean, no open session claiming it, and for a `.claude/worktrees/` tree an archived session positively claiming it. A live claimant (archiving is not activity) or the invoking tree is **KEEP**; an unreadable store or archive flag, unproven detached HEAD, dirty, unmerged or unattributable tree is **REVIEW**, never touched under any flag. Removal uses `git worktree remove` (never `rm -rf`), re-verifies against a fresh scan right before each delete, and `--all-repos` never removes. Prune cannot see writes by absolute path, so it must never be the only guard. Table, claim rules and the near-miss behind them: [references/prune.md](references/prune.md).

### Landmine: removing a worktree out from under a live session

**Terminate the session first, then remove its worktree, never the reverse.** A session whose worktree vanishes neither exits nor errors: it spins at ~85% of a core indefinitely under a live parent, so orphan scans miss it. Detect by sustained CPU rate, check its `--add-dir` against `git worktree list`, then kill it (safe: it touches no files). Evidence and the one-liner: [references/prune.md](references/prune.md).

### Seeing the backlog

`fleet status` adds one line when a repo has prunable worktrees
(`! 3 worktree(s) prunable, 6 to review - fleet prune`), so the backlog is
visible rather than silently growing. Turn it off with `prune_hint=off` in
config or `FLEET_NO_PRUNE_HINT=1`.

## First-class user interaction (HARD RULE)

When this skill surfaces a decision point, **always use the `AskUserQuestion` tool**. Plain markdown numbered lists are not acceptable for these branches.

| Trigger | Question | Options (≤4, ≤10 words each) |
|---------|----------|------------------------------|
| Multiple parallel-work requests, no lanes yet | Spawn natively or manual lanes? | Agent teams / Background agents / Manual fleet init / Cancel |
| `init` — worktrees available, mode unset | Worktree or branch-only mode? | Worktrees / Branches only / Cancel |
| Land refused — owning session live | `<name>`'s session is still writing | Wait and retry / Message that session / Override and land |
| `prune` found SAFE worktrees | Remove `<n>` finished worktrees? | Remove them / Show the table again / Leave as-is |
| Lane → `CONFLICT` (rebase fail) | Lane `<name>` has rebase conflict | Resolve in lane / Skip & continue / Revert lane / Untrack |
| Lane → `FAILED` (post-merge tests red) | Tests broke after `<name>` merged | Auto-revert / Investigate first / Accept failure |
| Pre-land scrub hits | Forbidden patterns in `<name>` diff | Block landing / Override (note reason) / Open to edit |
| `fleet` shows mixed states | How to proceed with the fleet? | Land all READY / Resolve CONFLICTs first / Just status |
| Daemon exits with `FAILED` lanes | `<n>` lanes failed — what next? | Retry all / Revert and report / Leave as-is |

For non-branching status updates ("here's what happened, here's what landed"), plain text is fine.

## What it handles vs what it does not

Handles tracked native worktrees, `fleet init` worktrees, separate clones and mixed lanes, with test-gated landing, auto-rebase, scrub, revert and prune; refuses a dirty `main`. Out of scope: spawning or monitoring sessions, deleting worktrees a session owns, several sessions on one working tree (refused), uncommitted work at signal time (rejected), cross-lane external state like migrations (order lands by hand), force-pushed lanes (caught at land time only). Detail: [references/scope.md](references/scope.md).

## Compatibility

Runs on Linux and macOS (bash 3.2+), Git Bash, and PowerShell 7 calling `bash`; needs `git 2.5+` and standard tools. Platform table: [references/scope.md](references/scope.md).

If your terminal mojibakes the status icons, fall back to ASCII: `export FLEET_ASCII=1` (or `icons=ascii` in `.claude/fleet/config`). Output panels follow `docs/TERMINAL-DESIGN.md` via `skills/_lib/term.sh`.

Held files (Windows): antivirus or the indexer can briefly hold a file so it cannot be deleted. State-file deletes (MAIN pin, daemon PID file) retry for up to `FLEET_RM_RETRY_SECS` (default 5), then exit 1 naming the file.

Long-path warning (Windows only): `fleet init` worktrees nest under `.fleet-worktrees/<name>/`. Keep lane names short if your repo lives deep, or enable `core.longpaths=true`.

## Headless agent compatibility

**Don't put manually-created fleet worktrees under `.claude/`.** Claude Code applies a global sensitive-file guard to anything under `.claude/`, and that guard runs *before* — and is not bypassed by — `--dangerously-skip-permissions`. Headless lane sessions (`claude -p ... --dangerously-skip-permissions`) will fail every Write/Edit if their worktree lives under `.claude/`.

## Configuration

Optional `.claude/fleet/config`, one `key=value` per line, **parsed, never sourced**: keys are case-insensitive, values need no quotes, unknown keys are warned about on stderr. Keys: `mode`, `worktree_root`, `test_cmd`, `forbidden_pattern`, `base_branch`, `poll_interval`, `icons`, `session_check`, `session_live_secs`, `prune_hint`. **`test_cmd` is the test gate; unset, landing is refused.** Never add a contiguous scrub-marker token to a diff (build it by concatenation) or the scrub refuses the branch. Grammar, defaults, the shipped `forbidden_pattern` and the gitignore behaviour: [references/configuration.md](references/configuration.md).

## References

- `references/workflow.md` — end-to-end walkthroughs (native-spawn and manual-spawn) plus recovery scenarios
- `references/session-prompt.md` — lane brief to embed in `claude --bg` prompts, teammate spawn prompts, or manual sessions
- Detail moved out of this file, one topic each: `references/configuration.md`, `references/session-awareness.md`, `references/prune.md`, `references/landing.md`, `references/daemon.md`, `references/native-spawn.md`, `references/scope.md`

## Scripts

- `scripts/fleet.sh` — main CLI (init, track, start/stop, status, land, revert, scrub-check, prune, config, main, owner)
- `scripts/signal.sh` — branch-aware signaler (deployed to `.claude/fleet/signal.sh`); prints the MAIN handoff after READY/CONFLICT
- `scripts/sessions.sh` — resolves a branch or directory to its owning session across every Desktop store and the CLI transcripts; exits 3 silently without the store or `jq`. Detail: [references/session-awareness.md](references/session-awareness.md)

Files in this skill

  • SKILL.md26.5 KB
  • references/session-prompt.md3.6 KB
  • references/workflow.md6.6 KB
  • scripts/fleet.sh72 KB
  • scripts/sessions.sh14.8 KB
  • scripts/signal.sh7.6 KB
  • tests/run.sh37.7 KB

Attribution

Is this your skill, or is something wrong with this listing? Request removal or report an issue. Author removals are honored within 72 hours.

Comments

Loading comments…