remote agents

Remote agent environments — Claude Code on the web, Codex cloud, CI runners, devcontainers — start from a fresh, ephemeral machine on every session. Nothing interactive survives between runs, so Mossbear is set up from the environment's startup or setup script instead of by hand.

before you start

Both recipes below need one thing from the dashboard: a CLI token, from Settings → Developer → CLI Token. Mint it on the same dashboard the container will talk to — staging and production have separate databases, so a production token is not valid on staging.

set up your environment

Pick your provider. The two differ in where the setup script lives, how credentials are stored, and what the container is allowed to reach on the network — so each recipe stands on its own rather than sending you between tabs.
Claude Code on the web runs each session on a fresh Anthropic-managed VM with your repo already cloned. One environment dialog holds everything Mossbear needs, and the steps below follow it top to bottom — the hosts the container may reach, the variables the CLI reads, then the script that installs it.
1

open your environment settings

At claude.ai/code, select the cloud icon showing the current environment's name to open the selector, then choose Add environment — or hover an existing environment and click the settings icon on the right. The dialog that opens holds every field in the steps below: name, network access, environment variables, and setup script.
2

allow your dashboard host

Network access is the first field under the name, and it is the one people skip. The default Trusted level allows npm, so the install succeeds and the sync is what fails later — a failure that looks like a bad token. Select Custom, then add one host per line under Allowed domains:
app.mossbear.io
Add staging.mossbear.io too if you point sessions at staging. That is the whole list: the CLI talks to your dashboard and nothing else, and grading runs there rather than from this machine, so no LLM provider host is involved. These are domains, not URLs, and * works as a wildcard.
Leave "Also include default list of common package managers" checked. Your entries replace the Trusted list rather than extend it, so unchecking it means npm is no longer reachable and the install in step 4 fails before Mossbear is ever on the machine.
3

add your token as an environment variable

Paste this into Environment variables, the next field down. It takes .env format — one KEY=value per line, and no quotes around values, since quotes are stored as part of the value:
MOSSBEAR_TOKEN=your-cli-token MOSSBEAR_DASHBOARD_URL=https://app.mossbear.io
Only the token is required. MOSSBEAR_DASHBOARD_URL defaults to https://app.mossbear.io, so set it only to point the container somewhere else.
Read this before pasting a token you use elsewhere. Claude Code on the web has no separate secrets store, so the value is visible to anyone who can edit the environment — and, unlike a secret, it is an ordinary variable in the agent's own shell. Claude can read it, and with the dashboard host allowed in step 2 it can call authenticated write endpoints with it, on the prompting of anything in the checked-out repo. CLI tokens are account-wide today, so the containment available is scope of blast radius over time: mint one specifically for this environment (dashboard → Settings → Developer → CLI Token) and revoke it there when you're done, rather than reusing your laptop's.
4

paste the setup script

The last field in the dialog. Setup scripts run as root on Ubuntu 24.04 before Claude Code launches, and their filesystem is cached and reused by later sessions. Four lines:
#!/bin/bash npm install -g mossbear mossbear init --no-prompt --dashboard-url "${MOSSBEAR_DASHBOARD_URL:-https://app.mossbear.io}" || true mossbear sync || true mossbear doctor || true
mossbear init registers the hooks and installs the mossbear-hook binary's wiring, also migrating any .claude/settings.json still naming the logger by an absolute path from another machine. Passing --dashboard-url explicitly matters even when it is the default: a cached filesystem can carry a dashboardUrl from an earlier build, and config outranks the CLI's baked-in default.
Every line after the install ends in || true on purpose. A setup script that exits non-zero stops the session from starting, and a session that can't reach the dashboard is still a working session — just an unconnected one.
5

commit the hooks, and check the sync one is there

The container is destroyed when the session ends, and anything still on disk inside it goes with it — which is why mossbear init writes a Stop hook that pushes the session before that happens. Step 4 has already put it in your repo's .claude/settings.json:
{ "hooks": { "Stop": [ { "hooks": [{ "type": "command", "command": "mossbear sync", "timeout": 180 }] } ] } }
Commit that file. It lives in the repo rather than in the environment, so the same hooks fire on your own machine, and a session resumed from the cached filesystem still has them.
This hook also refreshes any doc bundles the repo subscribes to, so a container that starts cold still gets them. It is safe to run while an agent is working: a file you or the agent edited is reported and left alone rather than overwritten, and the only files it removes are ones Mossbear itself wrote and can still match against what it wrote. Anything else in the folder is left where it is.
Set up before July 2026? Check the command in your settings file. Earlier versions wrote mossbear sync --no-docs, because the docs refresh was not yet safe to run unattended. Re-running mossbear init will not change it — it sees a sync hook already there and leaves your file alone rather than editing config you may have customized. Drop the flag by hand to get doc bundles refreshed at session end; actions upload either way.
A repo set up before this hook existed carries only the removed auden eval Stop hook — the state where actions are logged all session and never uploaded, which looks exactly like a dashboard that isn't receiving anything. Re-run mossbear init: it adds the sync hook and drops the dead eval one, merging rather than overwriting, so your other entries are left alone. mossbear doctor reports the same gap if you'd rather check first.
Track the whole file, every hook in it. Everything mossbear init writes there is machine-independent: the logger runs as the bare command mossbear-hook, resolved from your PATH, so the file is byte-identical on your laptop and in the container and stays that way.
Settings written by older versions named the logger by an absolute path into one machine's home directory, which made a committed file churn wherever it travelled. If your repo still carries one, init migrates it to mossbear-hook the next time it runs and says so in its output; mossbear doctor reports which form a project is on.
Be clear on what each hook carries, because they are not the same job. mossbear sync uploads the actions the logger recorded, and the dashboard grades those server-side — that is what puts a run in your dashboard. It also reads the transcript of the session it is syncing, on your machine, to count the tokens that session spent; the counts sync, the transcript never does. Guide and skill suggestions still come only from a sync run with --transcripts-dir, which this hook does not pass. The SessionEnd hook adds one thing on top: it names the session as finished, so the server closes that run and grades it instead of waiting for its idle sweep.
6

keep the CLI current in resumed sessions

The setup script installs Mossbear once per environment, not once per session. Its filesystem is snapshotted and reused, and a resumed session never re-runs it — so the version installed on the day you built the environment is the version every later session gets, however many releases have shipped since. A SessionStart hook is the part that runs every time, because it lives in your repo rather than in the environment. Add it to the same file as step 5:
{ "hooks": { "SessionStart": [ { "matcher": "startup|resume", "hooks": [ { "type": "command", "command": "[ \"$CLAUDE_CODE_REMOTE\" = true ] && mossbear update --yes || true", "timeout": 600 } ] } ], "Stop": [ { "hooks": [{ "type": "command", "command": "mossbear sync", "timeout": 180 }] } ] } }
mossbear update compares against the dist-tag matching the version you are running, so a prerelease container stays on its own channel, and it installs only a strictly newer release — a private or lagging npm mirror cannot walk it backwards. It also restamps the generated hook script, which a plain npm install leaves running the previous version's code.
The CLAUDE_CODE_REMOTE test is what keeps this from touching your laptop. Hooks are not cloud-only — a committed SessionStart hook runs everywhere — and that variable is set to true only in cloud sessions. Without the test, opening the repo locally would replace your global CLI without asking, which on a global npm prefix you don't own means a sudo prompt mid-session.
matcher matters: SessionStart also fires on /clear and /compact, and swapping the package out underneath a running session can leave a half-installed tree. Listing startup and resume limits the work to the points where nothing of Mossbear's is in flight.
7

start a session and read the setup log

The setup script's output appears in the session log, and mossbear doctor is the part to read. All checks passed plus a healthy dashboard line means the loop is connected. If the connection line reports the dashboard as refused by something between your machine and it — explicitly not a rejected token — step 2 is the fix.
The setup script runs on the first session in an environment and its result is cached; later sessions skip it. Changing the script or the allowed hosts rebuilds the cache, so edit the environment rather than expecting a new session to pick up a change on its own.

mossbear init flags for scripted setups

--no-prompt — skip all interactive questions.
--dashboard-url — point the CLI at a different dashboard instance.
--no-skill / --no-eval-hook — skip installing the Claude Code skill or the session-end evaluation hook if the environment doesn't need them.
--force — overwrite config left over from a previous image layer.

credentials come from the environment

Init needs no token — everything it does is local. To sync verdicts, store a CLI token as a secret in your environment's variables rather than committing it. The CLI reads both of these directly, so no command in either recipe needs a flag:
MOSSBEAR_TOKEN=... # CLI token — required to sync MOSSBEAR_DASHBOARD_URL=https://app.mossbear.io # optional — override the dashboard
A token from the environment is never written to ~/.mossbear/config.json — the environment owns the credential, so revoking it there ends access everywhere it was used. A --token flag still takes precedence and is still remembered after a successful sync; an explicit --dashboard-url likewise overrides the variable.

network access

Many cloud environments route outbound traffic through a proxy that only allows approved hosts, and a blocked host is refused with a 403. Package registries are usually allowed by default, so the install succeeds and the sync is what fails. mossbear doctor tells these apart: a blocked host is reported as refused by something between your machine and the dashboard, explicitly not a rejected token. When you see that, allow these hosts in your environment's network settings:
your dashboard host — sync, guide pull, and the doctor connection check.
your eval provider host — only for LLM-graded checks. Pattern-matched checks need no network at all.

how verdicts leave the machine

Hooks log actions and queue verdicts locally under ~/.mossbear/ inside the container. They reach your dashboard when mossbear sync runs — at session end via a Stop hook, or explicitly as a step in your script. Verdicts that cannot be sent stay queued and go out on the next successful sync:
mossbear sync

evaluation provider

Pattern-matched checks run with no API key at all. For LLM-graded checks the CLI calls your configured provider, which in remote environments is easiest to configure through environment variables:
MOSSBEAR_EVAL_PROVIDER=anthropic # openrouter | anthropic | openai-compatible | google | ollama MOSSBEAR_EVAL_MODEL=claude-sonnet-4-6 ANTHROPIC_API_KEY=sk-ant-... # or the matching provider key
MOSSBEAR_EVAL_API_KEY and MOSSBEAR_EVAL_BASE_URL override the provider-specific variables when set. Provider calls send action metadata and guide text only — never file contents or transcripts.New to Mossbear? Start with getting started for the basics.