Launcher status output is plain when stdout is redirected, including in CI.
Set FORCE_COLOR=1 to request color or NO_COLOR=1 to disable it; NO_COLOR
takes precedence. In-place progress updates require a stdout TTY.
The packaged npx bb-app flow stores persistent package settings under
~/.bb/config.json, provider environment values under ~/.bb/env.json, and
client SSH target mappings under ~/.bb/client.json.
Use bb-app config for non-secret bb settings:
npx bb-app config set BB_APP_URL https://<machine>.<tailnet>.ts.net
npx bb-app config set BB_INFERENCE codex/gpt-5.6-luna
npx bb-app config set BB_INFERENCE_FALLBACK codex/gpt-5.4-mini
npx bb-app config set BB_TRANSCRIPTION codex/gpt-transcribe
npx bb-app config list
npx bb-app config unset BB_APP_URL
npx bb-app config refreshUse bb-app env for provider credentials and provider-specific environment:
npx bb-app env set OPENAI_API_KEY <key>
npx bb-app env list
npx bb-app env unset OPENAI_API_KEYCommit .bb-env-setup.sh when a managed worktree needs repository setup.
Commit .bb-env-teardown.sh when bb must release external resources before it
removes that worktree. See Worktrees, setup scripts, and teardown
scripts for the lifecycle, environment, timeout, and failure
contracts.
bb-app config list shows non-secret values. bb-app env list redacts every
value and only shows whether a key is set.
The Add machine installer may also store a machineCredential and its
connectMachineId beside serverUrl in config.json. The credential is a
secret managed by bb connect: do not copy, edit, or commit it. Both fields are
intentionally omitted from bb-app config list. At runtime bb-app passes
machineCredential to the standalone host daemon through BB_SERVER_HEADERS,
and the daemon and its bundled bb CLI send it to the server as the
x-bb-connect-machine header; connectMachineId is only stored. These are
installer-managed transport details, not user configuration knobs; re-add the
machine instead of setting them by hand.
Use bb-app client ssh-target to let a local helper open files from a remote
bb server in local editors. The SSH target is the value that works after
ssh, such as devbox, user@devbox, or a Host entry from ~/.ssh/config:
npx bb-app client ssh-target set https://bb.example.test devbox --host-id host_abc
npx bb-app client ssh-target list
npx bb-app client ssh-target remove https://bb.example.test --host-id host_abcUse --host-id when the server has more than one machine; copy the ID from
bb machine list. Omit it to preserve the single-machine auto-selection for
set, or to remove every mapping for that server with remove.
Configuration is resolved in this order:
- Explicit launcher flags, such as
--data-dir,--server-port, or--server-bind-host. - Persistent
bb-app config,bb-app env, and client values. - Ambient shell environment.
- Built-in defaults.
For the packaged app, prefer bb-app config, bb-app env, and launcher flags
over shell variables. The environment remains the internal and deployment
substrate, and source-development commands still load .env files.
For source development, pnpm dev automatically injects
BB_DEV_CONNECT_BASE_URL=http://bb.localhost:<worktree-cloud-port>. The
Connect plugin accepts this loopback origin only when NODE_ENV=development
and uses it only as the unpaired default. Explicit bb connect --server ...
or --base-url ... targets take precedence, and packaged/production bb keeps
the https://getbb.app default. This value is launcher-managed, not a
bb-app config setting.
After bb-app config writes ~/.bb/config.json or bb-app env writes
~/.bb/env.json, it asks the running local server to reload. If bb is not
running, the new values apply on the next start. If you edit either file by
hand, run npx bb-app config refresh to apply the files to a running server.
The live reload applies config keys such as BB_APP_URL, BB_INFERENCE,
BB_INFERENCE_FALLBACK, and BB_TRANSCRIPTION, plus env values explicitly
consumed at runtime such as OPENAI_API_KEY. If one of those config keys is
stored with bb-app env instead, it is startup-only; use bb-app config when
you need a live change.
BB_LOG_LEVEL is the startup-only bb-app config key. The complete current
set of startup-only server or launcher env entries is:
BB_APP_SURFACE,BB_APP_URL,BB_DATA_DIR,BB_DEV_APP_PORT, andBB_EXTERNAL_URLBB_HOST_DAEMON_PORT,BB_INFERENCE,BB_INFERENCE_FALLBACK, andBB_INHERITED_SKILLS_ROOTSBB_LOG_LEVEL,BB_MANAGED_DEV_BUILTIN_PLUGIN_HOT_RELOAD,BB_MARKETPLACE_URL,BB_POSTHOG_API_KEY, andBB_TELEMETRYBB_SERVER_BIND_HOST,BB_SERVER_PORT,BB_TRANSCRIPTION, and allBB_FF_*feature flags
Setting or unsetting one still runs the reload for any other pending changes,
but the running processes keep their current values. Apply it with a full
launcher restart (bb-app stop && bb-app start) or by restarting the desktop
app. In particular, changing or unsetting BB_SERVER_BIND_HOST does not close
an existing 0.0.0.0 listener until that restart.
bb-app config refresh also notes any startup-only keys currently present in
config.json or env.json; those values apply on the next full restart.
When targeting a non-default running instance, pass the same --data-dir and
--server-port to bb-app config or bb-app env commands so they write the
right file and refresh the right server.
A running bb-app start writes <dataDir>/bb-app-runtime.json and removes the
file when it exits. The file records the launcher process id, the server URL,
the version, the start time, and how bb was started. Do not edit it.
Two things read that file:
npx bb-app stopstops the bb that owns the data directory. Pass the same--data-diryou started with when it is not the default~/.bb/.- The macOS desktop app asks before it uses a bb it did not start, and offers to stop that copy for you.
Both confirm that the recorded process really is a bb launcher before they signal it, so a stale file left by a crash cannot stop an unrelated process.
| Key | Command | When to set | Used for |
|---|---|---|---|
BB_APP_URL |
bb-app config |
Optional for remote use | Human-facing app URL used for generated links and allowed browser origins. Leave empty for local-only use. |
BB_INFERENCE |
bb-app config |
Optional | Primary server-side helper model in <service>/<model> format, where <service> is an AI service a loaded plugin registers (bb settings ai-services lists them; codex comes with the codex plugin and uses the codex CLI's credentials with no reasoning) or a pi-ai provider the server calls directly with its API key. Defaults to codex/gpt-5.6-luna. |
BB_INFERENCE_FALLBACK |
bb-app config |
Optional | Helper model used after a transient primary timeout, rate limit, or service-unavailable failure. Defaults to codex/gpt-5.4-mini. |
BB_TRANSCRIPTION |
bb-app config |
Optional | Voice transcription model in <service>/<model> format: a plugin-registered AI service (codex with the codex plugin; audio up to 5MB) or openai/<model> with OPENAI_API_KEY. Defaults to codex/gpt-transcribe. |
BB_MARKETPLACE_URL |
bb-app env, or environment |
Startup-only testing | Manifest URL of the reserved bb-community plugin marketplace. It defaults to https://getbb.app/marketplace/v2/marketplace.json. If the default v2 request returns 404, the server requests v1. Set another URL to test catalog refreshes. The server requests that URL without fallback. It changes only bb-community. Add other marketplaces with bb marketplace add. Restart the app after a change. |
BB_SERVER_URL |
bb-app config |
Remote CLI/host use | Server URL for standalone bb CLI and host-daemon commands on the current machine. The CLI defaults to http://127.0.0.1:38886 when unset. |
BB_SERVER_BIND_HOST |
bb-app env, environment, or --server-bind-host |
Startup-only | Server listener host. Defaults to 127.0.0.1; accepts only 127.0.0.1 or 0.0.0.0. A full launcher or desktop app restart is required; until then, a previous 0.0.0.0 listener remains exposed. This is not a bb-app config key. |
BB_SERVER_PORT |
bb-app env, environment, or --server-port |
Startup-only | HTTP listener port. Defaults to 38886. A full launcher or desktop app restart is required after a persistent set or unset. |
BB_HOST_DAEMON_PORT |
bb-app env, environment, or --host-daemon-port |
Startup-only | Local host-daemon API port. Defaults to 38887. A full launcher or desktop app restart is required after a persistent set or unset. |
BB_LOG_LEVEL |
bb-app config |
Startup-only debugging | Log level: trace, debug, info, warn, error, or fatal. A full launcher or desktop app restart is required. |
BB_ACCOUNT_POOL_PARENT_URL |
Set automatically by a parent bb server | Nested bb servers | Account Pooler hub of the bb server whose thread launched this one. When present the Account Pooler plugin is enabled on first run and defaults to proxying to that parent; bb pool parent isolate opts out. Not a bb-app config key. |
BB_ACCOUNT_POOL_PARENT_TOKEN |
Set automatically by a parent bb server | Nested bb servers | Machine token this nested server presents to the parent Account Pooler hub. Paired with BB_ACCOUNT_POOL_PARENT_URL; both must be well formed or proxying stays off. Not a bb-app config key. |
OPENAI_API_KEY |
bb-app env |
OpenAI opt-in routes | Required only when selecting explicit OpenAI provider routes such as openai/gpt-4o-mini or openai/gpt-transcribe. |
The bb CLI records each failed invocation on the machine that ran it, in
<data dir>/logs/cli-errors.jsonl: the time, CLI version, command path, error
code, exit code, current thread ID, and the unknown command or flag. It never
records argument values or error text. The file rotates to cli-errors.jsonl.1
at 2 MB. bb diagnostics cli-errors [--since 7d] [--json] tallies it and
--clear deletes it. Set BB_CLI_ERROR_LOG=0 in the environment that runs bb
to turn recording off.
By default, helper inference and voice transcription use Codex credentials from
the host daemon. Run codex login on the host for the default path. Set
provider env keys only when opting into a non-Codex provider route.
With a ChatGPT subscription login, codex/ voice transcription posts to a
chatgpt.com endpoint that sits behind Cloudflare bot protection. On some
networks Cloudflare challenges that request; bb retries, then reports
"Voice transcription is temporarily unavailable" and logs the Cloudflare
challenge on the server. If that happens often, route transcription through an
API key instead: codex login --with-api-key, or set BB_TRANSCRIPTION to
openai/gpt-transcribe with OPENAI_API_KEY.
The microphone picker in Settings → Voice Input is client-local. It stores the
selected browser MediaDevices device id in localStorage as
bb.voiceInput.audioInputDeviceId; it does not change bb-app config or the
server-side transcription model.
The built-in Push notifications plugin uses expoPushUrl for its relay URL.
The default is https://exp.host/--/api/v2/push/send. Change it with
bb plugin config push-notifications set expoPushUrl <url>. The plugin reads
the value when it sends a message. Independent mobileEnabled, webEnabled,
and desktopEnabled booleans default to true. Change each with
bb plugin config push-notifications set webEnabled false (or the other
channel key). Web and desktop clients receive system notifications while a bb
tab or window remains open; browsers require HTTPS or localhost and per-device
notification permission. Settings → Push notifications offers permission and
test controls. bb push-notifications test <web|desktop> broadcasts a test to
connected, permitted clients; it does not confirm OS display.
The builtin Keep Awake plugin has one autosaving configuration page with an
enable switch and an all-or-selected host picker. On selected macOS hosts it
runs /usr/bin/caffeinate -i -w <worker-pid> while enabled, preventing system
idle sleep while bb is running. It only blocks idle sleep: closing a laptop lid
or choosing Sleep manually still sleeps the Mac. Configure it from an agent or
terminal with:
bb keep-awake status [--json]
bb keep-awake enable [--json]
bb keep-awake disable [--json]
bb keep-awake hosts all
bb keep-awake hosts <host-id>...The builtin Concurrency limit plugin has an autosaving page under Plugins → Installed plugins. Its overall limit is unlimited by default. Each host defaults to Auto: one thread per available processor. A blank host field restores Auto, and 0 pauses new work for that scope. Configure it from an agent or terminal with:
bb concurrency-limit status [--json]
bb concurrency-limit global [unlimited|<limit>] [--json]
bb concurrency-limit host <host-id> [auto|<limit>] [--json]The "Show diagnostic events" toggle in Settings → General → Privacy & diagnostics shows provider
environment resolution and raw provider events that bb does not yet understand.
It defaults to off in all builds. Warnings, errors, and model fallback remain
visible. An existing unhandled-provider-events preference is preserved.
Set it with bb settings general showDiagnosticEvents <true|false> or
bb.sdk.system.updateGeneralSettings using the showDiagnosticEvents field.
For older SDK callers, the general-settings API still accepts and returns
showUnhandledProviderEvents as a deprecated alias. Both names control the same
setting. When a read-modify-write payload contains conflicting values, the
value changed from the saved setting wins. Hidden diagnostics do not count
toward timeline event or byte limits.
The "Default thread followup behavior" picker in Settings → General changes the
active-thread composer shortcuts when no typeahead suggestion is active. A
queued message waits and then runs when the agent stops. A steer message goes
to the agent during the current run. The picker defaults to "Steer" for a new
install: Enter steers and Command+Enter queues. "Queue" swaps them: Enter
queues and Command+Enter steers. Ctrl+Enter is the same modifier shortcut on
Windows and Linux. An earlier install with saved settings or work
keeps "Queue" because a one-time migration stamps the old default onto it. Set
it with
bb settings general steerActiveThreadOnEnter <true|false>, where true is
"Steer".
The "Streamer mode" toggle in Settings → General hides every customModels
entry from ~/.bb/config.json in all model lists: the web and mobile pickers,
bb provider models, and sdk.providers.models. Turn it on before a screen
share so a private or early-access model id does not appear. It defaults to
off. The entries stay in config.json, and a thread that names a hidden model
explicitly still runs with it. Default model resolution for a new thread also
keeps the full list, so a provider whose only models are custom still starts.
A composer whose stored selection is a hidden model treats it as unavailable
and falls back to the provider default; the next send records that default, so
select the custom model again after you turn streamer mode off. Set it with
bb settings general streamerMode <true|false>.
The "New branch prefix" field in Settings → General sets the text bb
puts in front of every branch name it creates for a managed worktree or a new
checkout branch. It defaults to bb/, which produces
bb/fix-login-flow-thr_ab12cd34ef. Change it to sawyer/ to group your branches
under your own namespace, or clear the field to create
fix-login-flow-thr_ab12cd34ef with no prefix. bb rejects a prefix that cannot
start a valid git branch name, such as one with a space or a leading -, and
the prefix is at most 64 characters. The prefix applies to branches bb creates
after you change it; it does not rename an existing branch or worktree. Set it
with bb settings general managedBranchPrefix <prefix>.
Settings → Providers lists every registered agent provider in picker order.
Move a provider up or down to change the order and choose the default for new
threads. Both are persisted preferences: providerOrder is the list of ids
that lead the picker (ids not listed follow in plugin install order, and an id
that names no registered provider is ignored) and defaultProviderId is the
provider new threads use when neither the caller nor the project chose one
(null means the first available provider in picker order). Set them with
bb settings general providerOrder '["claude-code","codex"]' and
bb settings general defaultProviderId claude-code (or null).
The "Collapse finished turns" switches in Settings → Providers choose, per
provider, how a finished turn appears in the thread timeline. Collapsed, the
turn's work folds into one "Worked for" row and the final answer stays
visible. Flat, every step of the finished turn stays visible, as it was while
the turn ran. Each provider declares its default (completedTurnDisplay on
its registration): Claude Code defaults to flat, and every other first-party
provider defaults to collapsed. Your choice is stored in
providerCompletedTurnDisplay, a map of provider id to collapse or flat;
a provider without an entry uses its default. The display applies to existing
threads as well as new ones, and to the conversation outline and
bb thread log. Set it with
bb settings completed-turns <provider-id> <collapse|flat|default>, where
default removes your entry, and list every provider's current display with
bb settings completed-turns.
Each provider's own options live on its plugin: Codex memory and native subagents under the Codex provider plugin, and Claude Code memory, native subagents, and the Workflow tool under the Claude Code provider plugin.
Claude Code starts without its Claude in Chrome browser tools when bb runs it,
even when the interactive claude CLI has Chrome enabled by default. Turn the
tools on for bb threads with
bb plugin config provider-claude-code set chromeEnabled true. bb then starts
Claude Code with --chrome. The host needs the Claude in Chrome extension and a
claude.ai login; API-key sessions keep Chrome off. A change restarts the thread's
Claude process before its next turn and keeps the conversation.
Outside an open typeahead menu, Shift+Enter inserts a newline. On coarse-pointer touch devices, the software-keyboard Return path inserts a newline and the submit button sends. iPadOS WebKit additionally preserves the Enter and Command+Enter shortcuts above for a connected Magic Keyboard.
bb theme controls CSS-variable overrides for the app palette and typography.
Custom themes live at <bb-data-dir>/theme/<name>/theme.css; use bb theme dir
to find the directory and bb theme show [id] --css to inspect resolved CSS.
The typography tokens are mode-independent and belong in the :root, .light
block:
--font-sanscontrols app UI and body text.--font-monocontrols code blocks, diffs, file paths, and previews.--font-serifcontrols serif prose.--font-terminalcontrols the integrated terminal's font family.
Always end font stacks with a generic fallback such as sans-serif or
monospace. The complete theme token reference is in the bb-cli skill's
references/theming.md.
Mod+Shift+P opens the quick palette: type to filter, then run a command with
Enter. It lists only commands that apply on the current surface, shows each
one's shortcut, and offers recently run commands first. The numbered
accelerator families and the relative cycle commands stay rebindable but
unlisted. Plugins can add their own rows, listed under "Plugins".
Settings → Keyboard edits app command shortcuts. Overrides are stored in the server database, applied live to every connected window, and kept across restarts. Resetting a shortcut removes its override so future bb releases can continue to update the default. Clearing a shortcut explicitly disables that command. Command context and native-only availability remain server-owned and are not editable. Actions supported by both clients use the same resolved bindings in the browser and desktop app; browsers may still reserve some chords before bb receives them.
Mod means Command on macOS and Control on Windows/Linux. Numbered thread and
pane shortcuts follow Slack's browser-safe convention: web uses
Control+1…9 on macOS and Ctrl+Shift+1…9 on Windows/Linux, while desktop
uses Mod+1…9. The web aliases leave native browser Mod+1…9 tab switching
untouched. Previous and next thread use Mod+Shift+[/] on desktop and
Control+Shift+[/] on the web.
On macOS, right-panel tabs use panel.previousTab / panel.nextTab with
Command+Control+ArrowLeft / Command+Control+ArrowRight. They wrap through visible
tabs and each pane's New tab button in displayed order across the active
chat's right-panel groups. Press Enter or Space on New tab to open the picker.
On the selected New tab page, panel.previousNewTabItem /
panel.nextNewTabItem use Command+Control+ArrowUp / Command+Control+ArrowDown to
move through search, enabled actions, and recent items in displayed order.
Search results replace actions and recents while searching. Enter activates
the focused item.
Chat splits use pane.focus.left / right / up / down with
Command+Shift+ArrowLeft / ArrowRight / ArrowUp / ArrowDown on macOS. These move
spatially to the adjacent chat pane, including stacked splits, and stop at the
layout edge. The initially unassigned pane.focus.previous / pane.focus.next
commands still cycle in reading order. On Windows/Linux, these arrow navigation
commands start unassigned to preserve native Control-arrow editing shortcuts.
Rebind any of these commands in Settings → Keyboard, via
bb settings keyboard set <command> <shortcut|disabled>, or SDK
system.updateKeyboardSettings; read bindings with system.config.
Plugin commands use plugin:<plugin-id>/<command-id> as their stable binding
ID. For example: bb settings keyboard set plugin:example/open-issue Mod+Shift+I.
bb settings keyboard reset plugin:example/open-issue restores the plugin's
default; set ... disabled explicitly unbinds it. The SDK supports the same IDs
through system.updateKeyboardSettings and system.config.
Overrides survive plugin disable/re-enable and reload. Every active plugin
command appears in Keyboard Settings; commands without defaults start unbound.
Conflicting plugin defaults stay unbound and display the conflicting command.
The UI offers Replace binding or Cancel when assigning an occupied shortcut.
keyboard list includes all saved overrides and core effective bindings;
plugin defaults and availability are resolved in each app window, where the
plugin frontend runs. CLI/SDK callers should clear conflicting explicit
bindings in the same update; plugin defaults yield to explicit bindings.
The "Show keyboard hints when holding CMD / Control" preference defaults
to on. Set it with
bb settings keyboard hints <true|false>. Turning it off hides the
delayed shortcut badges without disabling any shortcuts.
| Area | Command | Default | Availability |
|---|---|---|---|
| Palette | Quick palette | Mod+Shift+P |
All clients |
| Threads | New thread | Mod+N / Mod+Shift+O |
Desktop / web |
| Threads | Search threads | Mod+K |
All clients |
| Threads | Rename focused thread | Unassigned | Thread view |
| Threads | Archive focused thread | Unassigned | Thread view |
| Threads | Previous / next thread | Surface defaults above | Desktop / web |
| Threads | Open visible thread 1–9 | Platform defaults above | Web / desktop |
| Layout | Previous / next chat pane | Unassigned | While split |
| Layout | Focus chat pane 1–8 | Platform defaults above | Split (web / desktop) |
| Layout | Maximize / restore chat pane | Mod+Shift+E |
While split |
| Layout | Close focused chat pane | Mod+Shift+X |
While split |
| Window | New window | Mod+Shift+N |
Desktop |
| Window | Settings | Mod+, |
All clients |
| Layout | Toggle sidebar | Mod+\ |
All clients |
| Panel | New tab / close tab / toggle | Mod+T / Mod+W / Mod+J |
All clients |
| Workspace | Quick open file / toggle diff | Mod+P / Mod+D |
All clients |
| Workspace | Open terminal | Mod+Shift+Enter / Mod+Shift+T |
Web / desktop |
| Workspace | Open in preferred app | Mod+O |
All clients |
| Composer | Focus composer | Mod+Shift+C |
All clients |
| Composer | Toggle model picker | Mod+Shift+M |
All clients |
| Composer | Cycle model forward / backward | Alt+M / Alt+Shift+M |
All clients |
| Composer | Cycle provider forward / backward | Alt+P / Alt+Shift+P |
All clients |
| Composer | Cycle reasoning effort forward / backward | Alt+T / Alt+Shift+T |
All clients |
| Browser | Focus location / reload / find in page | Mod+L / Mod+R / Mod+F |
Desktop embedded browser |
| Questions | Choose visible answer 1–9 | 1 … 9 |
While a question is open |
Cycle commands wrap in both directions. Reasoning cycles only through the current model's supported efforts in canonical low-to-high rank order, not the provider response order. The cycle shortcuts act only from the active composer or an open picker; unrelated editable controls retain their Option-composed character input. A configured app shortcut takes precedence in editable controls; when no matching command handles a chord, the control retains its native behavior.
The desktop application menu uses the same resolved bindings for New Thread, New Window, New Tab, Close, and Settings. There is no separate menu shortcut configuration.
BB_SERVER_URL does not change where full npx bb-app startup binds locally.
It is for commands that need to target an already-running server, such as the
bundled bb CLI or a standalone host daemon. The CLI can omit it when targeting
the default local packaged server at http://127.0.0.1:38886; set it for remote
or non-default servers.
~/.bb/client.json is local to the machine showing the UI. The CLI resolves the
remote server's host ID and stores a mapping from that server/work-host to an SSH
target known to the local machine. The remote server does not read this file.
Example:
{
"servers": {
"https://bb.example.test": {
"hosts": {
"host_abc": {
"sshAuthority": "devbox"
}
}
}
}
}When a remote bb page asks the local helper to open a work-host path, the helper uses this mapping to launch remote-capable editors and terminals over SSH. Browsers or devices without a helper can still use bb; local editor actions are simply unavailable.
Known ACP agents appear when their CLI is installed on the host. bb exposes
acp-opencode when opencode is on PATH and can be launched as opencode acp,
acp-omp when omp (oh-my-pi) is on PATH, acp-grok when Grok Build's grok
CLI is on PATH and can be launched as grok agent stdio, and
acp-hermes-agent when Hermes' hermes CLI is on PATH. acp-cursor is always
listed.
Add your own agent through the ACP providers plugin's customAgents setting,
which holds a JSON array. In the app it is the multi-line editor on the
plugin's settings page (Settings → Plugins → ACP providers); from the CLI:
bb plugin config provider-acp set customAgents '[
{"id": "amp", "displayName": "Amp", "command": "amp", "args": ["acp"]}
]'Each entry needs id (lowercase letters, digits and dashes), displayName,
and command. bb derives the provider id acp-<id>; it never changes once a
thread has used it. An id bb always lists (cursor) is reserved; an id bb
lists only where the agent is installed (opencode, omp, grok,
hermes-agent) is not, so an entry with that id REPLACES the shipped agent.
A replacing entry keeps the shipped agent's nativeSkillRoots unless it sets
its own, and bb still lists the roots that agent's host config names (its
config directory, compat trees, configured paths, plugins) either way.
Optional fields: args, env, cwd, modelCli (CLI model listing and
selection), reasoningCli (launch-time reasoning flags), nativeReasoning
(ACP session/set_config_option reasoning), nativeSkillRoots (native skills
in the composer, as {"user": [...], "project": [...]} relative paths; an
entry is a path or {"path": ..., "recursive": true, "ancestors": true} for
an agent that nests skills or reads them from every ancestor directory),
permissionCli (permission-mode launch flags), supportsManualCompaction
(only if the agent accepts an explicit compaction request — bb hides
/compact otherwise), and dialect (the vendor side channels bb reads for
the agent: cursor, opencode, omp, or grok).
The change applies immediately: the plugin re-registers its providers when the
setting changes, with no restart and no config refresh.
A configured agent's command is local code execution and only works with a co-located daemon.
Before ACP agents were plugin-owned, custom agents lived in customAcpAgents
in ~/.bb/config.json. bb still reads that array so an existing agent keeps
working, logs a deprecation warning for each one, and never writes to it.
Support ends in 0.41 — move each entry into the customAgents setting above.
The two shapes are identical except that the setting has no logo field: a
plugin-registered provider's icon is a host glyph or an asset the plugin ships,
so a configured agent shows the generic tool glyph, and bb drops the field when
it reads the old array. A setting entry wins over a config entry with the same
id.
Register extra picker models by editing top-level customModels in
~/.bb/config.json. Use this for a model the provider accepts but does not
list, such as a non-public preview id. This list has no set/unset CLI surface:
edit the JSON, then run npx bb-app config refresh or restart bb. bb-app config list prints the entries.
{
"customModels": [
{ "providerId": "claude-code", "model": "claude-example-preview" },
{
"providerId": "acp-my-agent",
"model": "my-proxy/my-model",
"displayName": "My Proxy Model"
}
]
}providerId accepts a built-in provider id (codex, claude-code, pi,
acp-cursor) or any acp-* provider id: an installed-only plugin provider
such as acp-opencode, or a custom ACP agent's derived acp-<id>. displayName is
optional; bb derives the label from the model id when it is omitted. bb skips
an invalid entry with a warning and keeps the rest of the config.
Each entry appears in bb provider models <providerId> and in the model
picker after the provider's own catalog. The provider catalog wins on a model
id collision. The "Streamer mode" General setting
(bb settings general streamerMode true) hides every entry from these lists
until you turn it off again.
A customModels entry only makes the id selectable; the provider must still
accept it. Built-in providers such as claude-code and codex accept
unlisted ids. An ACP agent receives the id over the protocol at session start
and can reject it. OpenCode rejects a model that is not in its own catalog,
so do not pin OpenCode models here: add the model to the OpenCode config and
bb discovers it automatically.
An OpenCode "agent" (build, plan, or a custom primary agent) is a session
mode, not a model, so it does not belong in customModels. bb does not select
OpenCode agents; set the default agent in the OpenCode config instead.
bb can inject user-level and workspace-level agent instructions into every provider-backed thread's system prompt, alongside the skills convention.
For user-level defaults across projects, create AGENTS.md in the bb data dir:
<dataDir>/AGENTS.md
For repo-specific guidance, create .bb/AGENTS.md at the workspace root:
<workspace>/.bb/AGENTS.md
The file contents are appended alongside enabled plugin instructions when a
provider session starts, so the guidance applies regardless of which provider
runs. When both files exist, <dataDir>/AGENTS.md is appended first and
<workspace>/.bb/AGENTS.md second. An empty or whitespace-only file is treated
as absent.
No agent loads .bb/AGENTS.md natively, and provider-native instruction files
(CLAUDE.md for Claude Code, a repo-root AGENTS.md for Codex) remain
provider-specific. bb reads the files above itself and injects them, so use them
for guidance you want every bb thread to receive regardless of provider.
User-level bb skills live under <dataDir>/skills/<name>/SKILL.md; for the
packaged app this is usually ~/.bb/skills. Project skills live under
<workspace>/.bb/skills/<name>/SKILL.md and override same-named user or plugin
skills. Running plugins contribute another tier: skills/<name>/SKILL.md
files in an installed plugin (relocatable via the manifest's bb.skills field)
are imported while the plugin is loaded, subject to its agent configuration.
Project and user skills override plugin skills by name. BB guide owns the
four bundled core skills and can disable them together or individually.
bb indexes each provider's native skill roots for that provider's / command
menu. Each provider plugin declares where its agent keeps skills and slash
commands, and resolves on the host what only that machine and workspace know
(a moved config directory, installed vendor plugins, config-file entries); bb
itself knows no agent's layout. The Skills page and bb skill list show
native skills for every provider whose plugin declares or resolves roots. The
table lists what the shipped plugins declare and resolve.
| Provider | User roots | Project roots |
|---|---|---|
| Codex | ~/.agents/skills, $CODEX_HOME/skills |
.agents/skills from the repository root to the current directory, plus .codex/skills |
| Claude Code | $CLAUDE_CONFIG_DIR/skills or ~/.claude/skills, plus enabled plugin skills |
.claude/skills from the repository root to the current directory, plus enabled plugin skills |
| Pi | ~/.pi/agent/skills, ~/.agents/skills |
.pi/skills and .agents/skills from the repository root to the current directory |
| Cursor | ~/.cursor/skills, ~/.agents/skills, ~/.claude/skills, ~/.codex/skills |
The same four roots in the workspace |
| OpenCode | ~/.config/opencode/skills, ~/.claude/skills, ~/.agents/skills |
.opencode/skills, .claude/skills, and .agents/skills from the repository root to the current directory |
| omp | The active ~/.omp/.../agent roots and supported Pi, Agents, Claude, Codex, and OpenCode roots |
.omp/skills and the supported compatibility roots from the repository root to the current directory |
| Grok Build | $GROK_HOME/skills or ~/.grok/skills, plus ~/.agents/skills, ~/.claude/skills, ~/.cursor/skills |
The same four roots from the repository root to the current directory |
| Hermes Agent | $HERMES_HOME/skills or ~/.hermes/skills |
None |
OpenCode also uses $OPENCODE_CONFIG_DIR/skills when that variable exists.
Pi and omp use $PI_CODING_AGENT_DIR when that variable exists. omp also uses
$OMP_PROFILE or $PI_PROFILE to select its active profile root. Cursor and
Hermes can organize skills in category directories. bb scans those roots
recursively. Pi settings and packages can add skill paths. omp reads
skills.customDirectories from its YAML configuration. Hermes reads
skills.external_dirs from config.yaml.
Grok reads recursive paths from [skills].paths in config.toml. It also reads
enabled Grok and Claude-compatible plugin skills. Its Cursor and Claude
compatibility roots follow the related config and environment switches.
Settings → Machines offers Manual machine setup (the built-in manual provider)
alongside installed cloud, SSH and Tailscale providers. Manual machine setup prints
a private enrollment command and waits for the daemon. The one-line command
downloads /install.sh with a short-lived enrollment header. The server embeds
the pending bootstrap in its uncached response; cancelled, expired, or consumed
enrollments are rejected. bb machine create --provider manual follows the same lifecycle; --no-wait returns the creating
host ID. Manual machines never suspend or retire automatically. Removal
revokes access; run the original installer with --uninstall --host-id <id> on
that machine to uninstall its daemon. The local host remains provider-less.
Settings → Machines can also rename and remove machines; project settings can add a path or clone source on
each machine; and thread creation can target any enrolled machine with a usable
source. The CLI equivalents are bb machine list, bb project create --machine <id-or-name> ..., bb project source add --machine <id-or-name> ..., and bb thread spawn --machine <id-or-name> ....
Multi-machine execution is independent of browser access. Tailscale and bb connect let another browser reach the bb server; multi-machine support lets that server dispatch work to host daemons on other machines. The Settings → Machines installer can use a paired bb connect account to route the daemon and its CLI back to the server. Machine credentials remain locally managed as described at the top of this document.
Each machine has a permission ceiling (maxPermissionMode, default full).
The server resolves every thread on that machine down to the ceiling, so a
paired sandbox machine can keep Full Access while a personal machine stays at
Approve for me or Accept Edits. A provider that supports no mode under the
ceiling is refused on that machine. Only an owner session sets it, on the machine
page (Settings → Machines → the machine, which also carries that machine's
projects, provider CLIs, update state, and rename/remove); it is deliberately
absent from the SDK and the bb CLI,
and machine credentials are rejected at both the bb connect gate and the
server. The boundary it defends is machine-to-machine: a process already
running on the server machine has the data directory and the server itself, so
it is trusted as the owner here exactly as it is for renaming or removing a
machine. The current value is readable through the host API and
bb machine list --json.
Machine installation and daemon protocol repair use the owning server as the
distribution source: /install/version reports the server package/protocol and
/install/bb-app.tgz serves its exact host-only package with a SHA-256 digest
and strong ETag. That package contains the daemon, its workers and native
dependencies, and the bundled bb CLI; it omits the server and web app. The
installer verifies the digest and skips the download and npm install when its
recorded installed digest receives 304 Not Modified. It falls back to the npm
registry only when the package route returns 404. It installs the package under
the machine's bb data directory rather than npm's system-wide prefix, so
enrollment needs neither sudo nor a global npm configuration.
Installed services enable --auto-update; remove that flag from the launchd
plist or systemd user unit and reload the service to opt out. Updates only move
to a newer server protocol, retry failures with a persisted exponential backoff
from 5 seconds to 5 minutes, and never downgrade a daemon. Settings → Machines
and bb machine retry-update <id-or-name> can bypass the current backoff after
a transient failure.
Sidebar layout preferences are stored on the server in a keyed registry so
every window, device, and the CLI read the same value. Each key has a typed
schema, a default, and a revision that increments on every write. Writes name
the revision they expect and receive 409 ui_preference_conflict when another
client wrote first, so a stale window cannot silently clobber a newer value.
| Key | Value |
|---|---|
sidebar.organizationMode |
project, chronological, or machine |
sidebar.threadGrouping.environment |
auto, true, or false |
sidebar.chronologicalSort |
updated, created, alpha, or none |
sidebar.sectionOrder |
Section id list for By project |
sidebar.manualSectionOrder |
Section id list for Manually |
sidebar.machineSectionOrder |
Section id list for By machine |
sidebar.hiddenGroups |
Project, custom section, and machine ids moved into More |
sidebar.collapsedSections |
Collapsed built-in sections (pinned, threads) |
sidebar.collapsedProjects |
Collapsed project ids |
sidebar.collapsedThreads |
Thread ids whose children are collapsed |
sidebar.collapsedEnvironments |
Collapsed environment ids |
sidebar.collapsedThreadSections |
Collapsed thread section ids |
sidebar.collapsedMachines |
Collapsed machine ids |
sidebar.footerOrder |
Footer action order |
sidebar.hiddenFooterItems |
Footer actions moved into More |
sidebar.pluginPanelOrder |
Navigation entry order |
sidebar.visiblePluginPanels |
Navigation entries shown, or null for every entry |
sidebar.navigationProvider |
Plugin key, __automatic__, or __builtin__ |
sidebar.threadListProvider |
Plugin key, __automatic__, or __builtin__ |
New installations default to Custom (chronological) for sidebar.organizationMode.
Migrated installations with existing projects, threads, or UI preferences fall back
to By project (project). Explicit server choices take precedence over legacy
browser choices, which take precedence over this installation fallback. Reset
saves the installation fallback as an explicit choice.
sidebar.threadGrouping.environment decides whether two or more sibling threads
that share one worktree environment collapse into a single worktree row inside
their section. true groups them and false keeps every thread on its own row,
in every organization mode. The default, auto, groups them in By project
and By machine and leaves them flat in Custom, which is how each mode
behaved before the preference existed. The thread-list header's Organize menu
exposes it under Groups as the By environment toggle, which writes true or
false and so applies to every mode once you use it.
Each sidebar.threadGrouping.* key toggles one grouping dimension
independently, so a future dimension adds a key rather than changing this one.
Read and write them with:
bb settings ui list [--json]
bb settings ui get <key> [--json]
bb settings ui set <key> <value> [--json]
bb settings ui reset <key> [--json]set takes a plain string for enum and provider keys and JSON for lists and
null, for example bb settings ui set sidebar.organizationMode machine or
bb settings ui set sidebar.sectionOrder '["threads","pinned","projects"]'.
It reads the current revision first and retries once on a conflict. reset
writes the default and advances the revision. The SDK exposes the same
operations as sdk.system.uiPreferences.list(), .set({ key, value, expectedRevision }), and .reset({ key }) over GET /preferences/ui,
PUT /preferences/ui/:key, and DELETE /preferences/ui/:key. Every write
broadcasts a ui-preferences-changed system change to connected clients.
The sidebar waits for these values alongside the project list, so it never
paints a default layout that then snaps to the saved one. The first client to
reach a server that has never stored a key uploads the value it finds in the
old browser storage once, then deletes that copy, so an existing layout
survives the upgrade; a second device that loses that race adopts the server
value. A change on one device reaches every other connected window through the
ui-preferences-changed broadcast without a reload.
Sidebar width and open state stay in the browser because they depend on the window size.
Choose Hide from list in a project, custom section, or machine's menu to move it into More. Its menu in More offers Add to sidebar to restore it. Customize list manages visibility and order for the current organization. Hiding a group preserves its threads, saved order, and collapse state; pinned threads stay in Pinned. Hidden work remains reachable through More, search, and direct links. More shows activity without automatically restoring hidden groups.
sidebar.hiddenGroups defaults to [] and accepts project:<projectId>,
section:<sectionId>, and machine:<hostId> keys (machine:no-machine for the
unassigned machine group). Each organization uses only its matching keys.
Built-in Pinned and Threads sections cannot be hidden. Duplicate keys are
deduplicated; unavailable IDs are retained without creating sidebar rows, and
new groups default to visible.
bb settings ui get sidebar.hiddenGroups
bb settings ui set sidebar.hiddenGroups '["project:proj_example","section:sec_example"]'
bb settings ui reset sidebar.hiddenGroupsset replaces the complete list across organizations, so include any existing
keys you want to keep hidden. reset restores the default empty list and shows
every group. SDK callers use sdk.system.uiPreferences.list() for the current
value and revision, .set({ key: "sidebar.hiddenGroups", value, expectedRevision })
to replace the list, and .reset({ key: "sidebar.hiddenGroups" }) to show all.
Settings → Appearance → Sidebar footer lets users reorder and hide built-in and registered plugin actions. Right-click an action and choose Hide to move it into More. More appears only when registered actions are hidden; they remain usable. Hiding an open disclosure closes it; selecting it from More opens it again.
The UI preferences sidebar.footerOrder and sidebar.hiddenFooterItems contain
stable IDs: builtin:settings, builtin:report-bug, and
plugin:<encoded pluginId>/<encoded registrationId> (URI-encoded components).
Unknown and disabled-plugin IDs are retained across reloads; new actions default
visible. The existing SDK UI preferences and CLI manage the same values:
bb settings ui set sidebar.hiddenFooterItems '["builtin:report-bug"]'
bb settings ui set sidebar.footerOrder '["builtin:report-bug","builtin:settings"]'
bb settings ui reset sidebar.hiddenFooterItemsThread splits enable up to eight panes in the app's multi-pane thread view and
its sidebar, menu, and keyboard split controls. Edge placement creates panes
through the eighth pane; at the eight-pane limit, opening a new thread with an
edge placement replaces the focused pane. Every pane header can temporarily
maximize that pane without unmounting or resizing the underlying split tree;
the same control restores the exact arrangement. Maximization follows focus and
newly opened panes, closing the maximized pane restores the surviving layout,
and both the split tree and maximized pane restore after reload. Compact
viewports show the ordinary single-page surface while preserving that desktop
layout state.
It also enables explicit split placement through
bb thread open <thread-id> --split right|down|left|top|replace and the matching
SDK request, plus pane presentation controls through
bb thread pane maximize|restore|toggle|spotlight|clear-spotlight [thread-id] and
sdk.threads.paneAction({ threadId, action }). Pane actions apply only when the
target thread is already open in a multi-pane app window; the response reports
how many connected clients received the broadcast. spotlight focuses the
target pane and persistently dims the others; clear-spotlight focuses it and
persistently restores undimmed splits.
The builtin Account Pooler plugin is disabled on fresh installations. It stores
non-secret Claude and Codex account metadata in plugin KV, quota observations
in the plugin SQLite database, and each account token plus per-machine hub
tokens in 0600 files under
<data-dir>/plugins/account-pool/secrets/accounts/.
Enable it and add at least one account:
bb plugin enable account-pool
bb pool account add --provider claude --login
printf '%s\n' "$CLAUDE_AUTH_CODE" | bb pool account login-complete --session <id> --code-stdin
bb pool account add --provider codex --login
bb pool account login-poll --session <id>
bb pool account add --provider claude --import
bb pool account add --provider codex --import
printf '%s\n' "$ANTHROPIC_API_KEY" | bb pool account add --provider claude --api-key-stdin [--label <text>] [--priority <n>]
bb pool account refresh <id>The Claude login start command creates a ten-minute in-memory PKCE session,
prints a browser authorization URL and session ID, then exits. After sign-in,
pipe the code shown on Anthropic's manual callback page to
account login-complete with that session ID. The browser can be on a different
machine from the bb server, and the code stays out of process arguments. The
Codex login command prints a ChatGPT device verification URL, one-time code,
session ID, and an account login-poll command that waits until authorization
completes or expires. The Account Pooler plugin settings page exposes both flows
with Sign in to Claude and Sign in to Codex, plus Claude import,
API-key, enable/disable, and removal controls.
The CLI import paths read the Claude Code or Codex login on the bb server host.
--api-key-stdin reads exactly one non-empty key from piped standard input and
is the default API-key path for agents. The compatibility form --api-key <key> remains available, but exposes the secret in process arguments, shell
history, and agent transcripts. The hub starts immediately, so a newly added
or enabled account is available without a plugin reload.
When the plugin has an enabled account whose secret file is readable and
valid, it automatically contributes the provider's hub route and a
machine-specific secret token to Claude Code or Codex sessions on every host.
Claude Code also receives ENABLE_TOOL_SEARCH=true.
Codex receives CODEX_OPENAI_BASE_URL and the secret
CODEX_POOL_AUTH_TOKEN; bb applies both when launching codex app-server
without writing to ~/.codex/config.toml.
Codex image generation and editing use the same authenticated pool route.
Claude Code disables tool search behind a custom base URL by default; the hub
forwards tool_reference blocks unchanged, so the override keeps it on.
Tokens are never printed
by the CLI. Plugin startup and bb pool status remove token files for machines
that are no longer enrolled. Status lists token mint and last-use timestamps
plus recently routed threads whose machines do not have a usable local Claude
login. Rotate one machine's token with
bb pool token rotate --machine <id-or-name>; the prior token remains valid
for ten minutes so in-flight requests can drain. Bypass or restore routing for
one thread with bb pool bypass <thread-id> or
bb pool bypass <thread-id> --off. Account listing, enable, disable, removal,
priority changes, and usage refreshes are available through
bb pool account list|enable|disable|remove|priority|refresh.
Provider routing is independently persisted and defaults on. Use
bb pool routing <claude|codex> --off to stop contributing pool environment
and health for one provider, and omit --off to enable it again.
OAuth accounts refresh quota from Anthropic's usage endpoint when added or
enabled and every five minutes while idle. Use bb pool account refresh <id>
to request an immediate refresh for one account. account list adds columns
for the family buckets Anthropic reports; JSON status exposes their
utilization, reset, status, observation time, and header or usage source
under familyWeekly. Requests route around an account spent for their model family
without disabling that account for other families. Imported and newly signed-in
accounts retain their Anthropic account UUID, and the hub aligns a present
metadata.user_id account component with the selected account.
Accounts run sequentially per provider: lower priority numbers first, with ties following the order accounts were added. New conversations use the current account until it reaches the switch threshold or fails; the pool then advances to the next eligible account and wraps at the end. It keeps using that fallback even when an earlier account recovers. Existing conversations stay pinned while their account remains eligible. Short temporary rate limits wait on the same account once; longer holds return Retry-After for pinned conversations while new conversations can advance. A model-family limit detours only requests for that family without moving the session's main pin or the provider cursor. The cursor and session pins survive hub restarts. Session pins expire after 30 idle minutes, and the pool retains the 4,096 most recently used pins.
Use the up/down arrows in Account Pooler settings, or
bb pool account reorder <claude|codex> <id>..., to set the complete order for
one provider. Include disabled accounts too. Reordering changes the next failover
sequence without moving the current account. bb pool account priority <id> <n>
sets an individual priority; the same operations are available through the
account.reorder and account.setPriority plugin RPCs.
Three plugin-owned configuration values control routing. switchThreshold is
the shared or requested model-family quota fraction at which an account stops
receiving matching traffic and defaults to 0.98.
anthropicUpstreamBaseUrl defaults to https://api.anthropic.com and
codexUpstreamBaseUrl defaults to
https://chatgpt.com/backend-api/codex. Codex uses the hub's HTTP Responses
and models routes; the hub forwards each request upstream over HTTPS SSE
without keeping session state. Both URL values exist only for tests and QA
with a controlled fake upstream. Inspect or update the full plugin KV-backed configuration with:
bb pool config
bb pool config set switchThreshold 0.98
bb pool config set anthropicUpstreamBaseUrl http://127.0.0.1:9000
bb pool config set codexUpstreamBaseUrl http://127.0.0.1:9001Upgrading from an Account Pooler build that stored these values through
bb.settings resets the threshold and both QA-only upstream overrides to
their defaults. Those old values are not migrated.
bb connect --code <code> --server https://<handle>.getbb.app pairs this bb
server for browser access at <handle>.getbb.app (claim a handle and copy the
command at https://getbb.app). Remote access is owned by the builtin
connect plugin (plugins/connect/): pairing redeems the code and stores
the durable credential in the plugin's kv storage (in bb.db), and the
plugin's background service holds the connect tunnel — dialing the gate,
proxying relayed requests to the server's own loopback (which serves the SPA
/api+/ws), and reconnecting with capped backoff. The tunnel therefore lives as long as the bb server runs (with the plugin enabled) and re-establishes on restart; there is no foreground client. Pair from a machine without an installed bb vianpx -p bb-app@latest bb connect ….bb connect statusshows the connect state and every share's host and URL;bb connect offdisconnects and clears the pairing. After pairing,bb connect expose <port>run from a thread shares that thread environment's enrolled host. Server-host URLs remainhttps://<server-label>--<port>.getbb.app; other machines usehttps://<machine-label>--<port>.getbb.appand proxy directly through the owning daemon. Outside a thread the command defaults to the server host;--host <name-or-id>overrides host resolution. Access requires the owner's getbb.app session (not a public link).bb connect unexpose <port>andbb connect sharesuse the same host resolution and accept the same--hostoverride. Their JSON rows includehostId,hostName,port, andurl;shares --jsonalso includes the resolvedhost. A machine without a live Connect enrollment fails fast with instructions to remove and re-add it in Settings → Machines. Disabling the plugin (bb plugin disable connect) cuts off all remote access;bb plugin enable connectrestores it.
The tunnel client lives in plugins/connect/; the CLI command is proxied to
the plugin, and Settings → Connect drives the plugin's rpc (including shared
ports).
The bb mobile app reaches a paired bb through the same connect route. It
enrolls as a connect machine — its own credential on the getbb.app account,
separate from the server's pairing secret and individually revocable — so
pairing starts from the bb, not from the phone. Both pairing surfaces sit
behind the mobileApp experiment (Settings → Experiments → Mobile app, or
bb settings experiment mobileApp true) until the app is generally available;
the connect plugin reads the experiment from /system/config on every call,
so a toggle applies without a plugin reload:
- Settings → Remote access → Add mobile device mints a one-time code and shows it as a QR code plus copyable text with a countdown.
bb connect machine-codeprints the same code, server URL, connect apex, and expiry;bb connect machine-code --jsonreturns{code, serverUrl, apex, expiresAt}(the QR encodes that JSON).
Scan or type the code in the mobile app. The code lasts 10 minutes and works
once. The phone then appears in the getbb.app dashboard machine list, where you
can revoke it; every enrollment takes one of the account's machine slots
(desktop apps, remote execution machines, and phones all count), so a
machine-limit error asks you to revoke an unused device first. Both surfaces
need the experiment on, the bb paired (bb connect --code …), and the connect
plugin enabled; with the experiment off the panel hides the section and
bb connect machine-code exits 1 with a pointer to the toggle.
Message editing is available for eligible, accepted root user messages in a Codex, Claude Code, or Pi thread, including failed or incomplete turns. Grouped multi-message requests are not yet editable. Opening the editor does not change history; if the thread is running, submission stops the current turn and waits for it to settle before atomically replacing that message and every later turn while keeping workspace changes.
Experimental surfaces are changed in Settings → Experiments or with
bb settings experiment <key> <true|false>. All experiments start off.
The default-off changelogPreview experiment shows the latest release notes
as a compact, dismissible card on Settings → Updates.
The mobileApp experiment turns on pairing for the bb mobile app: the
Add mobile device card under Settings → Remote access and the
bb connect machine-code command (see "Pairing the bb mobile app" above). It
is off by default while the app is in early access.
BB releases restorable provider sessions after 30 idle minutes. The daemon checks for these sessions every five minutes. Active turns, commands, agents, workflows, and monitors keep their sessions loaded.
The sidebarProgressiveDisclosure experiment is off by default. In By
project and By machine, it shows the first five groups in the current sort
order, keeps attention groups visible, and reveals ten more per Show more
click. Revealed groups stay visible through activity and sort-order changes.
Manually is unchanged. Toggle it with bb settings experiment sidebarProgressiveDisclosure <true|false>.
The timelineWindowing experiment is off by default. When enabled, long
timelines and large expanded timeline details retain stable height-preserving
wrappers while mounting only rows near their active scrollport. Toggle it with
bb settings experiment timelineWindowing <true|false>.
The serverMove experiment is off by default. When enabled, Settings → Machines
offers Move server here, and the server accepts bb server move,
bb server export, and old server copy deletion (POST /api/v1/server/move,
/server/move/check, /server/export, DELETE /api/v1/hosts/:id/old-server-copy).
While it is off those routes return 403 server_move_experiment_disabled;
move status and cancel stay available. Toggle it with
bb settings experiment serverMove <true|false>.
The multiMachinePicker experiment is off by default. When enabled, projects
with at least three machines use a searchable, target-first environment picker,
and machine-only pickers become searchable when they have more than five
machines. Toggle it with bb settings experiment multiMachinePicker <true|false>.
Timeline pages select conversation groups using user-message anchors. The
BB_FF_TIMELINE_WINDOW_EVENT_BUDGET setting (default 1500) guides the number
of groups selected and limits the number of content leaves returned. Grouping
loads complete selected turns, their delegation descendants, and lifecycle
context. The setting is not a hard limit on query bytes, event count, or CPU:
one large turn can require substantially more work.
Pages target 4 MiB of rendered rows. An oversized group is continued by an opaque content cursor after grouping. Turn/delegation ancestors keep their canonical IDs, source ranges, and summary counts while their children are paged. A single indivisible row may exceed the byte target; its content is not silently discarded. Retained tool-output previews and full-output availability continue to use the large-output sidecar policy.
A walk is bound to its initial history sequence, grouping version, and display options. Appends do not move that snapshot. Edits, deletions, or out-of-order insertions invalidate its cursor, including after a server restart. A new latest response replaces the client's loaded snapshot when its identity changes; old page responses cannot merge into it. This conservative behavior also handles late events that change earlier grouping. It can require loading older pages again during live updates.
Older activity loads as the user scrolls. bb thread log --all walks one
snapshot and joins repeated summary ancestors; rerun it if an edit invalidates
the walk. The API and SDK contract is described in
timeline-pagination.md. The budget is a server-start
operator setting, not an app preference.
Timeline builds slower than 150ms log Thread timeline build blocked the event loop with a per-stage breakdown, and event-loop stalls over 500ms log Event loop stalled. Both log at info, so they are visible in ~/.bb/logs/ without
raising BB_LOG_LEVEL.
Plugins are on by default. Builtin plugins, including connect, ship with bb;
user-installed plugins come from bb plugin install or the bundled official
store.
Plugin state lives under the data dir:
<dataDir>/plugins/<id>/data.db Per-plugin SQLite database
<dataDir>/plugins/<id>/secrets/ Secret settings and the plugin HTTP token
<dataDir>/plugins/<id>/logs/ bb.log output (plugin.log, JSONL, rotated
at 5MB; read with `bb plugin logs <id>`)
<dataDir>/plugins/git/, npm/ Managed installs for git:/npm: sources
<dataDir>/marketplaces/staging/ Throwaway checkouts a git: marketplace
refresh reads its manifest from, deleted
as soon as the catalog is stored
<dataDir>/skills-generated/ Server-generated skills (the
plugin-commands skill listing plugin CLI
commands, injected into agent threads)
BB's official plugins (GitHub, Docs, Memory, and Tasks) ship bundled
inside the app and install from the local bundled copy — no network, no remote catalog.
Discover them with bb plugin search or Plugins → Browse plugins; users
cannot add, remove, or configure the bundled official plugin set. Installed official
plugins are pinned to the bundled copy and update with BB app releases. Local
path installs remain available directly through bb plugin install ./path or
path:..., and direct npm:/git: installs stay supported.
Marketplace catalogs and their validated icon bytes live in the bb database,
not on disk. bb marketplace add|list|refresh|remove and Settings → Plugin
marketplaces manage them; the reserved bb-community marketplace comes from
BB_MARKETPLACE_URL and cannot be added or removed. Adding a marketplace
installs nothing, and removing one keeps its installed plugins as direct
installs.
A repository can hold several plugins. Each plugin directory keeps its own
package.json and bb manifest; an optional .bb/plugins.json collection
manifest at the repository root indexes them:
{
"$schema": "https://getbb.app/schemas/plugins.schema.json",
"schemaVersion": 1,
"name": "acme-plugins",
"plugins": [
{ "name": "notes", "source": "./plugins/notes" },
{ "name": "status", "source": "./plugins/status" }
]
}The file is strict: schemaVersion must be 1, names match
^[a-z0-9][a-z0-9-]*$, unknown fields and duplicate names are rejected, and
each source is a repository-relative directory starting with ./ — absolute
paths, .., empty segments, and the repository root itself are refused. An
invalid file is rejected whole. The manifest is an index only; it never
overrides a plugin's identity, branding, entry points, or engine ranges.
Install one plugin of the repository with
bb plugin install git:<url>[@<ref|semver-range>] --plugin <name> (resolves a collection
entry) or --subdirectory <relative-path> (the primitive, which needs no
collection manifest). Both flags work for path: sources and are mutually
exclusive. Installs from the same repository and commit share one cached
checkout, and the selected subdirectory is recorded with the install, so
bb plugin outdated, update, rollback, and remove act per plugin. A
repository that has a collection manifest and is not a plugin itself refuses
an unselected install and lists its entry names.
Bundled builtin and official plugins update with BB app releases. For direct
git:/npm: installs, update application is manual: bb plugin outdated or
the "Check for updates" key on the Plugins page checks tracking sources, and
bb plugin update <id> / bb plugin update --all or the "Update x.y.z" pill
applies compatible candidates. The server also checks every installed plugin
every 6 hours (the first check runs when any plugin has no recorded check or
the oldest one is older than 6 hours), at most four plugins at a time, and a
manual check joins a sweep already in flight; a check only records what is
available and never installs or runs plugin code. There is no automatic plugin update
application or update audit feed. Reinstalling an already-installed managed plugin is
refused — use bb plugin update. Before activation bb snapshots the plugin
database, host-managed settings/storage/schedules, secrets, and registration.
A failed activation restores that snapshot and records the latest failure on
the plugin so it can be surfaced as needing attention.
The builtin Provider retry plugin is enabled on fresh installations. When a turn
fails on a structured Codex or Claude Code subscription-window limit that
reports a reset time, it queues that turn after the window opens. It also
retries structured provider overloads with exponential backoff and jitter.
Prior output or tool activity does not block recovery. If the provider accepted
the failed input, core sends an agent-only continuation; if it rejected the
input before starting, core re-sends the original message as agent-only. Disable
the plugin under Settings → Installed plugins or with
bb plugin disable provider-retry.
It never blocks a send. A remembered rate limit is a stale picture of the
provider's state, so the plugin never refuses a dispatch on one — if you raised
your plan or the window opened early, the next send simply works. The cost is
that several threads on one exhausted subscription each fail once before each
schedules its own retry; the retries are jittered so they do not all wake in the
same instant. Overload retries start after 5–10 seconds, double their delay
after each failure, and share the five-total-attempt cap with limit retries.
The maximumWait setting defaults to 6 hours; resets beyond that horizon are
not scheduled. Choose 24 hours or No limit under the plugin settings, or
configure it from the CLI:
bb plugin config provider-retry set maximumWait "24 hours"A pending retry is a queued row on the thread, not an in-process timer, so it
survives a restart and shows its reason and time on the queue card above the
composer — the one surface that narrates the wait. Inspect them with
bb provider-retry status, cancel one on that card or with
bb provider-retry cancel <thread-id>, or run
bb provider-retry retry <thread-id> to send it now instead of waiting. Limits
that do not reset on a clock — credit and spend-control exhaustion — schedule
nothing, because waiting does not fix them.
The builtin Workflows plugin is disabled on fresh installations. Enable it
under Settings → Installed plugins or with bb plugin enable workflows. Its six
settings are bounded integers, edited with numeric inputs under Plugins →
Installed plugins or with bb plugin config workflows set <key> <value>:
| Key | Default | Allowed range | Behavior |
|---|---|---|---|
maxActiveRuns |
4 |
1–32 |
Concurrent runs across the plugin; changes apply live. |
maxConcurrentAgents |
8 |
1–64 |
Concurrent agent calls within one run. |
maxAgentCalls |
100 |
1–1000 |
Total agent calls within one run. |
totalRunTimeoutMs |
86400000 |
60000–604800000 |
Maximum total run duration in milliseconds. |
retentionDays |
7 |
1–3650 |
Days to retain completed workflow data. |
maxNotificationBytes |
16384 |
1024–262144 |
Maximum UTF-8 size of a completion notification. |
The five settings other than maxActiveRuns are snapshotted into each new run.
Settings changes do not require a plugin reload.
bb plugin install npm:<package>[@<version|tag|range>] uses BB's shipped npm
and its running Node runtime; neither executable needs to be on PATH. Packages
are installed with --ignore-scripts. Git plugins also use this npm with
lifecycle scripts disabled and --omit=dev --omit=optional. Plugins may keep
normal development dependencies in their manifests; npm resolves these but
does not install them. They may depend on third-party runtime packages; bb
then builds both their server and frontend bundles. node_modules is
retained, because a dependency can load data files that bundling cannot
inline. A committed dist/ is always replaced by the bundles bb builds.
Dependency resolution and bundling run on install and update-apply only —
never on an update check, which reads the manifest and stops. An omitted npm
spec tracks the newest compatible stable release, ranges track within the
range, dist-tags track the tag, and exact versions are pinned. A bare HTTP(S)
Git repository URL or git:<url>[@<ref|semver-range>] requires git; an
omitted ref tracks the repository's default branch, explicit branches track
their head, and tags and commits are pinned. A semver range
(git:<url>@^1.2.0, or @semver:<range> to state the intent explicitly)
tracks the repository's [<tag-prefix>]vX.Y.Z release tags: bb installs the
highest release the range allows, excludes prereleases unless the range names
one, records the tag and the commit it pointed at, and refuses that tag later
if it moved. --tag-prefix <prefix> ranges over one plugin's tags in a
multi-plugin repository. A bare range that is also a literal branch or tag
name fails the install and asks for @semver: or @ref:. Local
path installs register the directory in place and never delete it. Builtin
plugins use builtin:<name> and ship with bb unless removed. Managed
(git:/npm:) installs
refuse plugins whose optional engines.bb or engines.bbPluginSdk ranges
do not match the running bb/SDK, or whose dist/*.meta.json plugin identity
does not match the package manifest; installing a non-builtin source whose
derived id collides with a builtin name (automations, connect,
custom-instructions, inline-vis, secrets, workflows) is also refused.
engines.bbPluginSdk is a floor, not a ceiling. bb reads the lowest version
the range allows and runs the plugin on any SDK at or above it within the same
major, so a caret range such as ^0.4.1 keeps working after the SDK moves to
0.4.3 or a later 0.x. Only a plugin that asks for a newer SDK than this bb
provides, or one pinned to a different major, is incompatible. Declare the
oldest SDK you need (>=0.4.3); a breaking plugin API change bumps the major.
The same tracking intent drives updates: bb plugin outdated checks for
compatible candidates (and reports blocked incompatible newer releases);
bb plugin update <id> / bb plugin update --all applies them. Pinned source
intent is never widened by update; remove and reinstall to choose a different
source intent. Dev builds (bb 0.0.0) do not enforce engines.bb and annotate
that on check results.
Update confirmation matches install (full-trust code; --yes skips; non-TTY
refuses without it). Plugins are full-trust code running inside the bb server
process: they can read all local bb data, including other plugins' secrets.
Use launcher flags for per-run startup details:
npx bb-app --data-dir ~/.bb-test --server-port 48886 --host-daemon-port 48887The server listens on 127.0.0.1 by default. Set
--server-bind-host 0.0.0.0 (or BB_SERVER_BIND_HOST=0.0.0.0) only when a
trusted network boundary must reach the listener directly. The public API is
unauthenticated and permits command execution and file reads, so never expose a
wildcard-bound server to an untrusted network. The only accepted bind hosts are
127.0.0.1 and 0.0.0.0; this startup-only setting is not available through
bb-app config.
The startup Server listening and app lines show the actual listener address.
With wildcard binding they show http://0.0.0.0:<port>, while bb's health check
and colocated host daemon continue to connect through 127.0.0.1. That local
connection does not narrow the listener. 0.0.0.0 exposes IPv4 interfaces only;
bb does not currently offer an IPv6 wildcard bind option.
The data directory is the root directory for all bb-managed state: the SQLite
database, logs, host identity, thread storage, custom themes (theme/,
including optional Pierre / VS Code pierre-dark.json and pierre-light.json),
and
plugins. It defaults to ~/.bb/ for the packaged app. The pnpm dev source launcher derives an isolated data
directory under ~/.bb-dev/<checkout-instance>/ from the checkout path. The
checkout instance id is the sanitized path to the checkout, relative to your
home directory, plus a short hash suffix. Use --data-dir to point packaged-app
instances at different data directories for fully isolated environments.
If the default ports are already in use, set explicit ports before starting:
npx bb-app --server-port 48886 --host-daemon-port 48887The Settings → Machines installer assigns every enrolled standalone host daemon
a stable local API port so it can coexist with the desktop app and with daemons
enrolled to other servers. The selected port is persisted in the machine data
directory and reused by subsequent runs. Its generated command accepts
--host-daemon-port <port> when an explicit port is required.
For source development only, pnpm dev, pnpm start:worktree,
pnpm start:worktree-remote, pnpm start:worktree --dryrun,
and pnpm start load the repo-root dotenv
cascade. Add a repo-root .env only when you need to override the defaults
described above.
The standard dotenv-cli cascade
applies to source development. pnpm dev loads .env, .env.local,
.env.development, and .env.development.local, then overrides the instance
selectors (BB_DATA_DIR, server URL/port, host-daemon local API port, and Vite
port) with deterministic values derived from the checkout path. The SQLite
database path is always derived from BB_DATA_DIR. Both the main server and
Vite app bind to loopback by default; an explicit BB_DEV_APP_HOST still
overrides the Vite listener. Remote HTTP dev via BB_DEV_APP_HOST also requires
BB_SERVER_BIND_HOST=0.0.0.0 for realtime updates; the Tailscale Serve HTTPS
path avoids this because WebSocket traffic goes through the Vite proxy.
pnpm start:worktree loads the same development dotenv cascade and uses the
same checkout-specific data directory, server port, and host-daemon port. It
builds production artifacts and serves the frontend bundle from the main
server, so there is no separate Vite listener or hot reload. Telemetry remains
disabled for this source-development command. Its worktree data directory,
ports, inherited skills, listener host, absent Vite port, and telemetry policy
take precedence over conflicting values saved in that instance's config.json
or env.json.
Add --dryrun to pnpm start or pnpm start:worktree to prepare through Turbo,
print the same resolved ports and paths that normal startup uses, and exit.
This writes build outputs and may repair native modules, but does not start
services, migrate instance data or require ports to be free. Normal startup
also runs Turbo preparation and preserves the command's runtime policy. See
Prepared Worktree Restarts.
pnpm start:worktree-remote applies the same policy while binding the main
server to 0.0.0.0 for direct access on a trusted network. The API is
unauthenticated and permits command execution and file reads, so protect the
port with a trusted network boundary such as Tailscale and a host firewall.
pnpm start loads .env, .env.local, .env.production, and
.env.production.local.
Production startup from source uses the same launcher policy as the packaged
app while reading build outputs directly from apps/app, apps/server, and
apps/host-daemon. pnpm start:host-daemon continues to run the packaged
packages/bb-app/dist/bb-app.js host-daemon entrypoint. Source-only scripts do
not own production ports or data-dir defaults.
Source checkout commands such as pnpm bb, pnpm bb:dev, and pnpm reset
are thin wrappers around @bb/scripts. Those wrappers force NODE_ENV to the
intended mode so ambient shell state does not silently retarget bb.
Use pnpm reset or pnpm reset:dev to clear a data directory. These only
remove bb-managed state, not provider credentials.
BB_PROVIDER_BRIDGE_RECORD_DIR=<dir> in the host daemon's environment turns
on bridge record mode: every provider bridge writes the lines that cross its
runtime and provider wires as NDJSON under <dir>/<providerId>/<threadId>/.
It is a development and diagnostics knob, off by default, and never reaches a
provider child. See provider-bridge-protocol.md,
"Record mode", and debugging-and-qa.md. Raw recordings
can contain secrets; redact them with scripts/provider-recordings/redact.mjs
before you share them.
Agents use bb browser-automation through its bundled skill. Screenshot results
contain temporary JPEG paths and the browser host ID; remote captures can be
fetched with bb file read <path> --host <host-id> --json. Read or copy images
before closing the session, which deletes its temporary files. Opening a
local headless session also returns a previewDirective that the agent pastes
into its message; BB renders it inline as a live preview that expands into a
lightbox. Desktop sessions return none, because
that browser is already visible in the app. There is no setting for it;
collapse the card to pause it.
The Browser Automation plugin supports desktop attachment and headless Chrome on enrolled hosts. Cloud browsers are deferred. The plugin pins one exact dev-browser npm release (currently 1.0.0-rc.3) with per-platform binary digests in plugins/browser-automation/runtime-pin.ts; the pin, the verification steps, and the bump procedure are documented in plugins/browser-automation/README.md.
On each selected browser host, the plugin's host worker installs that release automatically on first use under <plugin host dataDir>/runtime/npm/, using the host's npm with scripts disabled, verifying the registry signature and SLSA provenance, downloading the matching GitHub release binary, and checking its digest before launch. Later sessions reuse the verified install without network access. Headless mode discovers installed Chrome/Chromium or uses <plugin host dataDir>/runtime/chrome. These files belong to the plugin host storage directory; they are not paths on the server or invoking agent host, and the user's global npm installation is never modified. No runtime sandbox-disabling setting is provided.
For isolated development smoke tests only, DEV_BROWSER_SMOKE_BINARY selects the absolute binary path for the runtime smoke, DEV_BROWSER_SMOKE_CHROME selects the absolute Chrome path, and DEV_BROWSER_SMOKE_NO_SANDBOX=1 enables the fixture's no-sandbox wrapper where the test host requires it. The smoke:install task performs a real install of the pinned release into a disposable directory. These variables do not change normal plugin runtime behavior.
BB guide is installed and enabled by default. In Settings → Installed plugins
→ BB guide, introduction controls the BB introduction, skills controls all
four bundled skills, and bbCli, pluginAuthoring, skillCreator, and submitPlugin control
individual skills. All default to true. Disabling BB guide removes its
introduction and skills; other plugins and independently installed skill
copies retain their own configuration.
Connect's sendRemoteInstructions setting ("Tell agents about remote access")
defaults to true. When false it suppresses Connect's active/recent remote-use
message without disabling sharing.
Use bb plugin config <id> set <key> true|false or the SDK's
plugins.updateSettings({ pluginId, values }). These settings apply when
agent configuration is next assembled, not retroactively to existing text.
Machines settings expose Server URL reachable by machines (machineServerUrl)
and Default machine access (defaultMachineAccess). The URL must be HTTP
or HTTPS without embedded credentials. An unset URL falls back to
BB_EXTERNAL_URL. The URL input appears when Manual is selected. An unset access provider selects
bb connect, even when unpaired; Machines settings links to its setup. Choose
Manual (direct) explicitly to use your own URL. An explicit provider must
be installed and available. Configure these with bb settings general machineServerUrl <url-or-null> and bb settings general defaultMachineAccess <provider-id-or-null>. bb settings show --json reports the effective access
selection. Access grants serve ongoing runtime requests as well as enrolment.
Bootstrap v2 carries optional provider headers. The machine persists them as
serverHeaders in its private config.json; the launcher supplies them to the
daemon through BB_SERVER_HEADERS as a JSON string map. These headers are private
credentials and cover enrollment, HTTP, WebSocket, and runtime proxy requests.
Direct grants omit headers. Legacy machineCredential configuration is translated
into the corresponding request header when loading an existing machine.
For machine enrollment, BB_DATA_DIR selects isolated machine state instead of
~/.bb-machines/<server-host>. bb machine enroll refuses the default ~/.bb
directory and a conflicting host or server identity. Local bb machine start|stop|uninstall --host-id <id> treats BB_DATA_DIR (or --data-dir) as an
ownership assertion, not permission to act on arbitrary files: lifecycle commands
require a canonical installer-owned directory under ~/.bb-machines and verify
identity and service/process ownership. Optional --server-url asserts the server.
Without an explicit directory, lifecycle commands locate the unique matching host.
Core resolves machine contributions through
apps/server/src/services/hosts/host-environment.ts before dispatching setup and
teardown hooks. Ordinary setup uses environment.attach; explicit hooks use
environment.hook.run. Both carry transient contributedEnv values;
the daemon applies them to the hook child process. Hook progress and errors are
forwarded as-is, so contributed values printed by the child remain visible.
Machine selection and precedence stay in the server
resolver.
Settings → Environment variables is the machine environment editor. Its scope
control sits under the section header and switches between All projects and a
single project. Project settings →
Advanced settings exposes the same editor for that project, where inherited
global variables are listed read-only with an Override action. Project variables
follow the project across machines and worktrees, including the primary host.
Values are encrypted in one environment_variables table;
project_id IS NULL denotes global scope. Database migration preserves existing
ciphertext from app_settings_values. New ciphertext authenticates both scope
and name; legacy global ciphertext remains readable. The server keeps the key
in its data directory's machine-environment-key file (mode 0600); back it up
with the database. Names and notes are public metadata; settings APIs never
return saved values. Provider environment diagnostics mask core contributions.
Commands can still print their own environment values.
Built-in credentials are overridden by global variables, then project variables. Agent-provider contributions retain precedence over these values. Empty strings are explicit overrides. Deleting a project override restores the inherited value. Project deletion removes its variables when its database row is removed.
Global values synchronize into every host daemon, including the primary host, before work, on reconnect, and when settings change. Project values are resolved by the server and passed only to project operations core runs: agent turns/resume, source clones, setup/teardown hooks, and new terminals. They never modify the daemon's global environment. Existing terminals and commands retain their launch environment. Agent turns receive fresh values on the next turn; providers reconstruct sessions where needed to apply changed or removed values.
Plugin host calls receive global variables only; they cannot select a project.
A plugin worker keeps its current environment while any of its calls are active,
as before. This means a project override of a credential such as GH_TOKEN
applies to the project's clone, setup script, terminals, and agent turns, but
not to git commands an environment provider plugin runs on the machine, which
use the global value. Project-scoped contributions require daemon protocol 211;
older daemons update before the server accepts their session.
For non-primary hosts, the built-in GitHub row uses gh auth token --hostname github.com and gh api --hostname github.com user on the server host. It
supplies GH_TOKEN, Git's
GIT_CONFIG_* environment entries for an HTTPS credential helper and SSH URL
rewrites for github.com, and author/committer identity. The helper expands
GH_TOKEN when Git calls it; no helper file, global Git config, or credential
store is installed. The primary host continues using its local Git authentication
unless an explicit global or project GH_TOKEN overrides it. Private email uses
<id>+<login>@users.noreply.github.com.
A user GH_TOKEN overrides the built-in token, and the row shows overridden.
Tokens obtained from gh are never persisted by the server. Image construction
and Modal filesystem snapshot settings do not receive these contributions.
Use bb machine env list, bb machine env set NAME [--note text], and
bb machine env unset NAME; all accept --project <id> and --json.
Omitting --project retains global behavior. Set reads stdin, removes one
trailing newline, and never accepts a value in argv. For example:
printf '%s' staging | bb machine env set DEPLOY_REGION --project proj_example.
SDK parity: sdk.system.machineEnvironment, replaceMachineEnvironment,
setMachineEnvironmentVariable, and deleteMachineEnvironmentVariable manage
global values. The same methods under sdk.projects take projectId.
Set/delete mutate one variable atomically. Replacement atomically replaces one
scope, with value: null retaining an existing secret; missing saved values
are rejected. Project reads return variables and inheritedVariables, with
value: null and secret: true for every row. builtInGit reports effective
GitHub credential readiness.
Automatic GitHub credential forwarding to non-primary hosts is enabled by default. Use
bb settings general machineGitCredentialsEnabled false to stop forwarding the
server gh credentials to machines; true enables them again. In Settings →
Environment variables, the automatic GH_TOKEN switch controls the same setting.
This does not log the server out or suppress an explicit custom GH_TOKEN.
Changes apply to new turns, setup commands and terminals.
The optional Modal sandbox plugin builds/reuses named tools images for new
machines. Its plugin page keeps the bundled Standard Dockerfile as the first
image and can add named Dockerfiles, existing Modal image IDs, and named CPU/memory
presets. CLI: bb modal image show, bb modal image set --file PATH, and
bb modal image reset (append --json). Typed plugin RPCs image.definition,
image.set({dockerfile}), and image.reset expose the same persistent definition.
Supported instructions are one FROM followed by RUN, ENV, WORKDIR, and USER; no
build context or multi-stage builds. Saving does not build or modify existing
machines. The next new machine uses the saved definition. BB installs the daemon
on demand, then clones the project and runs its setup hook.
Configure tokenId and tokenSecret in secret plugin settings; appName defaults
to bb-sandboxes. With no size preset, Modal's CPU and memory defaults apply.
idleMinutes defaults to 15 (0 disables idle suspension), and timeoutMinutes
defaults to 1440 with an allowed range of 1–1440. Existing machines use current idle
policy; running compute keeps its vendor deadline and restored compute uses the
current lifetime. Resource reservations stay pinned across restore.
There is no automatic retention removal. Use bb machine remove MACHINE --yes
for explicit cleanup. Manual and idle pauses save a filesystem snapshot before
terminating compute. There is no pre-expiry scheduler: a sandbox that stays active
until its configured timeout can lose changes since its last successful pause.
Provider details expose expiry and saved-image status; missing compute never
silently restores stale state. Open terminals prevent idle suspension.
bb modal account inspect --json tests credentials without allocating resources.
Create with bb machine create --provider modal-sandbox --project PROJECT --json.
See modal-sandboxes
for prerequisites and lifecycle commands.
App production builds persist validated React Compiler transform results at
<git-common-dir>/bb-cache/react-compiler, shared safely across this
repository's worktrees. Non-Git checkouts use Vite's cache directory. Missing,
invalid, or corrupt entries are rebuilt; development and compiler diagnostic
modes bypass the cache. The cache has no user configuration and can be removed
while no builds are running. See build performance for
its identity, portability, and verification contract.
Anonymous usage telemetry can be disabled in Settings → General → Privacy & diagnostics → Share anonymous usage data,
or with bb settings general telemetryEnabled false. The saved server-wide preference
takes effect immediately and persists across restarts. SDK callers can use
system.updateGeneralSettings with telemetryEnabled. BB_TELEMETRY=false
always disables telemetry, even when the saved preference is enabled.