Running · page 4 of 7
Worktrees per slot, the none, Docker and Supabase environments, and repairing a slot that is down.
A slot is one copy of your repository that one story builds in. A worker is a slot that is
allowed to build right now: slots says how many copies Foreman keeps, workers how many of
them build at the same time. This page is for whoever sets a project up to build more than one
story at once, or runs a project whose stories need a database or a container stack of their
own. It covers the folder each slot gets, what the three built-in environments give a slot,
and what to do when a slot is down. With the Salesforce pack, two more environments give each
slot a sandbox or a scratch org instead; see Build orgs.
Worktrees and env files
Each slot w1 to wN gets a git worktree at .foreman/worktrees/wN (under
stateDir when you change it). Foreman creates it the
first time the slot starts a story: it fetches <remote>/<baseBranch> from your main checkout
(three tries, since several slots fetching at once can collide), forgets any worktree whose
folder was deleted, and adds the new one detached at that commit. The story’s own branch is
checked out inside it when the session starts. The dashboard shows “preparing the slot” while
this happens, as a new slot can take a few minutes.
Your project’s env file, envFile (.env.local by
default), is linked into the worktree from the main checkout, when the main checkout has one.
Foreman never opens the file; it only makes the link. Foreman’s own keys file,
foremanEnvFile (.env.foreman), is never linked,
copied or created in a slot, so the agents building a story cannot read it. If envFile names
that same file, nothing is linked.
The install block says how a fresh copy gets its
dependencies:
"install": { "run": "npm ci", "when": "package-lock.json", "creates": "node_modules" }
run and when are required; creates is optional. The default is npm ci when
package-lock.json changes. The command runs with sh -c in the worktree when the hash of the
when file differs from the one recorded at the last install (kept in
.foreman/slots/wN.install), or when the creates path is missing from the worktree. A
worktree without the when file runs no install, and "install": null turns it off. A failed
install puts the slot down (see below).
Three more things live in the folder. Foreman’s learned skills are copied into
.claude/skills/foreman-learned-* and excluded from git, so a story commit never carries them;
see How Foreman learns. When a story ends, anything
left uncommitted is saved on a local leftover/ branch and the folder is reset and detached at
the base again; see Leftover
branches. And the dirty check that
decides whether a folder is clean ignores the
generated paths, so a regenerated lockfile or schema
never parks a slot.
Every story session gets FOREMAN_RUN=1, FOREMAN_SLOT (w2), FOREMAN_STORY (the id),
FOREMAN_SUPERVISOR_PID and FOREMAN_TEST_CORES in its environment, plus whatever the slot’s
environment adds. The session itself is described on Story
sessions.
How many slots and workers
slots is 1 by default and at most 8;
a change takes effect when the run restarts. workers is 1 by
default, never more than slots, and takes effect from the next pick; foreman run --workers N
sets it for one run, and the dashboard’s Workers control saves it for this computer. When you
lower the worker count, a slot above it finishes the story it has and then drains:
foreman status reads “4 running, limit 3: draining”. A story waiting in a slot over the count
moves to a free slot under it. A pack may cap slots: the Salesforce pack allows one slot per
build org, and one slot in all when the first stage’s org is also a build org.
What each slot action does to the folder
The dashboard’s slot cards and foreman slot <wN> <action> offer these actions, by the slot’s
state. Their words are the dashboard’s.
| Action | What it does | Shown when |
|---|---|---|
Skip story (skip) |
The session stops and the branch is kept. The story is not picked again while this run lasts, standby included, unless you ask for it with Build next; the slot moves on. foreman status lists the skipped stories. |
building, parked after a crash |
Take over (takeover) |
Any work in progress is committed and pushed to the story branch. The story is claimed for you and blocked, and the slot moves on to another story. | building, held, parked after a crash |
Park (park) |
An idle or down slot parks at once and picks up nothing until you unpark it. On a building slot, the story in progress carries on to the end, then the slot parks. | building, idle, down |
Unpark (unpark) |
The slot goes back to idle and takes the next story that can be built. | parked by you |
Resume (resume) |
The slot goes back to work on the same story, keeping what was done so far. | held, down, parked after a crash |
Restart fresh from main (restart) |
The folder is reset, the local branch deleted, and the story built again from the start. | parked after a crash |
Resume as is (resume-as-is) |
With a story, the changes in the folder are that story’s work and it carries on. With none, they are put aside in a git stash and the slot takes a new story. | parked on a dirty folder |
Discard changes and reset (discard) |
It asks you to confirm first. The folder is hard reset to main and the changes are gone for good. | parked on a dirty folder |
A slot held by a halt offers only Take over, since nothing can start until you press “I’ve fixed it”. Skip, Take over and Restart tidy the folder the same way a finished story does: what was uncommitted goes on a leftover branch first.
Test cores and check runs
Several slots running tests at once would each take every core. So each story session gets
FOREMAN_TEST_CORES: the machine’s cores split evenly between the workers, at least 2 each.
Foreman’s own test config reads it, and your project’s test runner can too. foreman doctor
warns when the load is over twice the cores during a run, and names the busiest processes.
Full check runs share the machine as well. checkRuns.maxParallel
says how many full test runs may go at once across all slots; unset, it is one per 8 cores, at
least 1. A run over the limit waits for a seat. A check runs once per tree: when a second slot
reaches a check another slot is already running on the same merged tree, it waits and takes
that run’s result instead of running the suite again. foreman report counts those waits.
env.adapter: none
The default. Nothing to start, nothing to repair: a slot is always up. The adapter takes one
option, ports, a map of variable names to base port numbers. Each slot gets FOREMAN_SLOT=wN
and every named port moved up by the slot number, so slot 1 gets base + 1, slot 2 base + 2.
"env": { "adapter": "none", "ports": { "PORT": 3000, "API_PORT": 4000 } }
Slot w2 then runs with PORT=3002 and API_PORT=4002. A base that is not a whole number, or
any other option, is a config error. Use this when each slot can run its own copy of the app
from the worktree alone, or when the stories need no running app at all.
env.adapter: docker
One Docker Compose project per slot. Options: composeFile (compose.yaml by default),
projectPrefix (required) and ports, a map of variable names to base ports.
"env": { "adapter": "docker", "projectPrefix": "acme", "composeFile": "compose.yaml", "ports": { "DB_PORT": 5400, "APP_PORT": 3000 } }
Each slot gets FOREMAN_SLOT=wN, COMPOSE_PROJECT_NAME=<prefix>-wN (acme-w2) and every named
port moved up by 100 times the slot number: slot 2 gets DB_PORT=5600 and APP_PORT=3200. Your
compose file reads those variables for its published ports, so the slots never collide.
Before a story starts, Foreman runs docker compose -f <composeFile> -p <prefix>-wN up -d --wait in the worktree with those variables set. When that fails it runs down and tries up
once more; a stack that comes up on the second try is reported as “environment reset”. If it
still fails, Foreman runs down again, so unhealthy containers do not keep holding the slot’s
ports, and the slot is down. The adapter needs the docker command; foreman doctor checks
it is there.
env.adapter: supabase
One local Supabase stack per slot, for projects whose stories need a database. Options:
projectPrefix (required), portStep (100), extraPorts (a map of names to { base, step }),
excludeStudio (true) and colima (false).
"env": {
"adapter": "supabase",
"projectPrefix": "acme",
"portStep": 100,
"excludeStudio": true,
// Only when Docker runs through Colima on this Mac.
"colima": false,
"extraPorts": { "APP_PORT": { "base": 3000, "step": 1 } }
}
The Supabase CLI reads its ports from environment variables, so supabase/config.toml is never
edited. Each slot n gets, with up = portStep × n:
| Variable | Value |
|---|---|
SUPABASE_PROJECT_ID |
<prefix>-wN |
SUPABASE_API_PORT |
54321 + up |
SUPABASE_DB_PORT |
54322 + up |
SUPABASE_DB_SHADOW_PORT |
54320 + up |
SUPABASE_DB_POOLER_PORT |
54329 + up |
SUPABASE_STUDIO_PORT |
54323 + up |
SUPABASE_INBUCKET_PORT and SUPABASE_LOCAL_SMTP_PORT |
54324 + up |
SUPABASE_ANALYTICS_PORT |
54327 + up |
SUPABASE_EDGE_RUNTIME_INSPECTOR_PORT |
8083 + n |
each extraPorts name |
base + step × n |
FOREMAN_SLOT |
wN |
Before a story starts, Foreman climbs a ladder, cheapest rung first: supabase status passing
means the stack is up; otherwise supabase start (with -x studio while excludeStudio is on)
means it was restarted; otherwise supabase stop --no-backup and start again means it was
reset; and if that fails too the slot is down. A slot that needed a restart or a reset gets an
event saying so. With colima: true, the adapter also has a repair: when the stack will not
come up at all, Foreman runs colima status and, if Colima is not running, colima start,
then tries the ladder again. The repair runs at most once per run.
The adapter needs the supabase command, and colima too when that option is on; foreman doctor checks for both. The migrate-supabase ship step uses the same block to find the
hosted project; see Deploy targets.
The Salesforce pack’s environments
With "pack": "salesforce", two more adapters join these: sandbox-pool, one sandbox per slot
from the salesforce.build list, and scratch, a scratch org made from a Dev Hub for each story
and deleted when it closes. Both take no options of their own; the pack’s block configures them.
The Salesforce pack is a preview: its release paths pass their own tests, but no story has yet
shipped through them on a live org. See Build orgs.
When a slot is down
A slot is down when its environment would not start, or its worktree could not be prepared.
The slot’s card on Waiting on you reads “Slot w2 is down” with the reason, foreman status
shows the slot as down with the same reason, one warn event is logged (“Slot w2 is down: Its
environment would not start: …”) and you get one down email. The other slots carry on. A down
slot keeps its story, so Resume can queue it again.
The reasons you will see:
- “Its environment would not start: <the adapter’s error>” when the adapter answered down, or threw. The error text is redacted and cut to 200 characters, since it goes into the email.
- “Its environment would not start.” when the adapter answered down without saying why.
- “Its worktree could not be prepared: Could not fetch origin/main after 3 tries.” when the
fetch failed; “…Could not create the worktree for slot w2 at <path>.” when
git worktree addfailed; “…The install command (npm ci) failed in slot w2 (<path>).” when the install failed.
The repair ladder runs once per run. When the adapter has a repair (the Supabase adapter with
colima: true) and prepare answers down, Foreman runs the repair and prepares the slot again.
The outcome is one of two events: “The slot environment was down; repaired it.” and the story
starts, or “The slot environment was down and could not be repaired.” A slot that is still down
after a repair pauses the whole run with “The slot environment would not start, even after a
repair.”, because the environment itself has probably gone (a container runtime that stopped),
not just one slot. A later slot that goes down in the same run gets no second repair; it is
put down with its reason.
What to do:
- Read the reason on the slot’s card or in
foreman status. For Docker and Supabase, check the runtime is up (Docker Desktop or Colima) and try the command Foreman ran, in the worktree folder with the slot’s variables set. - Fix the cause. For a failed install, run the
install.runcommand in.foreman/worktrees/wNand read its output. - Press Resume on the slot’s card, or run
foreman slot wN resume. The slot prepares again and takes its story back. If the run paused after a failed repair, press Resume on the pause as well. - If you want the slot kept out of the run for now, press Park instead; Unpark brings it back.
A down slot stays down across a restart of the run, as a parked one stays parked; both wait for you. See When things go wrong for the other ways a run can stall.
Was this page useful? Something missing or wrong?Email us and say which section.