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 pressEnterto choose. Forffleet 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 numbered1) 2) 3)menu — type a number and pressEnter.
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 SLUGdrops 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-BthenD. This leaves the agent running — you're just stepping out. Re-attach later withffleet up SLUG. - Peek without risk:
ffleet up SLUG --peekattaches 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--jsonfor scripts).ffleet projects lswidens 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--jsonfor 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. unknownis 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/workspaceinstead of the worktree path the rollout records. Runtime state,attachandtailall 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 whosesession.jsonis 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 stopstays 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; oneffleet up(orffleet 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 SLUGfor that one. - Nothing is sent, nothing is attached. The agents come back idle, exactly
where they left off; attach with
ffleet up SLUGwhen 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-BthenD— 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-Bthen[, scroll with arrows/PageUp,qto exit. - Copy / paste: tmux captures mouse selection, so a normal drag-select may
not hit your clipboard. Hold
Shiftwhile 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 withCtrl-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.