Obsolete on Claude Code 2.1.185+ The spawn mechanics shown below no longer match the latest Claude Code. Since v2.1.178 the team API changed shape: TeamCreate, TeamDelete, and TeamList were removed, a team forms automatically when the first teammate is spawned (gated by CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS), and team_name on the Agent tool is accepted but ignored. See the 2026-06-22 update below for the current model, and treat the spawn recipes that follow as historical.

Update (2026-05-09, verified on Claude Code 2.1.138). Since CC 2.1.133 the binary ships a native worktreeBaseRef setting ('fresh' or 'head') in /config and settings.json. If all you need is for worktrees to be based on the current HEAD, set worktreeBaseRef: "head" and you can skip the WorktreeCreate hook described in Decision 2 below. The hook is still useful for custom worktree paths under .claude/worktrees/, the worktree/<slug> branch name with --no-track, and git worktree lock keyed on agent_id and pid. When the hook is present, the binary delegates entirely to it and ignores the native setting, so the warning in the pre-flight check (“WorktreeCreate hook missing”) is now strictly accurate only when both the hook and worktreeBaseRef are absent.

Update (2026-05-17, verified on Claude Code 2.1.143). Two further changes affect this post.

First, worktree.bgIsolation: "none" (new in 2.1.143) lets a background session edit the working copy directly without EnterWorktree. This is for the lead session itself (for example when you run claude --bg), not for subagents spawned with isolation: "worktree", so the skill’s default of per-teammate worktree isolation is unchanged. To opt in for a specific repo, add "worktree": { "bgIsolation": "none" } to that repo’s .claude/settings.json. The related bug from 2.1.142 (background sessions not recognizing pre-existing git worktrees, which blocked Edit while EnterWorktree refused to create a duplicate) is also fixed.

Second, the claude agents dashboard (added in 2.1.139, polished through 2.1.143) lists every session (running, blocked on you, done) in one view, including team members spawned by nf-agents. From 2.1.142, the view accepts --add-dir, --settings, --mcp-config, --permission-mode, --model, and --effort flags to configure dispatched sessions. The tmux or iTerm2 pane backend is unchanged: each member is still a real shell session, the dashboard just centralizes status, model selection, and permission mode in one place. From 2.1.143, dispatched sessions also honor permissions.defaultMode from settings.json instead of being overridden to auto mode, and claude --bg --dangerously-skip-permissions persists across retire and wake.

Two smaller fixes worth knowing: worktree cleanup no longer falls back to rm -rf when git worktree remove fails (2.1.143), so gitignored or in-progress files inside a worktree are preserved, and empty idle background sessions auto-retire after five minutes (2.1.141) so the dashboard does not accumulate ghost rows.

Update (2026-05-19, verified on Claude Code 2.1.144). Three changes touch this skill’s surface area.

First, /resume now lists background sessions alongside foreground ones with a bg tag, and session notifications include runtime in the form 3h 2m 5s. For nf-agents teams where teammates often run in background, this means you can locate a specific teammate by tag rather than scanning roster.json or state.json by hand. The Stop hook and Telegram bridge notifications inherit the new runtime field automatically.

Second, /model no longer changes the default model for future sessions by default. It now affects only the current session; press d after picking a model to persist it as the default. This matters for nf-agents workflows that spawn many short-lived teammates: setting /model in a parent session no longer accidentally rewrites the default that subsequent teammates inherit.

Third, two reliability fixes worth knowing: dispatched background sessions now keep the model they were spawned with (previously a teammate could pick up a different session’s model due to a race), and a macOS-specific crash in background sessions running inside Full Disk Access-protected folders (Documents, Desktop, iCloud Drive) is fixed. The API startup hang (75s when api.anthropic.com is unreachable) is also cut to 15s, which makes spawning a team of five teammates noticeably less painful on flaky networks.

Update (2026-06-22, verified on Claude Code 2.1.185). This is the big one: the team primitives this skill was built on changed shape in v2.1.178, so parts of the recipe below are now historical. The official write-up is at code.claude.com/docs/en/agent-teams (“as of v2.1.178”), and the facts here are cross-checked against the 2.1.185 binary.

TeamCreate and TeamDelete no longer exist. Grepping the 2.1.185 binary returns zero hits for either tool name. With CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 set, a team now forms automatically when the first teammate is spawned, and the team’s shared directories are cleaned up automatically when the session ends. There is no setup call and no teardown call. The team_name input on the Agent tool is still accepted but ignored; the team name is derived from the session (session- plus the first eight characters of the session id). The team_name field in the TaskCreated, TaskCompleted, and TeammateIdle hook payloads is deprecated and carries that session-derived name. Net effect on this skill: the standby and tasks spawn recipe below (call TeamCreate, then Agent({team_name, ...})) is the pre-2.1.178 shape. The modern equivalent is to spawn teammates directly once the env var is on, and to let session exit do the cleanup that teardown’s TeamDelete used to do.

The feature is gated by CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1. Without it, no team is set up at session start, no team directories are written, and the lead will not spawn or propose teammates. Set it in settings.json under env, or in your shell.

In-process is now the default display mode (v2.1.179). Before v2.1.179 the default was auto, so an upgraded session that used to open split panes now stays in one terminal until teammateMode is set explicitly. The binary accepts three values: in-process, auto, tmux. auto opens split panes only when you are already inside tmux or iTerm2 and falls back to in-process otherwise; tmux forces split-pane mode and auto-detects tmux versus iTerm2. Per session: claude --teammate-mode auto. This softens Decision 1 below: the mode setting now governs whether split panes are attempted at all, while the backend (tmux versus iTerm2) is still auto-detected from the launch environment. In in-process mode the teammates live in an agent panel under the lead’s prompt (up and down to select, Enter to view and message, Escape to interrupt, x to stop, Ctrl+T to toggle the task list); an idle row hides after 30 seconds and returns on the teammate’s next turn (v2.1.181). Split panes are unsupported in VS Code’s integrated terminal, Windows Terminal, and Ghostty, so the pre-flight “no split-pane backend” line now means “falls back to in-process,” not “spawn fails.”

Team and task state live on disk. ~/.claude/teams/{team-name}/config.json holds runtime state (session ids, tmux pane ids) and is removed when the session ends. ~/.claude/tasks/{team-name}/ persists locally under your cleanupPeriodDays, so a resumed session keeps its tasks. The team config’s members array is how a teammate discovers the roster. Do not hand-edit or pre-author either; they are regenerated and overwritten on every state update.

Task claiming is file-locked and dependency-aware. Tasks have three states (pending, in progress, completed). A pending task with unresolved dependencies cannot be claimed until they complete, and dependents unblock on their own. Concurrent self-claims are serialized with file locking. The documented caveat matches what the skill warns about: task status can lag when a teammate forgets to mark a task complete, which blocks its dependents until the lead nudges.

Plan approval for teammates is new. You can require a teammate to work in read-only plan mode until the lead approves its plan; the lead approves or rejects with feedback autonomously, and a rejected teammate revises and resubmits. The 2.1.185 binary confirms the path (“plan approval request(s), auto-approving”). This is the clean way to gate a risky refactor that a tasks-mode teammate would otherwise start immediately.

Teammates no longer inherit the lead’s /model by default. Set Default teammate model in /config, or pick “Default (leader’s model)” to follow the lead. A model outside the allowlist falls back to the default teammate model.

Subagent definitions double as teammate roles. Spawn a teammate using a subagent type (project, user, plugin, or CLI). Its tools allowlist and model are honored and its body is appended to the teammate’s system prompt; SendMessage and the task tools stay available even under a tools restriction. The skills and mcpServers frontmatter fields are not applied to a teammate, which loads those from project and user settings like any session.

Worktree base ref is native now. worktree.baseRef: "head" (the binary accepts fresh or head) bases worktrees on your current HEAD, which is the built-in form of the WorktreeCreate hook in Decision 2; subagent and teammate worktrees follow the same base. This was first noted in the 2026-05-09 update and is restated here because it, not the hook, is the supported path.

Cooperative shutdown survives; cleanup is automatic. SendMessage and the shutdown handshake (approve, or reject with an explanation) are still in the binary. Shutdown is still slow because a teammate finishes its current tool call first, and the zombie risk is smaller now that team directories are removed on session exit. An orphaned tmux session can still linger: tmux ls then tmux kill-session -t <name>. The /exit escape hatch in Decision 4 still works.

Sizing guidance is now documented. The docs codify the range this skill assumes: 3 to 5 teammates, 5 to 6 tasks per teammate; below that, plain subagents are cheaper and simpler.

Claude Code can spawn agents, and those agents can be grouped into a team where each member has its own pane and its own worktree. Once you know it works, the question becomes: how do you avoid forgetting one of the half-dozen flags every time you spawn a team at 2am?

nf-agents is the skill I wrote to answer that. It does not invent any new capability. It collapses the recipe into five named modes, runs a pre-flight check before any spawn, and hard-codes the patterns I kept reaching for.

The problem the skill solves

Without a skill, “spawn a team” is roughly seven decisions you make every time:

  1. Use a single Agent call, or TeamCreate và several Agent calls?
  2. Pass team_name to each agent (or accidentally forget and end up with isolated agents that cannot message each other)?
  3. Pass isolation: "worktree" (or accidentally let an agent edit your live working copy)?
  4. Are you inside tmux? Are you in iTerm2 with it2 installed? Neither? Spawn fails differently in each case.
  5. Is teammateMode set to a split-pane backend, or to in-process (which silently changes everything)?
  6. Is the WorktreeCreate hook present so worktrees are based on your current HEAD and not origin/main?
  7. What are you actually asking each teammate to do? Standby and wait, or execute a brief autonomously?

Get any one of these wrong and you discover it five minutes later: panes that look right but cannot send messages to each other, an agent that mutated your live branch, a worktree that was based on stale main and is now ten commits behind your work.

The skill turns those seven decisions into one: pick a mode, answer the questions the skill prompts you with, done.

The five modes

ModeUse when
standbyYou have several parallel domains in flight but you do not yet know each task. Spawn a team, give each member a role, drive them interactively.
tasksYou know exactly what each member should do. Spawn pre-briefed members that execute autonomously and report back when done.
soloOne task, no parallelism, but you still want a worktree so the agent can commit and push without touching your working copy.
statusInspect the current team. Member names, busy/idle, recent updates.
teardownShut the team down. Sends a cooperative shutdown signal, calls TeamDelete, surfaces any zombies for manual cleanup.

Standby is the default. The model that fits my brain is “spawn the team, then think about what each member should work on first.”

Pre-flight checks (run on every spawn)

Each of these maps to a specific past failure:

# 1. teammateMode must enable split-pane mode (NOT "in-process")
MODE=$(jq -r '.teammateMode // "auto"' ~/.claude/settings.json)
[ "$MODE" = "in-process" ] && echo "FAIL: set teammateMode to 'tmux' or 'auto' first"

# 2. Split-pane backend available - ONE of these must hold:
#    a. inside tmux session     -> tmux split backend
#    b. iTerm2 + it2 CLI >= 3.6.6 -> iTerm2 native split backend
if [ -n "${TMUX:-}" ]; then
  echo "OK: tmux backend"
elif [ "${TERM_PROGRAM:-}" = "iTerm.app" ] && command -v it2 >/dev/null; then
  echo "OK: iTerm2 backend"
else
  echo "FAIL: no split-pane backend"
fi

# 3. WorktreeCreate hook present (so worktrees base on current HEAD)
jq -e '.hooks.WorktreeCreate' ~/.claude/settings.json >/dev/null \
  || echo "WARN: WorktreeCreate hook missing"

# 4. Current cwd is a git repo (required for isolation: "worktree")
git rev-parse --git-dir >/dev/null 2>&1 || echo "FAIL: not in a git repo"

Any failure is surfaced before the spawn is attempted. The skill does not auto-fix the environment, terminal launch context is the user’s responsibility.

A first run, end to end

You type:

/nf-agents standby

The skill asks (one batch, four questions):

  1. Team name. Suggested kebab-case (worker-team).
  2. Members. Names và a one-line role each (api-worker: handles incoming webhooks, feed-worker: handles RSS ingestion).
  3. Target branch. Defaults to your current branch.
  4. Backend. iTerm2 split (default, lighter) or tmux session (persists if you close the terminal).

It then runs the pre-flight, calls TeamCreate, and for each member spawns:

Agent({
  name: "<member-name>",
  team_name: "<team-name>",
  subagent_type: "fullstack-engineer",
  isolation: "worktree",
  prompt: "<standby brief>"
})

Note (2026-06-22): on Claude Code 2.1.178 and later this exact recipe is historical. TeamCreate is gone and the team_name input is accepted but ignored. With CLAUDE_CODE_EXPERIMENTAL_AGENT_TEAMS=1 set, you spawn the teammate directly and the team forms on its own. See the 2026-06-22 update at the top of this post.

The standby brief is templated. It tells the member: do not start any code work, wait for the user to type into your pane or for SendMessage from the team lead, when you finish a task call TaskUpdate(status: "completed") and SendMessage the lead a one-paragraph summary, open PRs against the target branch, never auto-merge, never push directly to the target branch.

Acknowledge in two lines, then idle. The lead returns to idle. You start driving members.

The rest of this post walks through the four design decisions inside the skill that took the longest to get right and explains the incidents that drove each one.

Decision 1: Backend choice (tmux vs iTerm2 split)

When Claude Code spawns a team, every member needs a pane. The pane has to live somewhere, and where that “somewhere” is depends on the environment when you launched claude, not on a setting you can flip mid-session.

The decision tree is small but easy to misread:

Environment when claude was launchedBackend chosen
TMUX env var is set (claude was launched inside tmux)tmux split-window in the same tmux session
TERM_PROGRAM=iTerm.app, it2 CLI present, TMUX unsetiTerm2 native split (separate panes in iTerm2)
NeitherSpawn fails, or falls through to in-process if teammateMode: auto

The setting teammateMode: tmux is misleading: it does not force tmux. It means “any split-pane backend, do not fall back to in-process.” The binary auto-detects which split backend to use.

The persistence question is the real one

The reason backend choice matters is not aesthetics, it is whether the team survives a closed terminal:

BackendClose terminal, what survives
iTerm2 splitAll claude PTYs are killed. Team teardown cascades. Individual transcripts persist on disk under ~/.claude/projects/.../<session-id>.jsonl and you can claude --resume <id> to pick up a single session, but the team registry and the SendMessage graph between members are gone.
tmux sessiontmux server keeps running detached. All claude processes alive. tmux attach -t <name> reconnects exactly where you left off. Survives terminal close, even SSH disconnect.

The skill asks about this before spawning:

Backend: do you want iTerm2 split (default, lighter, no persistence) or tmux session (persist, attach from another machine or mobile)?

If you pick tmux but claude was not launched inside tmux, the skill aborts and tells you to tmux new -s <name> first, then re-run claude inside it. It does not auto-fix the environment because the launch context is the user’s responsibility, and silently re-launching claude would orphan the current conversation.

When I pick which

  • Daily work, single machine, a few hours: iTerm2 split. Lighter, and I do not need to attach from elsewhere.
  • I might close the lid and come back tomorrow, or I want to peek at the team from my phone over SSH: tmux.
  • Long-running team (more than a day): tmux, no question.
  • One parallel task, do not need to watch live output: standalone Agent, no team, the backend question does not apply.

Decision 2: Worktree base branch (and why a hook had to override the default)

isolation: "worktree" tells Claude Code to create a temporary git worktree for each agent. The agent commits, pushes, and PRs from inside the worktree, leaving your live working copy untouched.

The thing that took an incident to learn: by default, Claude Code bases the agent’s worktree on origin/<default-branch> (typically origin/main), not on your current HEAD.

That sounds reasonable until you actually use it. Imagine the workflow:

  1. You are on a feature branch with eight commits of in-progress work.
  2. You want an agent to add a small refactor to that feature.
  3. You spawn the agent with isolation: "worktree".
  4. The worktree is created from origin/main, not from your current branch.
  5. The agent does the refactor, commits, pushes, opens a PR.
  6. You merge the PR back into your feature branch.
  7. The merge brings in the refactor, và a complete reversion of your eight in-progress commits, because the worktree never knew they existed.

The fix is a WorktreeCreate hook in ~/.claude/settings.json that intercepts the worktree creation command and rewrites it to base on the parent session’s current HEAD instead. Roughly:

git worktree add --no-track -b worktree/<slug> <path> HEAD

The --no-track is deliberate: the worktree branch should not silently track any upstream. The first push from the agent must use git push -u origin worktree/<slug> to set its upstream explicitly, after which subsequent pushes are plain git push.

The skill checks for the hook on every spawn:

jq -e '.hooks.WorktreeCreate' ~/.claude/settings.json >/dev/null \
  || echo "WARN: WorktreeCreate hook missing - worktrees will base on origin/<default-branch>"

If the hook is missing, the skill warns but does not block. (Some users genuinely want the default behavior, particularly if they rebase their work onto main constantly.) For my workflow the hook is required, so the warning is also a reminder to re-verify after every Claude Code version upgrade, since the binary occasionally changes its hook resolution.

The agent’s commit and PR contract

Once the worktree is on the right branch, the agent’s contract is small:

  1. Auto-commit and auto-push are allowed, follow the team’s commit format.
  2. First push uses -u origin worktree/<slug>. Subsequent pushes are plain.
  3. PRs go back to the parent branch the worktree was based on, not to main.
  4. Never auto-merge. Never push directly to the parent branch.
  5. After PR is open, the agent’s job is done. The user reviews and merges.

The skill bakes all of this into the standby and tasks prompt templates, so every spawned member starts with this contract loaded.

Decision 3: Standby vs tasks (when to use which)

Both modes spawn a team. The difference is who is driving:

Standby is for “I have several domains and I will figure out each task as I go.” Members get a role, an idle protocol, and wait. You then either:

  • Type directly into a member’s pane (works for tmux and iTerm2 splits, the model behind that pane is a real Claude session you can talk to).
  • Send a SendMessage from the lead pane: SendMessage({to: "api-worker", message: "Write a unit test for the webhook signature validator..."}).

Tasks is for “I know exactly what each member should do.” The brief is in the spawn prompt. The agent reads it, executes it, calls TaskUpdate(status: "completed")SendMessage(to: "team-lead", message: "<summary + PR url>") when done.

The split matters because the brief is shaped differently. Standby briefs are about identity and protocol (“you are api-worker, your role is incoming webhooks, wait for direction”). Task briefs are about scope and acceptance (“read these files, change this behavior, here are the acceptance criteria, here is the reporting protocol”).

Vague task briefs are the most common failure I see. The agent ends up asking the user clarifying questions mid-execution, which defeats the point of autonomy. A good task brief includes:

  • Scope: one paragraph, “what to change.”
  • Acceptance criteria: bullet list, “the change is done when X, Y, Z.”
  • Pointers: file paths, doc links, memory file names the agent should read first.
  • Reporting protocol: TaskUpdateSendMessage on done, PR target branch.

If you would not give the brief to a junior engineer expecting them to deliver without asking, do not give it to an agent.

Decision 4: The cooperative shutdown protocol

When you are done with a team you call teardown. The skill’s job is to:

  1. Send SendMessage({type: "shutdown_request", reason: "..."}) to each member, once.
  2. Wait roughly ten seconds.
  3. Call TeamDelete({team_name}) to free the registry name.
  4. Surface any zombies and tell the user how to clean them up by hand.

The key word is cooperative. A teammate has to actively call SendMessage({type: "shutdown_response", approve: true}) to terminate. If it only writes “Acknowledged, shutting down” as plain text without calling the tool, its process keeps running. The pane is a zombie: the worktree the team registry pointed it at has been deleted by TeamDelete, so the zombie cannot do useful work, but the OS process is still resident.

This is the part of the skill that has the most stern wording, because it is the part most likely to leave you with leaked processes:

  • Do not retry the same shutdown_request. A misunderstanding ten seconds ago will be the same misunderstanding ten seconds from now.
  • Tell the user explicitly which member did not approve. Naming the zombie makes it actionable.
  • Suggest manual cleanup in this order: click the iTerm2 pane and Cmd+W, focus the tmux pane and Ctrl-B x, or ps aux | grep claude and kill the PID with kill (then kill -9 only if SIGTERM is ignored).
  • Do not try to auto-kill. The lead does not have the teammate’s PID and does not manage iTerm2 or tmux directly. Pretending it can leads to “I killed something but I am not sure what.”

The escape hatch

If multiple zombies refuse to approve and Cmd+W-ing each pane is tedious, there is a one-action nuclear option: type /exit in the lead pane.

Claude Code shows a dialog, “Background work is running, Exit anyway / Stay.” Pick Exit anyway. The lead terminates, a cascade signal kills every member, every pane closes, and in iTerm2 the entire window or tab closes too.

The cost is that the lead session is gone. The transcript persists at ~/.claude/projects/.../<session-id>.jsonl and you can claude --continue later, but the live conversation context is lost.

I recommend the escape hatch only when there is no further valuable context in the lead session anyway. If the lead has been planning something complex, do not nuke it for the sake of zombie cleanup, do the manual Cmd+W walk.

Mistakes the skill exists to prevent

These are the patterns that cost me time before the skill existed. The skill encodes a check or a default for each:

  • Spawning Agent without team_name when you wanted a team. The result looks like an isolated background agent: it cannot SendMessage to anyone. The skill always asks “team or solo” up front so this is impossible.
  • Manual tmux split-windowclaude --continue in another pane. The panes look like a team, but they have no shared messaging registry, they are just independent sessions. The skill always uses Agent + team_name so the registry exists.
  • Forgetting isolation: "worktree" for code-editing teammates. Their work mutates your live working copy directly. The skill defaults isolation: "worktree" for any agent that will write files.
  • Vague task briefs. Covered above. The skill’s templates include the structure I have learned to write.
  • Auto-merging the agent’s branch. Never do this. The skill’s prompt template is explicit: PR only, never auto-merge.

What you should keep even if you never use my skill

Four patterns generalize cleanly:

  1. Pre-flight checks before any irreversible action. Spawning a team that lacks split-pane backend ends with you killing eight processes by hand. Four bash lines at the top of the skill removes the entire class of failure.
  2. Named modes instead of free-form arguments. “Spawn a team for me” is ambiguous. standby, tasks, solo are not. The skill’s user (you, six hours later) does not have to remember which arguments do what.
  3. A WorktreeCreate hook that bases agent worktrees on your current HEAD. Re-verify after every Claude Code upgrade.
  4. A reporting protocol baked into every spawn prompt. TaskUpdateSendMessage on completion, PR not auto-merge, never push to parent branch directly.

The skill itself is just one way to encode those, and the encoding is the cheap part. The patterns are the value.

Get the skill

White-labeled, ready to drop into ~/.claude/skills/. Published in the claude-skills-toolkit repo.

# Option 1: download the latest release zip
curl -L https://github.com/llawliet11/claude-skills-toolkit/releases/latest/download/nf-agents.zip -o nf-agents.zip
unzip nf-agents.zip -d ~/.claude/skills/

# Option 2: clone the repo
git clone https://github.com/llawliet11/claude-skills-toolkit.git
cp -r claude-skills-toolkit/nf-agents ~/.claude/skills/

Verify in a new Claude Code session: type /nf-agents and the skill should appear in the slash-command list. The skill’s own README.md covers required environment, the WorktreeCreate hook, and customization.

If you adopt it and find a failure mode I have not yet hit, open an issue on the repo or let me know.