frembench — dev benches
frembench is a Linux machine you rent by the minute and reach from your browser — a dev bench. It carries the same language toolchain your CI runners do, so something that builds on a bench builds in CI.
What it costs
A bench seat is €39 per month and includes 200 hours of runtime. Beyond the pool it is €0,20 per hour, and the total is capped outright — so your bill can never exceed €99 per bench seat in a month, however hard you run them. On a single seat the cap binds at 500 hours of runtime; past that the bench keeps running at no further charge. Every figure here is in EUR, which is what benches are charged in — as is the rest of your subscription.
Hours pool across bench seats, the same way CI minutes do. The allowance is 200 hours × seats, measured against everything your organisation’s benches ran, so two seats share 400 hours: one busy bench draws on a quieter colleague’s allowance rather than going straight into overage. The ceiling pools with it — the cap is €99 per bench seat, not per machine, so a single bench in a two-seat org can account for more than €99 while the org stays inside €198.
Two details worth knowing, because they change what you actually pay:
- Time is measured in seconds and rounded once, at the end of the period. A bench suspends after 30 minutes idle and wakes when you attach, so a working day is many short intervals. Rounding each one up to a minute would bill twenty ten-second wakes as twenty minutes — charging you for our own suspend optimisation.
- 200 hours is far more than a working month of attached time. The pool is sized against wall-clock time a bench is awake, not hours you spend at the keyboard; an agent left running a long job counts, an idle editor does not.
Seats are prorated when you add or remove them, like any other seat. See the pricing page for how it sits beside the rest of the plan.
You do not manage it. It wakes when you attach, and suspends itself when nobody is attached and nothing is running.
What survives, and what does not
This is the one thing worth understanding before you use a bench, because it shapes everything else.
Your bench has two disks. The profile volume, 50 GB, is yours and persists. The boot disk is rebuilt from a fresh image on every wake, which is how you get patched without a maintenance window and without ever being asked to reboot.
$HOME is on the profile volume. So:
| What | Survives a suspend |
|---|---|
Your code, your node_modules, your build output | yes |
| Dotfiles, shell history, git config, SSH keys | yes |
npm i -g, pip --user, uv tool, pipx, cargo install, go install | yes |
| Your Claude Code sign-in | yes |
apt install | no |
apt is the deliberate exception: system packages are what the image is for. If
you need one every morning, it belongs in the image — open an issue rather than
scripting it into your shell profile, and the weekly rebuild stops it being your
problem.
Taking your files with you
Your profile volume outlives a suspend, but not your bench. When an org admin removes your bench seat, the bench is removed with it:
- It stops at once, and you cannot open it again, not in the browser and
not with
fremforge bench attach. - Its profile volume is kept for 30 days. For those 30 days an org admin can undo the removal on the organisation’s Benches admin page. That brings the bench back as it was, asleep, on the same disk, with your seat.
- After 30 days the profile volume is deleted. It cannot be reattached or recovered, and a new bench always starts with an empty profile.
So take what you want to keep while you still have the bench, for example before you leave a project or the organisation. If it has already been removed, ask an org admin to undo the removal within the 30 days, copy your files off as described below, and let them remove it again.
There is no scp, rsync or SSH into a bench: it has no inbound network path,
and fremforge bench attach gives you a terminal, not a file transfer. What you
take with you leaves through frem.sh, in one of the three ways below.
Push your work
Code belongs in git, and the bench already has a git credential for frem.sh. To find every repository under your home directory with something not yet pushed (uncommitted changes, or commits on a branch that is on no remote):
find ~ \( -name node_modules -o -name .cache \) -prune -o -name .git -print 2>/dev/null |
while read -r g; do
r=$(dirname "$g")
if [ -n "$(git -C "$r" status --porcelain)" ] ||
[ -n "$(git -C "$r" log --branches --not --remotes --oneline -1)" ]; then
echo "not pushed: $r"
fi
doneCommit and push each one. If you cannot push to the branch you are on, push to
a branch of your own (git switch -c wip/<topic>, then git push -u origin HEAD). git stash list shows stashes, which a push does not take with it.
Everything else: a private repository
Dotfiles, notes and scratch files that belong to no repository can go into a
private repository of their own. Create one in your organisation from the
frem.sh web interface (New repository, visibility Private), for example
bench-files-<your-login>. If you cannot create repositories in the
organisation, ask an org owner. Then, on the bench:
mkdir ~/bench-files && cd ~/bench-files
cp -a ~/.bashrc ~/.tmux.conf ~/notes . # whatever you want to keep
git init -b bench-files
git add -A
git commit -m "Files from my bench"
git remote add origin https://frem.sh/<org>/bench-files-<your-login>.git
git push -u origin bench-filesThe branch is called bench-files rather than main so that your
organisation’s branch protection, which covers main by default, does not
refuse the push. On your own machine:
git clone -b bench-files https://frem.sh/<org>/bench-files-<your-login>.gitTwo things not to copy:
~/.git-credentialsand~/.config/fremforge/token. They are minted for the bench, revoked when it goes, and work nowhere else.- Secrets of your own, such as SSH private keys or cloud CLI credentials. Everyone with access to the repository can read them, including the organisation’s owners. Create new ones on your next machine instead.
Git is a poor fit for large binary files, so keep the repository to what you actually need.
A single small file: download it in your browser
For one small file there is a shortcut through Forwarded ports. Pack what you want into one file and serve its directory:
mkdir -p ~/outbox
tar -czf ~/outbox/dotfiles.tar.gz -C ~ .bashrc .tmux.conf
cd ~/outbox && python3 -m http.server 8000Port 8000 appears under Forwarded ports. Open it, click the file to download
it, then stop the server with Ctrl-c. A forwarded response is capped at 8 MB,
so this works only for small files; use a repository for anything larger.
Running an agent
Codex and opencode are pre-installed. Both are open-source licensed, so we ship them.
Claude Code is not pre-installed, and the reason is a licence rather than an oversight: its terms are not an open-source licence, and this is a product we sell, so shipping it would be redistributing proprietary software. It is one command, under your own Anthropic agreement:
frembench-install-claude-code
claudeIt installs from the public npm registry through the bench’s egress proxy, like any other package you install on the bench. You only do this once — the install and the sign-in are on your profile volume.
Running more than one thing at once
Your bench terminal is not a bare shell — it attaches to a tmux session
called frembench:
tmux new-session -A -s frembenchThat is what makes an agent survive a closed laptop, and it is also how you run
several at once. The prefix key is the stock Ctrl-b, and the mouse is enabled.
| Keys | What it does |
|---|---|
Ctrl-b c | New window — start a second agent here |
Ctrl-b n / Ctrl-b p | Next / previous window |
Ctrl-b 0 … Ctrl-b 9 | Jump straight to a window |
Ctrl-b w | Pick from a list of windows |
Ctrl-b , | Rename the current window — worth doing, name them per task |
Ctrl-b % | Split into side-by-side panes |
Ctrl-b " | Split into stacked panes |
Ctrl-b z | Zoom one pane to fullscreen, and back |
Ctrl-b d | Detach — everything keeps running |
A second browser tab mirrors; it does not fork. Every attach goes to the
same frembench session, so two tabs show the same window and will fight over
it. Use windows and panes for parallel work, not more tabs.
If you do want two tabs showing different things, give the second one its own view of the same windows:
Ctrl-b : new-session -t frembench -s second
Ctrl-b : switch-client -t second-t frembench groups the new session with the original, so both see the same
window list but each can sit on a different window. A freshly opened tab always
lands on frembench, so you re-issue switch-client per tab.
Two details that surprise people:
- A new window is a plain login shell.
~/.frembench/startupruns only when the session is first created, so it does not re-run per window. - Different-sized tabs do not shrink each other.
aggressive-resizeis on, so a window is sized to the client actually looking at it rather than to the smallest one attached.
Opening a dev server in your browser
A bench has no inbound network path. Nothing on the internet can reach it, and that is not a limitation we plan to remove — it is why the attach surface is defensible.
Dev servers still work. Start one as you normally would:
npm run devWithin a few seconds the port appears under Forwarded ports — a card of its own on the frembench page, below Holds; under the shell on the full-screen terminal page. Click it. The connection travels outbound over the same channel as your terminal; no port is exposed and no tunnel binary is involved.
The card names what is listening as well as where:
| Port | Service |
|---|---|
| 5173 | node |
| 8000 | python3 |
Some things to know:
- Only ports your bench is actually listening on are offered. You cannot ask for an arbitrary port, and neither can anything else holding a link.
- The service column is the process’s own short name, read from the bench —
noderather thanvite, because that is what the process calls itself. It readsUnknownfor a port whose process could not be identified, and on a bench running an image from before this shipped, which picks it up on its next wake. - A server bound only to the bench’s private address is not listed, because
a forward to it would fail. Bind to
localhostor0.0.0.0. - SSH is never offered. Use the terminal.
- Hot reload works. A forwarded page is served from your bench’s own origin, and a WebSocket opened from that origin is carried too — which is what a dev server’s live-reload channel needs. This note previously said it did not work; that was written before the per-bench origin existed and stayed here after it arrived.
This is also how you reach an agent web UI such as OpenChamber. It is installed
on every bench, so there is nothing to set up: start it and open its port. Do not
run openchamber tunnel — that is a Cloudflare tunnel, and the forward above is
both the sovereign path and the one that already authenticates you.
The API credential on a bench
A bench authenticates to frem.sh with two credentials, and knowing which is which saves an afternoon.
| Credential | Where | What it reaches |
|---|---|---|
| Git credential | ~/.git-credentials | git clone / push / pull, and Forgejo’s own API at /api/v1/… |
| API credential | ~/.config/fremforge/token | the fremforge API at /_app/api/v1/… |
Both are minted for you by the platform. You do not create them, paste them or renew them, and you should not replace them with a personal token.
It is minted per wake
The API credential is an ffp_… token minted when your bench starts, valid for
about a day, and revoked when the bench suspends. The next wake mints a fresh
one. That is why it is safe for it to sit on the profile volume: a token read
off a disposed disk has already been revoked.
The practical consequence: a change to what your bench may do takes effect on the next wake, not immediately. If a capability was just enabled for you and the API still refuses, suspend and wake the bench before reporting a bug.
It acts as you, with a narrower reach
The token is bound to your identity, not to an org. Every request is still checked against your real org ownership, so a bench can never reach an organisation you could not reach from your laptop.
What it deliberately does not carry: billing, SSO configuration, seat changes, and the ability to mint further tokens. Those are the scopes whose consequences would outlive the bench being thrown away. Everything else a bench needs day to day — orgs, runners, findings, SBOMs, audit reads, dependency updates, deployment environments and secrets, and starting and downloading a data export — is included.
403 Token lacks required scope '…' from a bench means the endpoint exists and
your bench credential does not carry that scope. It does not mean the
feature is missing. The wording is easy to misread as “no such endpoint” — check
the scope before concluding anything about the API.Org owners can switch the bench API credential off entirely for the whole organisation in admin → Authentication policy. It is on by default.
Using it
curl -fsS https://frem.sh/_app/api/v1/orgs/<your-org>/environments \
-H "Authorization: Bearer $(cat ~/.config/fremforge/token)"Note the /_app prefix. The bare /api/v1/… path is Forgejo’s own API, which
does not accept this token — and its rejection message quotes the token you
sent, so avoid pasting the output of a failed call into a ticket or a chat.
Suspend, wake, and what you are charged for
You are charged for the minutes your bench is awake. A suspended bench costs only its profile volume, so what keeps it awake is worth knowing precisely.
What keeps it awake
Your bench stays up while any of these is true:
- Someone is attached — a browser tab or an SSH session actually connected.
- An agent is working. Not merely running: see below.
- You pinned it, from the bench page.
When none of them holds, the bench suspends after 30 minutes.
What “an agent is working” means
An agent sitting at a prompt is not working, and this is the distinction that decides your bill. A Claude session left open overnight looks busy by every naive measure — it still ticks along using about 1% of a CPU — so we do not use CPU on its own to decide.
An agent counts as working when either is true:
- it has written to its own session history in the last 10 minutes, or
- it or anything it started is using at least 10% of a CPU core.
The second exists for long tool calls. If your agent kicks off a thirty-minute build, it writes nothing for thirty minutes while it waits — but the compiler it started is busy, so the bench stays up.
So a genuinely idle agent stops holding your bench about 10 minutes after its last activity, and the bench suspends about 30 minutes after that: roughly 40 minutes from your last interaction, if nothing else is holding it.
The one case you have to tell us about
A long job you started yourself — not through an agent — does not keep the
bench awake. A make -j, a data import, a soak test: if you start it in a shell
and close your laptop, the bench will suspend under it.
That is deliberate. If any busy process kept a machine awake, an idle bench could be kept billing forever by anything at all, and the cheapest thing to run on somebody else’s machine is a crypto-miner. So the rule is that we only count work we can attribute to an agent, and everything else is your call: pin the bench, and unpin it when the job is done.
Staying attached also works, since an attached client is itself a hold.
Waking is automatic — attach and it comes back. Your files are where you left them, and your terminal layout comes back with them: the windows you had open are recreated in the same directories, and Claude, Codex, opencode and OpenChamber are relaunched — each resumed into the session it was in, waiting at a prompt. Nothing is replayed for you; an agent does not act while you are away.
Two things do not come back. A session started without a resumable id returns as a
fresh one in the right directory, and any other long-running process — your own
dev server, a build — is not restarted. Declare it in
~/.frembench/restore-commands.txt, one command name per line, if you want it to
be.
Pin a bench when you have a long job that our tracking would not see, from the bench page. A pin is not permanent: a bench that has gone too long without a rebuild is patched regardless, because an unpatched machine on the internet is not something a pin should be able to buy.
Getting one
Dev benches are enabled per tenant. If your org has them, the page is under Settings → Dev benches; if not, that page will say so. Your org admin sets how many benches your tenant may run at once.
One bench per organisation
A bench belongs to an organisation, not to you. If you are a member of several organisations that have bench seats, you get one bench in each — separate machines, separate profiles, billed separately to the organisation that granted the seat. You cannot create a bench in an organisation that has not bought seats, so no organisation can be charged for a machine it did not agree to.
Profiles do not follow you between organisations. Each bench has its own profile volume, so the tools, shell configuration and credentials you install on one bench do not exist on a bench in a different organisation. Set up each one you use.
This is a consequence of the tenancy boundary rather than a limitation we plan to remove: a shared profile would mean one organisation paying to store another organisation’s work, and would carry files across a boundary customers rely on.
Benches suspend independently after thirty minutes idle, so holding benches in several organisations does not keep several machines running — only the one you are actually using stays awake.
Why Claude Code is not pre-installed
The bench image ships codex (Apache-2.0) and opencode (MIT) ready to use.
Claude Code is not pre-installed, and that is a licensing decision rather than
an oversight: its licence is not open source, and frembench is a product we
sell. Shipping it inside our image would redistribute it as part of something
we charge for.
Installing it yourself is one command, and it then runs under your Anthropic agreement rather than ours — which is also why it survives a suspend: your profile volume keeps it, so you install it once per bench.
This is the same rule applied everywhere in the product: dependencies we ship are sovereign, current, and fully open-source licensed. Anything that fails that test is something you bring, not something we bundle.