Skip to main content

Worktrees

Creating, managing, and understanding worktrees in Daintree.

Updated
Reviewed

Worktrees

Daintree uses Git worktrees to run multiple agents on the same repo in parallel. Each worktree gets its own branch and working directory, so agents can make changes without stepping on each other.

Creating a Worktree

Open the New Worktree dialog from the + button in the sidebar, or right-click any worktree card and select New Worktree. The dialog supports three creation modes: creating a new branch, using an existing local branch, or checking out a PR branch from GitHub.

New Branch (Default)

This is the default mode. A New Branch / Existing Branch toggle appears at the top of the dialog (hidden when opening from a PR). In New Branch mode, you fill in the following fields:

  • Link Issue (optional) — a searchable issue selector. Picking an issue auto-generates the branch name in {type}/issue-{number}-{slug} format, with the branch type prefix detected from the issue's labels or title keywords.
  • Base Branch — a searchable dropdown with fuzzy matching. Branches are grouped into "Recent" and others. Branches already checked out in another worktree show an in use badge; clicking one navigates to that worktree instead of selecting it. Defaults to the current branch on dialog open, or main/master if none is current.
  • New Branch Name — a freeform input field. As you type, an autocomplete popover suggests known branch type prefixes: feature/, bugfix/, chore/, docs/, refactor/, test/, release/, ci/, deps/, perf/, style/, and wip/. Common aliases are normalised automatically (feat becomes feature, fix or hotfix becomes bugfix). If the branch name already exists, Daintree auto-increments it with a numeric suffix (e.g. feature/login-1) and shows a green hint.
  • Worktree Path — auto-generated from the branch name using your configured path pattern. You can edit it manually or use the folder picker button. If the path already exists on disk, it is auto-incremented in the same way.
  • Create from remote branch — a checkbox that tells Daintree to base the new worktree off the remote tracking branch. Auto-selected when the chosen base branch is remote.
  • Assign to me — appears when a GitHub issue is linked and a GitHub account is configured. Assigns the issue to you on creation. This preference is remembered across sessions.
  • Run Recipe (optional) — a dropdown for selecting a recipe to run after the worktree is created. Shows your project's default recipe if one is configured.
Tip
Branch names cannot start with . or -, cannot end with a space or ., and cannot contain \, :, or ... The prefix component is validated separately from the rest of the name.

Existing Branch

Click the Existing Branch tab in the mode toggle to switch to this mode. Use it when you want to create a worktree for a branch that already exists locally, such as resuming interrupted work or picking up a branch created outside Daintree.

The dialog simplifies: the base branch picker and branch name input are replaced by a single Select Branch dropdown. This dropdown lists local branches that are not already checked out in another worktree. Remote-only branches and branches already in use are excluded. A search box at the top of the dropdown lets you filter by name.

Once you select a branch, the worktree path is generated automatically (with the same auto-increment logic if the path already exists). The Create button stays disabled until you make a selection. On submit, Daintree uses the existing branch as-is rather than creating a new one.

Tip
If a branch you expect isn't showing up, it is either already checked out in another worktree or only exists as a remote branch. For remote branches, switch to New Branch mode and select the remote branch as the base.

Switching between New Branch and Existing Branch mode resets the branch selection, search query, and any validation errors.

PR Checkout

You can create a worktree directly from a pull request using the Create Worktree button in the GitHub Integration panel's PR list. This opens the dialog in PR Checkout mode: the title changes to Checkout PR Branch, the mode toggle is hidden, and a banner at the top shows the PR number and title.

On open, Daintree automatically resolves the PR's branch through the following sequence:

  1. Checks whether the branch exists as a remote tracking branch (origin/{headRefName}). If found, selects it.
  2. Checks whether the branch exists locally. If found, selects it.
  3. If neither exists, runs git fetch origin pull/{number}/head:{headRefName} to fetch the branch from GitHub.
  4. After the fetch completes, re-checks for the local branch and selects it if available.
Note
The auto-fetch is fully automatic. No manual git commands are needed. Fork PRs are not supported by the checkout flow — the Create Worktree option is disabled for fork PRs in the GitHub panel.

If the branch still cannot be resolved after fetching, the Create button is disabled and an amber warning banner appears:

You will need to fetch the branch manually and reopen the dialog before you can create the worktree.

Branch Prefix

Daintree can automatically prepend a namespace prefix to new branch names. Configure this in Project Settings > Worktree Setup > Branch Prefix. Three options are available:

  • None (default) — no prefix is added.
  • Username — reads your git config user.name, converts it to a lowercase slug (e.g. alice), and prepends it as alice/.
  • Custom — lets you enter any string you like (e.g. myteam/ or feature/).

A live preview shows what a branch name will look like with the prefix applied. The configured prefix is pre-populated into the branch name input when the dialog opens. If an issue is linked, the prefix is prepended to the auto-generated branch name from the issue.

This namespace prefix is separate from the branch type prefix (feature/, bugfix/, etc.) that comes from issue labels or the autocomplete popover. The two stack together, so with a username prefix of alice/ and a type prefix of feature/, the resulting branch name would be alice/feature/issue-42-add-login.

This is a per-project setting. There is no global branch prefix option.

Error States

If something goes wrong during worktree creation, Daintree shows a banner in the dialog with a friendly message. Common errors include:

ConditionMessage
Branch already checked out elsewhere"This branch is already open in another worktree." An Open Worktree button appears so you can navigate directly to it.
Directory creation failed"Cannot create directory — check permissions or available disk space."
Invalid branch name"The branch name contains invalid characters."
Worktree path conflict"A worktree already exists at this path."

For any other git error, the raw output is shown directly. A collapsible "Show details" section reveals the full git output when the friendly message differs from the raw error.

Bulk Worktree Creation

When you want to spin up worktrees for several GitHub issues or pull requests at once, bulk creation handles the entire batch in a single operation. Select the items you need, and Daintree creates a worktree for each one with per-item progress tracking, automatic retry for transient errors, and step-level recovery if something goes wrong partway through.

To start, open the Issues or Pull Requests panel from the toolbar. Hover over any row to reveal its checkbox, then check the items you want. Once the first item is checked, all rows show their checkboxes for the rest of the session. A floating action bar slides up from the bottom showing the selection count and a Create Worktrees button. See GitHub Integration for more on the GitHub panel.

Selection shortcuts

Shift-click extends the selection as a range from the last checked item to the clicked one. A Select all button appears in the header when a search is active, and for issues there is also a Select unassigned option. Press Escape to clear the selection. Note that the selection resets when the dropdown closes.

Tip
Use the numeric search syntax to quickly find specific items: #42 for a single issue, #42,#43,#44 for multiple, or #40-45 for a range. Select what you need from the results, then click Create Worktrees.

The plan view

Clicking Create Worktrees opens a dialog showing every selected item with its proposed branch name. Before execution begins, you can configure two optional settings:

  • Assign to me (issues only) — assigns each issue to your GitHub account during creation. This toggle is remembered across sessions.
  • Run Recipe — picks a recipe to run on each new worktree after creation. Defaults to your project's configured default recipe, or the last recipe you selected for bulk creation if you've changed it. See Recipes for more on recipes.

Items that cannot be created are shown at reduced opacity with a skip badge explaining why:

  • Closed or Merged — the issue or PR is no longer open
  • Has worktree — a worktree already exists for this item
  • No branch info — the PR lacks branch metadata (typically fork PRs)

If every selected item is skipped, Daintree shows a toast and the execution view never appears.

Branch naming

For issues, Daintree generates a branch name from the issue's labels and title. Labels like bug or hotfix produce a bugfix/ prefix, feature or enhancement produce feature/, and docs produces docs/. If no label matches, the title is scanned for keywords, falling back to feature/. The full pattern is {prefix}/issue-{number}-{slug}, for example feature/issue-42-add-dark-mode. If the branch name already exists, Daintree appends a numeric suffix automatically.

For pull requests, Daintree uses the PR's existing source branch directly.

Execution and progress

Once you click the confirm button, each item moves through a series of stages: creating the worktree, spawning terminals (if a recipe is selected), assigning the issue (if enabled), and verifying terminal health. The dialog shows a spinner, checkmark, or warning icon for each item as it progresses, along with a progress bar tracking the overall batch.

Daintree processes up to three items concurrently. The old limit of two has been raised now that git worktree add --no-track sidesteps the .git/config.lock contention that used to block parallel operations. Non-transient git errors still fail fast, so lock collisions do not turn into silent retries.

Tip
Retry Failed picks up where each item left off. If a worktree was already created before the failure, that step is skipped on retry. Only the failed step (worktree creation, terminal spawn, or verification) is re-attempted. This means you never end up with duplicate worktrees from retrying a partially completed batch.

Automatic retry

Transient errors are retried automatically up to two times per item with exponential backoff (starting at 3 seconds, capped at 30 seconds). A small retry badge appears on the item row when this happens. Errors that trigger automatic retry include git lock file conflicts, rate limits, and network timeouts. Non-transient errors like validation failures or missing resources fail immediately without retry.

Post-batch verification

When a recipe is selected, Daintree waits briefly after all items complete and then checks whether any spawned terminals crashed immediately after launch. Items with crashed terminals are downgraded from succeeded to failed, so you can use Retry Failed to re-spawn those terminals without recreating the worktree.

Completion

When the batch finishes, a toast notification shows the result: either all worktrees created successfully, or a count of successes and failures. The dialog footer shows a Done button that closes the dialog and switches to the last successfully created worktree. If any items failed, a Retry Failed button appears alongside it.

Note
If you cancel a bulk operation mid-execution, any worktrees already created remain on disk. Only items that had not yet started are dropped. These worktrees will appear in the sidebar and show as "Has worktree" in future bulk create sessions.

Moving an Agent to a New Worktree

If you're working with an agent in the main worktree and decide the work should be isolated to its own branch, you can create a new worktree and move the agent there in one step. The agent restarts fresh in the new worktree with your recent terminal output injected as context, giving it enough background to continue without you having to re-explain the task.

This option is available on agent panels only (Claude Code, Gemini CLI, Codex CLI, OpenCode). Cursor agent panels, plain terminals, browser panels, notes, and dev-preview panels don't have it.

How to access

  • Right-click the agent panel header and select Move to New Worktree… from the context menu
  • Open the three-dot menu (•••) in the panel header and select Move to New Worktree…

What happens

  1. Daintree captures the terminal's visible output buffer (up to 20,000 characters, with escape sequences stripped).
  2. The standard New Worktree dialog opens. Choose a base branch and enter a new branch name, the same as creating any worktree.
  3. On confirm, the panel moves to the new worktree: its working directory and worktree association update, and the agent restarts fresh in the new directory.
  4. Once the agent reaches an idle state, the captured history is injected as an opening prompt so the agent has context on what was done previously.
Note

The transferred history is what was visible in the terminal output, not structured session data. It's a best-effort handoff: the agent gets enough context to continue, but may not have full recall of earlier reasoning. If the terminal buffer is empty when you trigger the action (for example, on a panel that was just launched or restarted from scratch), no history is injected and the agent starts completely fresh.

The new worktree also starts without node_modules and other untracked files. Re-run your setup steps manually, or use lifecycle scripts to automate this.

Switching Worktrees

Click a worktree card in the sidebar to switch to it, or use Cmd+Alt+1 through 9 to switch by position. When you switch worktrees:

  • The panel grid shows terminals belonging to that worktree
  • Active worktree terminals get higher refresh rates
  • Background worktree terminals drop to 1 FPS to save resources

Deleting a Worktree

Delete a worktree from its context menu in the sidebar. You can optionally delete the associated branch. The main worktree (your repository root) is protected and cannot be deleted. If a worktree has running processes or a stuck teardown, the context menu also offers Force Delete to remove it immediately with a shorter timeout.

Comparing Worktrees

When multiple agents are working in parallel across different worktrees, you'll want to see how their changes diverge. The cross-worktree diff opens a side-by-side comparison between any two worktrees, showing every file that differs between their branch tips (based on each branch's latest commit; uncommitted changes are not included).

There are a few ways to open it:

  • Right-click a worktree card in the sidebar and select Compare Worktrees... — the worktree you clicked is pre-selected as the left (base) side
  • Three-dot menu on any worktree card header — same behavior, same pre-selection
  • Action palette — search for "Compare Worktrees" to open the modal without any pre-selection

Selecting worktrees

The modal has two dropdowns at the top: Left (base) and Right (compare), separated by a "vs" label. The main worktree appears first in both lists, followed by the rest alphabetically. Each dropdown disables whichever worktree is already selected on the other side, so you can't compare a worktree against itself. The comparison runs automatically as soon as both sides are selected.

File list

A sidebar on the left shows all changed files with a count header (e.g. "12 files changed"). Each file has a single-letter status badge:

BadgeColorMeaning
AGreenAdded — file exists only in the right worktree
DRedDeleted — file exists only in the left worktree
MAmberModified — file changed between the two branches
RBlueRenamed — file was moved or renamed
CPurpleCopied — file was duplicated from another path

Click any file in the list to load its diff in the main panel.

Diff panel

The right side of the modal shows a split diff view with syntax highlighting. Additions on the right (compare) branch appear in green; deletions appear in red. Binary files and files larger than 1 MB display a placeholder message instead of the diff content.

Tip

The left side is the base for the comparison. If you want to see what another agent added, put your worktree (or main) on the left and the agent's branch on the right. Additions that exist only on the right side show up as green lines.

The comparison uses a direct branch-tip diff rather than a merge-base comparison, so it shows the complete difference between the two snapshots regardless of shared history. For staging and committing changes within a single worktree, see Review Hub.

Worktree Cards

Each worktree appears as a card in the sidebar showing:

  • Branch name with a badge for the main worktree (moves to the secondary row when an issue is linked)
  • Mood indicator (stable / active / stale / error)
  • GitHub issue title as the card headline when linked, plus PR badges showing PR state
  • Git status — modified file count, insertions, and deletions
  • Last commit message summary
  • Terminal list — all panels in this worktree with their type and state
  • Quick actions — launch agents, run recipes, open in editor

Local git state on worktree cards (commits, branch switches, and file changes) stays current through file-system event watching rather than polling. Daintree monitors git-internal files and the working directory for changes, so the sidebar reflects new commits and branch switches within roughly 300 milliseconds. During sustained file writes from an agent, updates arrive within one to two seconds. A 30-second polling fallback kicks in automatically if the OS-level watcher is unavailable, which primarily occurs on Linux when the inotify watch limit is exceeded.

Tip

On Linux, if you notice sidebar updates lagging by around 30 seconds, the inotify watch limit may be exhausted. Check Daintree's log file (Settings > Troubleshooting > Open Log File) for a watcher initialization warning. To fix this, raise the limit with sudo sysctl -w fs.inotify.max_user_watches=524288. To make the change permanent, add fs.inotify.max_user_watches=524288 to /etc/sysctl.d/99-inotify.conf and run sudo sysctl --system.

Cards can be collapsed by double-clicking anywhere on the card header. Collapsed cards take up less vertical space in the sidebar while still showing key status information. Double-click again to expand.

Lifecycle Stage Chip

A small triangular chip in the top-left corner of each worktree card tells you the next action needed for that worktree. The chip is color-coded and appears only when there is something actionable.

ColorStateMeaning
AmberWaitingAn agent in this worktree is waiting for your input.
GreenCompleteThe worktree has a linked issue, an open PR, no uncommitted changes, and no active agents. It is ready for review.
PurpleCleanupThe PR has been merged. This worktree has served its purpose and the branch can be deleted.

When multiple conditions apply, the chip shows the highest-priority state. Priority order is: cleanup, then complete, then waiting. A merged PR always takes precedence over other signals because the lifecycle action (delete the worktree) is the most definitive.

The chip only appears on secondary worktrees. The main/primary worktree never shows a lifecycle chip. The chip is also absent while git data is still loading for a worktree.

Note
The green "complete" chip requires both a linked GitHub issue and an open pull request. A PR without a linked issue will not trigger the green chip. For details on linking issues and PRs to worktrees, see GitHub Integration.
Tip
When you see a purple chip, the branch is merged and the worktree is safe to delete. This is Daintree's signal that the worktree has done its job. Right-click the card and choose Delete Worktree to clean up.

When a worktree reaches the "complete" state (green chip), you can open the Review Hub from its context menu to stage, review, and push the changes.

Quick State Filter

A row of pill-shaped tabs sits above the worktree list, letting you filter worktrees by their current agent state. The bar is visible whenever you have at least one secondary worktree.

Four tabs are available: All, Working, Waiting, and Finished. The Working, Waiting, and Finished tabs each show a count in parentheses (e.g. "Working (3)") that updates reactively as agent states change. Counts exclude the main worktree.

The active tab gets an accent-tinted background to make the current filter obvious. Clicking a tab that is already active resets the filter back to All, so you can toggle a filter on and off with the same button.

Each filter maps to specific worktree states:

  • Working shows worktrees with at least one agent session in the working or running state
  • Waiting shows worktrees where an agent is waiting for input (amber chip)
  • Finished shows worktrees with the complete (green) or cleanup (purple) chip

The quick filter composes with the search bar and the advanced filter popover using AND logic. All three can be active simultaneously, narrowing the list progressively.

Note
The quick filter is session-only. It is not persisted to disk and resets when you restart Daintree. This is deliberate, so you never accidentally filter out worktrees on next launch.

Collapsed Cards

When a worktree card is collapsed, the full card body (git status, last commit, terminal list, quick actions) is hidden. What remains in the header row is the branch name and a set of compact session state indicators.

These indicators show the count of agent sessions in each non-idle state, using small colored icons:

StateIconColorAnimated
WorkingSpinning circleOrangeYes
DirectingInteracting circleBlueNo
WaitingHollow circleGrey (amber when prompting)No
RunningPlay iconBlueNo
CompletedCheckmark circleGreenNo

Each indicator is rendered as an icon followed by a count (e.g. a spinning circle and "2" for two working sessions). Hovering the indicator group shows a tooltip with the full summary, such as "3 sessions: 2 working, 1 waiting". Idle sessions are never shown in the collapsed view.

If all sessions are idle or there are no active sessions, no indicators appear and the collapsed card shows just the branch name. For full definitions of each session state, see AI Agents.

Primary Worktree Card

The main worktree card (typically your main, master, or develop branch) uses an enhanced two-row header layout that gives you a project-level overview at a glance.

The top row shows the project name (not the branch name), a sprout icon, a collapse button, and the actions menu. The second row (visible when expanded) shows the branch label, an upstream sync indicator, and an aggregate summary of all secondary worktrees.

Upstream sync

When your main branch is ahead of or behind the upstream remote, the second row shows commit counts: ↑N in green for commits ahead and ↓N in amber for commits behind. Hovering shows the full text (e.g. "3 commits ahead, 1 commit behind upstream").

Aggregate summary

When your project has secondary worktrees, the second row also shows a compact summary: a branch icon with the total worktree count, followed by colored state counts for working and waiting worktrees. The finished count only appears when there are no working or waiting worktrees. This prevents a mixed signal where finished worktrees might distract from active work that still needs attention.

Note
The aggregate counts come from all secondary worktrees in the project. The finished count is intentionally hidden when any worktrees are in the working or waiting state. Once all active work completes, the finished count appears.

Card body

When expanded, the primary worktree card body shows two summary rows:

  • Worktree and agent counts with the same aggregate breakdown as the header (worktree count, working, waiting, finished)
  • GitHub health pulse showing CI status, open PR count, and open issue count. This row only appears when GitHub health data is available, which requires a configured GitHub token. See GitHub Integration for token setup.

Search and Filters

At the top of the worktree sidebar, a unified search bar lets you find worktrees by name or branch. Type to search with a 200ms debounce, so the list filters as you type without flickering on every keystroke.

Press Escape to clear the search text and any active filters in one action. If the search is already clear, pressing Escape again blurs the input. The X button at the right edge of the search bar also clears both text and filters.

Advanced filter popover

The filter icon button inside the search bar opens a popover with detailed filter and sort options. When any non-search filter is active, a small green dot badge appears on the filter icon.

CategoryOptions
Sort byDate created, Recently updated, Alphabetical, Custom order
Group by typeToggle on/off (groups worktrees by branch type prefix)
StatusActive, Dirty (uncommitted changes), Stale, Idle
Branch TypeFeature, Bugfix, Refactor, Chore, Docs, Test, Release, CI, Deps, Perf, Style, WIP, Main, Detached, Other
GitHubHas Issue, Has PR, PR Open, PR Merged, PR Closed
SessionsHas Terminals, Working, Running, Waiting, Completed
ActivityLast 15 min, 1 h, 24 h, 7 days

The "Custom order" sort option is hidden when "Group by type" is enabled. A "Clear all filters" button appears at the bottom of the popover when any filter is active.

Note
Advanced filters from the popover persist across sessions. The quick state filter bar above the worktree list does not. This means your sort order and detailed filters are remembered between restarts, but the quick filter always starts fresh.

Visual Hierarchy

When you have many worktrees open, Daintree dims idle and stale cards to keep active work visually prominent. This muting reduces the visual weight of cards that do not currently need your attention.

A card is muted when it is idle or stale, has no waiting agents, is not the currently selected worktree, and is not being hovered or focused. Muted cards use a dimmer text color for branch names and other labels.

The key exception: any card with a waiting agent is never muted, even if the worktree is otherwise idle or stale. Waiting demands your attention, so these cards always render at full visual weight. The currently selected card and any card you hover over are also never muted.

Fleet Arming

Fleet arming is persistent multi-cursor selection for agent terminals. You pick the agents you want to act on, and then every Accept, Reject, Interrupt, Restart, Kill, or Trash (plus anything you broadcast through the Fleet Composer) fans out to all of them at once. The armed set survives until you explicitly disarm it, so you can arm a group, read their output, and decide what to do next without losing the selection.

Only agent terminals are eligible: Claude Code, Gemini CLI, Codex, OpenCode, and similar. Plain terminals, browser panels, notes, and dev-preview panels cannot be armed. Terminals that move to trash or background are pruned from the armed set automatically, and the entire set clears when you switch projects.

Arming Terminals

Every worktree card has an Active Sessions accordion listing its agent terminals. Arming happens there:

  • Click a terminal tile to toggle its armed state.
  • Shift-click a tile to extend the selection from the last armed terminal to the clicked one.
  • Drag a marquee across tiles to arm a rectangular region. The drag activates once you move past a 4px threshold, so click-to-arm is not affected.
  • Press Cmd+Shift+B to arm every eligible agent in the current worktree (terminal.armDefault).

Armed terminals get a 2px accent-green outline, a subtle green-tinted background, and a numeric badge showing their arm order (1, 2, 3…). Arm order matters because broadcasts and quick actions run against terminals in that order.

Tip
Arm order is the order you armed them in. If you want one agent to receive a prompt first (for example, the one whose response you will use as a template), arm it before Shift-click or marquee-extending the rest.

The Fleet Arming Ribbon

Whenever at least one agent is armed, a full-width ribbon slides in above the panel grid. It stays visible until the armed set is empty. Reading left to right, the ribbon contains:

  • An armed count label in accent color (e.g. "3 agents armed").
  • State preset pills for Working, Waiting, and Finished. Click a pill to replace the current selection with every terminal in that state; Shift-click to extend the existing selection instead.
  • Quick action buttons for Accept, Reject, Interrupt, Restart, Kill, and Trash. Each button disables itself when its eligibility rule is not met.
  • Hint text reading "Esc to disarm · ⌘Esc Esc to interrupt · Shift-click to extend".
  • An X button on the right that disarms everything.

Quick Actions

The six quick actions run against all armed agents in arm order. Each has a default keyboard shortcut and a confirmation threshold that kicks in above a certain count or for inherently destructive actions.

ActionShortcutEligibilityConfirmation
AcceptCmd+YAt least one armed agent in waiting stateNever
RejectCmd+NAt least one armed agent in waiting state5 or more waiting agents
InterruptCmd+Esc Esc (double-tap, 350ms)At least one agent in working, running, or waiting state3 or more eligible agents
RestartCmd+Shift+RAt least one live armed terminalAlways, with a session-loss warning when any agent has an active session
KillCmd+Shift+KAt least one live armed terminalAlways
TrashCmd+Shift+BackspaceAt least one live armed terminal5 or more live terminals

When confirmation is required, the ribbon body swaps to a single-line prompt ("Interrupt 4 agents?") with Enter to confirm and Esc to cancel. Confirmation does not fire while focus is inside a text input, textarea, or terminal (xterm) surface, so you will not interrupt a fleet by pressing Enter mid-sentence. If armed agents exit or become ineligible while the prompt is showing, the confirmation collapses on its own.

Interrupt is special: rather than a single chord, it fires on a Cmd+Esc double-tap within a 350ms window. This keeps the normal Esc stack (clear confirmation, then disarm) working without colliding with a global interrupt shortcut.

Pressing Esc clears a pending confirmation first, and only disarms the fleet once no confirmation is active. If you want a faster bailout, the X button on the ribbon disarms everything in one click.

Fleet Composer

Directly below the ribbon sits the Fleet Composer: a textarea that broadcasts whatever you type to every armed agent. It is only visible while the fleet is armed, and it preserves its draft as long as the armed set is non-empty. The placeholder reads "Broadcast to N armed agents (Enter to send)".

  • Enter sends the message.
  • Shift+Enter inserts a newline.
  • Cmd+Enter force-sends, bypassing any safety confirmation.
  • Arrow Up / Arrow Down walk through broadcast history for this project.
  • Esc with a non-empty draft clears the draft first; with an empty draft, it bubbles up to the escape stack and disarms the fleet.
  • Cmd+Shift+F (terminal.focusFleetComposer) focuses the composer from anywhere.

The composer watches for three conditions and shows an amber confirmation strip when it sees any of them: a multi-line message, a payload larger than 512 bytes, or a destructive command pattern (rm -rf, sudo, git clean -f, DROP TABLE, truncate table, chmod -R, mkfs, dd if=, and fork bombs). The strip offers a Cancel button and a Send anyway button; Esc dismisses it and Cmd+Enter forces the send.

Broadcasts support per-terminal template substitution using double-brace syntax: {{branch}}, {{worktree_path}}, {{worktree_name}}, {{project_root}}, and {{endpoint}}. Each armed agent receives a version of the message with its own values substituted in.

Note
The Fleet Composer and Remote Compute variables are a separate namespace from the Bulk Operations and Recipes variables documented on the AI Agents page. Bulk Operations uses {{branch_name}}, {{issue_number}}, {{pr_number}}, and similar. Copying a template across systems will leave unresolved placeholders in the output.
Tip
Template variables make "run the test suite for {{branch}}" or "open the README at {{worktree_path}}/README.md" work across a fleet without hand-editing per agent. Unresolved placeholders are left as-is, so typos fail loudly instead of silently.

Cluster Attention Pill

The Cluster Attention Pill is a proactive nudge that lives in the ContentDock toolbar above the panel grid. It only appears when two or more agent terminals share a notable state, which saves you from scanning for agents that need the same action. Only one pill is visible at a time; if several clusters exist, the pill picks the highest-priority one (prompt beats error beats completion, with ties broken by size then recency).

ClusterAppears whenColor
PromptTwo or more agents are waiting with a promptAmber (clock icon)
ErrorTwo or more agents exited with a non-zero codeRed (alert icon)
CompletionTwo or more agents completed within the last 30 secondsGreen (check icon)

Each pill carries an arm button that arms every member of the current cluster in one click, and an X button that dismisses it. Dismissals are per-signature and session-only: the same cluster stays hidden, but a new cluster (different members or timestamps) will surface again. Completion clusters dissolve automatically after 30 seconds if you leave them alone.

For a consolidated list of fleet shortcuts alongside every other keybinding, see Keyboard Shortcuts. For more on the underlying agent states, see AI Agents.

Remote Compute

Remote compute is a per-worktree lifecycle hook system. You describe how to provision, pause, resume, tear down, check the status of, and connect to an external compute resource (Docker container, cloud VM, Akash lease, remote sandbox) and Daintree runs those scripts in response to worktree actions. The compute lifecycle is bound to the worktree lifecycle, so spinning up a branch spins up its environment, and deleting the worktree tears it down.

This builds on top of the project-level lifecycle scripts. Lifecycle scripts cover local setup (install dependencies, copy env files); remote compute covers anything that lives off-machine.

Configuration

Resource configuration lives in .daintree/config.json. Daintree checks three locations in priority order and the first valid file wins, with no merging between levels.

  1. ~/.daintree/projects/<sanitized-root>/config.json — user-level override, kept outside the repo. Handy for personal resources that should not be committed.
  2. <worktreePath>/.daintree/config.json — worktree-level config, scoped to a single branch.
  3. <projectRootPath>/.daintree/config.json — repo-level config, the usual location for team-shared environments.
Note
Worktree-level config takes precedence over repo-level config. A branch that needs its own compute definition can carry a .daintree/config.json at the worktree root and override the repo default without touching shared files.

A legacy .canopy/config.json is auto-migrated to .daintree/config.json on first load. The migration is cached per-path, so polling does not re-migrate on every check.

The config file has two shapes. Use the singular resource key for a single environment:

{
  "resource": {
    "provision": ["docker compose up -d"],
    "teardown": ["docker compose down -v"],
    "resume": ["docker compose start"],
    "pause": ["docker compose stop"],
    "status": "docker compose ps --format json | jq -s '{status: (if length > 0 then \"running\" else \"stopped\" end)}'",
    "connect": "docker compose exec app bash",
    "timeouts": { "provision": 300, "teardown": 300 },
    "statusInterval": 60,
    "provider": "docker"
  }
}

Or use the plural resources key for multiple named environments:

{
  "resources": {
    "docker-local": { "provision": [...], "teardown": [...], ... },
    "akash": { "provision": [...], "teardown": [...], ... }
  }
}

When resolving which environment to use, Daintree walks the following order: the exact environmentId requested, then resources["default"], then the first entry in the map, then finally the singular resource key.

Lifecycle Phases

Six lifecycle phases map to six actions available in the action palette and the worktree card context menu. Execution order is provision → (use) → pause ↔ resume → teardown, with status as a side-channel poll and connect as an interactive handoff.

PhaseActionDefault timeoutConfirmation
provisionworktree.resource.provision300sNone
teardownworktree.resource.teardown300sConfirm
resumeworktree.resource.resume120sNone
pauseworktree.resource.pause120sConfirm
statusworktree.resource.status120sNone
connectworktree.resource.connectn/a (opens a terminal panel)None

Each timeout is configurable per-phase in the timeouts block of the resource config. On timeout, Daintree sends SIGTERM to the process group and escalates to SIGKILL after 5 seconds on Unix, or taskkill /F /T on Windows. The last 8192 bytes of output are captured and shown in the notification.

The status command must output JSON with at minimum a status field. Additional fields like endpoint and meta are parsed and stored alongside the worktree. Setting statusInterval (in seconds) enables automatic periodic polling. Daintree re-runs the status command at that cadence and updates the resource badge on the worktree card.

Teardown failure does not block worktree deletion. This is deliberate: if the remote is unreachable or already gone, you should still be able to delete the worktree locally and clean up the remote separately.

Template Variables

Lifecycle commands support two substitution syntaxes. Double-brace {{variable}} maps to fleet and runtime values; single-brace {variable} maps to filesystem-derived slugs. All substitutions are shell-escaped automatically (single-quote wrapping on Unix, double-quote on Windows), except {branch-slug} which is sanitized to [a-z0-9-] and left unquoted. Unresolved placeholders are left in place so typos are obvious. See Security > Lifecycle Command Injection for the threat model and escaping rules in full.

SyntaxVariableValue
{{...}}{{branch}}Branch name on the worktree
{{...}}{{worktree_path}}Absolute path to the worktree directory
{{...}}{{worktree_name}}Short name of the worktree
{{...}}{{project_root}}Absolute path to the project root
{{...}}{{endpoint}}Last known endpoint from the status command
{...}{branch-slug}Branch name sanitized to lowercase [a-z0-9-]
{...}{repo-name}Repository folder name
{...}{base-folder}Base folder of the worktree path
{...}{parent-dir}Parent directory of the worktree

In addition to template substitution, Daintree injects the following environment variables into every lifecycle command:

  • DAINTREE_WORKTREE_PATH — absolute path to the worktree
  • DAINTREE_PROJECT_ROOT — absolute path to the project root
  • DAINTREE_WORKTREE_NAME — short name of the worktree
  • DAINTREE_BRANCH — branch name (when present)
  • DAINTREE_RESOURCE_PROVIDER — value of resource.provider from the config
  • DAINTREE_RESOURCE_ENDPOINT — endpoint returned by the last status call
  • DAINTREE_RESOURCE_STATUS — raw output of the last status command

Named Environments

Named environments let a single project support multiple compute targets. For example, a cheap local Docker setup for day-to-day dev and a remote Akash lease for longer-running integration runs. Define them under resources in the config, or manage them visually in Project Settings > Resource Environments (Worktree Setup tab).

The settings GUI supports multiple named environments with a dropdown selector, an icon picker (Server, Cloud, Container, CPU, Globe, Rocket, Database, Terminal, Box, Layers), per-environment command lists with drag-to-reorder, an in-UI variable reference, and a Default Worktree Mode selector that controls what is pre-selected in the New Worktree dialog.

Settings are stored under resourceEnvironments, activeResourceEnvironment, and defaultWorktreeMode in either ~/.daintree/projects/<sanitized-root>/settings.json (user-local) or <projectRoot>/.daintree/settings.json (in-repo mode), matching the storage mode you picked for the project.

Auto-Provision

Auto-provision fires only when a worktree is created with Remote mode selected in the New Worktree dialog. It does not run on app launch, project open, or when you switch modes on an existing worktree. This keeps remote spin-up predictable: provisioning is always the result of an explicit choice at creation time.

Once the resource is provisioned, the worktree card shows a resource status badge driven by the most recent status command output. The badge color reflects the JSON status value: green for healthy/running states, amber for paused or transient states, red for unhealthy or error states.

Tip
To provision an existing worktree that was originally created in local mode, run the Provision action from the worktree card context menu (or the action palette). The same applies to Teardown, Resume, Pause, Status, and Connect. Every phase is available as an explicit action once a resource is configured.