Skip to content

How-to: completing & cleanup

When a piece of work is merged or abandoned, tear the environment down. When you're done with a whole project — or want to uninstall — clean up the ~/.forge-fleet registry. For full flags see the command reference.

flowchart TD
    E[Environment done] -->|ffleet remove SLUG| R[Worktree deleted; ffleet-created branch deleted]
    E -->|ffleet remove --done| S[Tick the landed ones; all removed in one pass]
    S --> D
    R --> D{More environments in this project?}
    D -->|yes| E
    D -->|no| P[Project-level cleanup]
    P -->|ffleet projects ls| L[See every known project and its environments]
    P -->|ffleet projects prune| PR[Drop stale entries + their ~/.forge-fleet home dirs]
    P -->|ffleet projects rm ID| RM[Delete one project's ~/.forge-fleet/ID dir + registry entry]

Get your work out first

Forge Fleet manages environments, not your git workflow: it never pushes your branch or opens a PR for you. Landing the work is your job — or the agent's, from inside the container — and you must do it before remove, which deletes the branch Forge Fleet created.

Find the branch. A worktree environment lives on a branch named <project>-<slug> (the same name as its container). Confirm it with status:

$ ffleet status my-feature
...
branch: my-project-my-feature
worktree: ~/.forge-fleet/my-project/worktrees/my-feature

Push / open a PR — either ask the agent ("commit, push, and open a PR"), or do it yourself from the worktree:

$ ffleet goto my-feature
$ git push -u origin my-project-my-feature
$ gh pr create
$ exit

Push credentials are not provided by default. The container knows your origin URL but has no push auth — no SSH key, no gh/git token, no host git config. It does speak HTTPS to your remote's host (git_https_rewrite, on by default), so all you supply is a token: create a fine-grained personal access token scoped to this one repository and pass it through docker_env_file:

# docker.env
GITHUB_TOKEN=github_pat_…
GH_TOKEN=github_pat_…

Prefer that to mounting ~/.ssh: a mounted key is your real, long-lived credential, valid for every repository it can reach and for as long as the environment exists. Your commit identity (name/email) is forwarded automatically from [git] or your host git config — only push credentials need this. Prefer to stay fully local? git_local_main = true bases the branch on your local main so you can merge locally with no push at all.

Once credentials are in place, the agent can push on its own. If you want it to commit but never publish, set git_allow_origin_writes = false: it denies git push, gh pr create, gh pr merge, gh release create, gh workflow run and the common spellings of a mutating gh api call at the agent's own permission layer. Matching is by command prefix, so the coverage has edges worth reading before you rely on it — see the caveats. It pairs naturally with git_local_main but is independent of it — neither implies the other, so set both if you want a fully local-only environment.

Forge Fleet sandboxes your setup — it doesn't replace it. The real leverage is still your own prompts, your skills/subagents, and templates that wire an issue straight into a seeded session. Forge Fleet's job is to run all of that inside an isolated, disposable container — so you can let an agent work autonomously without it touching your host. It's the safe sandbox around your workflow, not a substitute for it.

Remove one environment

ffleet remove SLUG tears the environment down: it stops the container and deletes the worktree. If Forge Fleet created the branch, that branch is deleted too; a branch you brought in with --from is left alone.

$ ffleet remove my-feature
$ ffleet remove my-feature -y   # skip the confirmation prompt

remove surfaces unpushed/uncommitted work before deleting so you don't lose it by surprise. This is irreversible — if you only want the container gone, use ffleet stop instead.

Before stopping the container, remove briefly asks the agent to exit on its own (/exit for Claude Code, /quit for Codex), waiting a few seconds at most. For Claude Code this is what makes it archive the session shown on claude.ai/code — otherwise you'd have to archive it by hand. stop deliberately skips this: a stopped environment is meant to come back, so its session is left active rather than archived.

Plain remove makes no network call at all: it is deliberately fast and offline-safe.

See which environments have landed

ffleet ls has a PR column. For each environment it shows what the pull requests whose head branch is that environment's branch say about it:

PR shows What it means
merged #4 the work landed
merged #4,#7 it landed as a stack of pull requests
open #9 still in review
— nothing known: no pull request on that branch, only closed ones, or the lookup could not run

The match is on the branch, which is a first-class GitHub field — so it still works after the branch is deleted on merge. If the agent opened its pull request from a sub-branch instead of the environment's branch, the column reads —: the environment is shown, just never swept.

The lookup needs the gh CLI, and gh owns its own auth. Forge Fleet never handles a token, a key or any credential for this — run gh auth login once and that is the whole setup. gh is optional: with it missing, unauthenticated, or pointed at a repo with no origin, the column simply reads — and everything else works exactly as before — no error, no warning, same exit code. Projects on pm = "linear" or pm = "none" leave the column blank and never invoke gh at all.

Sweep the landed environments (remove --done)

When several environments have finished, ffleet remove --done collapses N teardowns into one reviewed action:

$ ffleet remove --done
[forge-fleet] Refreshing remote refs (git fetch --prune)
Remove these landed environments:
  [x] fix-login   [my-project-fix-login]    merged #4
  [x] add-export  [my-project-add-export]   merged #7,#9
  [ ] flaky-test  [my-project-flaky-test]   merged #11  — unsaved work: uncommitted changes

Tick what you want gone and press enter. The checkmark is the approval — there is no second y/n prompt. Then a tally:

  fix-login: removed
  add-export: removed
  flaky-test: skipped (uncommitted changes)

removed 2, skipped 1, failed 0

Two independent things must both be true before an environment is offered:

  1. it landed — at least one merged pull request on its branch, and no open one;
  2. nothing would be lost — the same unsaved-work check plain remove runs.

A branch that is pushed but unreviewed is safe to delete yet not done; a merged branch with uncommitted changes is done yet not safe. An environment failing the second check is listed with its reason but never pre-ticked — you can still tick it deliberately. Every ambiguity resolves to "not swept": the worst case is that you clean up by hand, exactly as before.

Notes:

  • The sweep starts with one visible git fetch --prune, because the unsaved-work check reads your remote-tracking refs and is otherwise only as fresh as your last fetch. This is the only fetch in the teardown path; plain remove never fetches. A failed fetch is a warning, not a stop — the check just stays conservative.
  • A landed branch is not flagged just because its commits were squashed away: the check asks whether the default branch already contains the branch's content, not whether the commits themselves are there. Uncommitted changes and untracked files are still flagged on such a branch.
  • --done takes no SLUG. It sweeps, or it removes one — not both.
  • ffleet remove --done --yes skips the picker and removes every environment that is landed and clean. On a non-interactive stdin (CI, a pipe) --yes is required — without it the command errors rather than acting silently.
  • Nothing is ever removed unattended: there is no auto-removal on any trigger.

The project registry (~/.forge-fleet)

Forge Fleet tracks the projects it knows about in ~/.forge-fleet/registry.json, with each project's home data under ~/.forge-fleet/{id}/. The ffleet projects commands manage that registry — useful for multi-project cleanup and uninstalling.

  • ffleet projects ls — every known project with its environments grouped under it (add --json). Start here: each header carries the project's registry id, which is what projects rm takes. Bare ffleet projects does the same.
$ ffleet projects ls
project: my-project  [my-project-1a2b3c4d]  /repos/my-project
  • ffleet projects prune — remove stale registry entries and delete their ~/.forge-fleet home directories. Good housekeeping after you've deleted repos. Prompts first; -y to skip.
$ ffleet projects prune
  • ffleet projects rm PROJECT_ID — permanently delete one project's ~/.forge-fleet/{id} directory and its registry entry. Prompts first; -y to skip.
$ ffleet projects rm my-project-1a2b3c4d

Uninstalling Forge Fleet

To leave no trace:

  1. ffleet remove every remaining environment (or ffleet ls per project to find them).
  2. ffleet projects prune, or ffleet projects rm ID for each project, to clear ~/.forge-fleet.
  3. Delete the ffleet binary from your PATH.

Removing environments deletes worktrees and ffleet-created branches — double-check nothing is unpushed first.