Skip to content

How-to: working with a running environment

Your environment is up. This page is about living with it day to day — attaching, detaching, inspecting, and parking it without losing work. For full flag detail see the command reference.

flowchart TD
    R[Running environment] -->|ffleet up SLUG| A[Attached to agent session]
    A -->|detach: Ctrl-B D| R
    R -->|ffleet goto SLUG| S[Shell in the worktree]
    S -->|exit| R
    R -->|ffleet ls / status / tail / path| I[Inspect: no state change]
    R -->|ffleet stop SLUG| P[Stopped: container gone, worktree+branch+metadata kept]
    P -->|ffleet up SLUG| A

Selecting an environment

Every environment command (up, remove, goto, stop, tail, status, path, continue) takes an optional SLUG. Omit it and Forge Fleet first tries to resolve the environment from your current directory; if that doesn't match, it opens an interactive picker:

  • On a capable terminal you get an arrow-key highlight bar — move with ↑/↓ and press Enter to choose. For ffleet up, the list ends with a "create a new environment" entry.
  • On a plain terminal (TERM=dumb, NO_COLOR, or no ANSI support) it degrades to a numbered 1) 2) 3) menu — type a number and press Enter.

When stdin isn't a terminal (a pipe or CI), no picker is shown: pass SLUG explicitly, or you'll get a "SLUG is required…" error.

Attach & detach

  • Attach: ffleet up SLUG drops you into the agent's session (when it's live). That's the same command you used to start it.
  • Detach: press tmux's Ctrl-B then D. This leaves the agent running — you're just stepping out. Re-attach later with ffleet up SLUG.
  • Peek without risk: ffleet up SLUG --peek attaches only if the agent is already live and never relaunches anything.

You can also send a follow-up from your host shell:

$ ffleet up my-feature -p "Continue with the remaining tests" --no-attach

For Codex, this submits the message in the existing conversation and leaves its terminal interface available for your next reply. Use --prompt-file for a longer or multiline instruction.

goto — a shell in the worktree (not the agent)

ffleet goto SLUG opens a shell in the environment's worktree directory — distinct from attaching to the agent. Use it to inspect files, run a command, or make a manual edit alongside the agent. Leave it with exit.

$ ffleet goto my-feature   # you're now in the worktree
$ git log --oneline -5
$ exit

Related: ffleet path SLUG just prints the worktree path, so you can cd "$(ffleet path my-feature)" from your own shell.

Environment metadata — where it lives

Each environment has a small metadata directory (session.json, container.git, claude.json) recording its live state — which container it maps to, the agent session to resume, and git bookkeeping. Forge Fleet reads it for ffleet ls / status and to resume your session on the next up.

New environments keep this outside the checkout, under ~/.forge-fleet/{project-id}/worktree-meta/{env-name}/. This keeps it outside the reach of even aggressive in-worktree cleanup — git clean -fdx, git stash --all — which can't delete it or orphan the container. You never need to tiptoe around a .forge-fleet/ directory in your worktree.

Older environments (created before this change) still keep the directory in-worktree at <worktree>/.forge-fleet/. There's no migration — they keep working. For those, deleting the directory by hand still orphans the environment, so use ffleet stop / ffleet remove to tear one down rather than removing files. Forge Fleet marks that in-worktree directory as ignored (via the worktree's info/exclude) so routine git stash -u / git clean -fd won't touch it, but git clean -fdx / git stash --all / rm -rf .forge-fleet still would.

Either way: to tear an environment down, use ffleet stop or ffleet remove — never delete metadata by hand.

Inspecting

None of these change state:

  • ffleet ls — all environments for the project (add --json for scripts). ffleet projects ls widens that to every registered project.
  • ffleet status SLUG — live status: container up?, agent live/exited, what the agent is doing and why we say so (add --json for scripts).
  • ffleet tail SLUG — the last lines of the agent's transcript (-n/--lines N, default 100).
  • ffleet path SLUG — the worktree/working-directory path.

Is the agent still working?

Forge Fleet answers that from what the environment can actually show: whether the container is up, whether the agent's terminal pane is still alive, and the agent's own session transcript — the record each agent writes as it works. It reports one of four states in the AGENT_STATE column:

State Meaning
working mid-turn: thinking, or running a tool.
idle it finished its turn; the next move is yours.
exited the agent process is gone (the container may still be up).
unknown there is no evidence to read — see the probe reason in status.
$ ffleet ls
project: my-project
SHORT        ENV                     AGENT   CONTAINER               CONTAINER_STATE  RUNTIME  LAST_ACTIVITY              AGENT_STATE
-----------  ----------------------  ------  ----------------------  ---------------  -------  -------------------------  -----------
add-logging  my-project-add-logging  claude  my-project-add-logging  running          running  2026-08-12T09:14:22+00:00  working
fix-auth     my-project-fix-auth     claude  my-project-fix-auth     running          running  2026-08-12T09:12:03+00:00  idle

LAST_ACTIVITY is when the agent last did something (its last transcript record), not when Forge Fleet last touched the environment.

The RUNTIME column is the layer below: whether the container is up (stopped, starting, running, failed, unknown). starting means the container is up and the agent is still coming up — normally a few seconds. An environment that is still without an agent two minutes after its container started reads as failed instead: the agent never came up, or died at launch. Relaunch it with ffleet up SLUG (see Troubleshooting).

status adds the why: the probe reason behind the state, and the raw evidence it was derived from.

$ ffleet status add-logging
Environment status
  name: my-project-add-logging
  slug: add-logging
  branch: my-project-add-logging
  agent: claude
  runtime: running
  agent state: working
  probe reason: tool call pending
  last activity: 2026-08-12T09:14:22+00:00
  evidence: liveness=running, pane=alive, source=transcript, pending_tool=Bash
  container: my-project-add-logging
  session id: 6f1c…
  tmux session: main
  worktree: ~/.forge-fleet/my-project/worktrees/add-logging

Two things worth knowing:

  • Both agents report real state. Claude Code is read from its per-session transcript, Codex from the session rollout it writes under --codex-dir (~/.codex/sessions/…), matched to the environment by the working directory it was started in.
  • unknown is never an error by itself. It says the evidence was missing — a fresh environment whose agent has not written anything yet, a stopped container, or a session whose transcript is gone. For Codex that also covers an environment created by an older Forge Fleet, whose container works from /workspace instead of the worktree path the rollout records. Runtime state, attach and tail all work as usual either way.

For scripts, ffleet ls --json carries agent_state plus the raw probe_reason token (turn_ended, tool_call_pending, agent_thinking, agent_exited, runtime_not_running, runtime_starting, tmux_session_missing, transcript_absent, transcript_unreadable, adapter_not_supported), alongside runtime_state.

It also carries broken_reason — empty for a healthy environment, and otherwise one of:

  • worktree_registration_missing — the environment's git registration was deleted (recovery)
  • workdir_missing — an in-place environment whose working directory is gone (recovery)
  • metadata_missing — a worktree directory whose session.json is gone or unreadable (recovery)
  • worktree_missing — a surviving container whose worktree was deleted (recovery)

The table prints the same information as a warning under the rows. Broken environments are listed so you can see and clear them: ffleet remove SLUG tears each one down, while restore and remove --done skip them.

Changed in this release. Agent state used to be inferred by diffing the terminal 400 ms apart, and an unchanging screen was reported as waiting for input. That guess is gone, along with the BLOCKED_REASON column and the blocked_reason JSON field — read agent_state / probe_reason instead.

stop — park it without losing work

ffleet stop SLUG removes the container but keeps the worktree, branch, and metadata. It's the safe "get this out of the way" move — reversible, unlike remove.

$ ffleet stop my-feature   # container gone, everything else preserved
$ ffleet up   my-feature   # revive: rebuilt from config, session resumed, no data loss

Because containers run with --rm, there's no lingering "stopped container" — a stop removes it, and up rebuilds a fresh one when you come back. Your work lives in the worktree and the saved session, not the container.

ffleet projects stop does the same for every environment on the machine — the whole fleet parked in one command. Each one is recorded as parked, so a later ffleet projects restore leaves them down; ffleet up SLUG brings one back.

For Claude agents, session resume is exact — it reattaches this environment's own last session. For Codex agents it's best-effort: Codex resumes the most recently used session (filtered to sessions started from the same working directory) rather than one by ID. Since each environment's container workspace is mounted at its own host-identical path, this resolves correctly for concurrent environments in the common case even though they share --codex-dir by default — see the container-config note for the mechanism and the fallback if you ever hit a mismatch.

restore — bring everything back after a reboot

A host restart — or restarting the Docker daemon — takes every container with it. Your worktrees, branches and saved sessions are untouched, but the agents are gone. ffleet restore brings back, in one go, every environment that was supposed to be running:

$ ffleet restore           # this project
$ ffleet projects restore  # every project you've run `ffleet init` in
project my-app
  my-feature: restored
  spike: skipped (parked)
  review: skipped (already running)

restored 1, skipped 2, failed 0
  • It respects what you asked for. Only environments whose last recorded intent was "running" come back. Anything you parked with ffleet stop stays parked. Environments that predate this feature have no recorded intent at all, so they are left alone too — restore never guesses. Your first restore after upgrading may therefore do nothing; one ffleet up (or ffleet stop) per environment records the intent, and restore picks it up from then on.
  • It brings back containers, not dead agents. Restore is for the case where the containers are gone. An environment whose container is still up is reported "already running" and left as-is, even if the agent inside it exited — use ffleet up SLUG for that one.
  • Nothing is sent, nothing is attached. The agents come back idle, exactly where they left off; attach with ffleet up SLUG when you want one.
  • Safe to re-run. An environment that is already up is reported as such and left alone.
  • One failure doesn't stop the rest. Each environment is reported on its own line; the command exits non-zero if any of them failed.

ffleet projects restore works from any directory — it walks the projects you've registered with ffleet init, so you don't have to visit each repo. A project you never ran ffleet init in has nothing recorded to restore.

Working inside tmux

When you attach, you're inside a tmux session running the agent. You mostly just type at the agent, but a few tmux things are worth knowing:

  • Detach: Ctrl-B then D — leaves the agent running. The one key you must know.
  • Scroll back: the mouse wheel may not reach the agent's history. Enter copy-mode with Ctrl-B then [, scroll with arrows/PageUp, q to exit.
  • Copy / paste: tmux captures mouse selection, so a normal drag-select may not hit your clipboard. Hold Shift while selecting to use your terminal's own copy/paste (works in most terminals). Or copy within tmux via copy-mode (Ctrl-B [, select, Enter) and paste that buffer with Ctrl-B ].

New to tmux? See the tmux wiki or a shortcut cheat sheet.

Kept deliberately simple: one tmux session per container, a single pane (0). You can split extra panes yourself, but multiple tmux sessions — and attaching from inside another tmux (tmux-in-tmux) — are not supported.

Beyond tmux: driving Claude Code from the web

Attaching over tmux is Forge Fleet's built-in way to reach the agent, and it works for any agent. But if the agent is Claude Code, Claude Code can also expose its own session to the web — so you can watch and steer it from a browser or phone without ffleet up or tmux at all:

  • Remote Control — continue this session from another device via claude.ai/code. Execution stays inside the Forge Fleet container; you just reconnect to it from the web. This is the natural fit for detached/background work — start it here, keep an eye on it from anywhere.
  • Claude Code on the web — the fully hosted cousin, where sessions run on Anthropic-managed infra. Mentioned for completeness; it's a different execution model than Forge Fleet's containers.

Remote Control is a Claude Code feature, configured inside the agent (not by Forge Fleet), and has no equivalent for other agents such as Codex. Forge Fleet's tmux attach/detach above keeps working regardless.

Forge Fleet does name each Claude session after its environment (<project>-<slug>) and gives the container a matching hostname, so entries in Remote Control and the /resume picker are identifiable at a glance.

What can and can't change mid-life

When ffleet up rebuilds a stopped/broken environment, it refreshes config-derived settings from your current ffleet.toml/flags and saves them back. But identity/continuity settings are fixed at create time — to change those you must remove and recreate.

Refreshed on the next up (revive-time-mutable) Fixed at create (need remove to change)
image branch
extra_mounts worktree
extra_hosts in-place vs worktree mode
docker_env_file git-mode
credential auth coding agent
credential dirs (~/.claude / ~/.codex) the session
agent cmd / extra_args
agent model / reasoning effort

So: to pick up a new image, an added mount, or a changed env file, just ffleet stop then ffleet up. To change the branch, git-mode, or which agent runs, ffleet remove and start fresh. This is the practical face of config precedence.

Next

Done with the work? Completing / cleanup.