Skip to content

Latest commit

 

History

History
1589 lines (1354 loc) · 98.4 KB

configuration.md

File metadata and controls

1589 lines (1354 loc) · 98.4 KB

Configuration

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 refresh

Use 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_KEY

Repository worktree hooks

Commit .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_abc

Use --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.

Precedence

Configuration is resolved in this order:

  1. Explicit launcher flags, such as --data-dir, --server-port, or --server-bind-host.
  2. Persistent bb-app config, bb-app env, and client values.
  3. Ambient shell environment.
  4. 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, and BB_EXTERNAL_URL
  • BB_HOST_DAEMON_PORT, BB_INFERENCE, BB_INFERENCE_FALLBACK, and BB_INHERITED_SKILLS_ROOTS
  • BB_LOG_LEVEL, BB_MANAGED_DEV_BUILTIN_PLUGIN_HOT_RELOAD, BB_MARKETPLACE_URL, BB_POSTHOG_API_KEY, and BB_TELEMETRY
  • BB_SERVER_BIND_HOST, BB_SERVER_PORT, BB_TRANSCRIPTION, and all BB_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.

Stopping A Running bb

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 stop stops the bb that owns the data directory. Pass the same --data-dir you 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.

Common Keys

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.

Themes

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-sans controls app UI and body text.
  • --font-mono controls code blocks, diffs, file paths, and previews.
  • --font-serif controls serif prose.
  • --font-terminal controls 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.

Keyboard Shortcuts

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.

Client SSH Targets

~/.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.

Custom ACP Agents

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.

The deprecated customAcpAgents config array

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.

Custom Models

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.

Agent Instructions

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.

Skills

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.

Multi-machine

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 preferences

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.

Thread-list visibility

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.hiddenGroups

set 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.

Sidebar footer

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.hiddenFooterItems

Thread splits

Thread 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.

Account Pooler [Experimental]

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:9001

Upgrading 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

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 via npx -p bb-app@latest bb connect …. bb connect status shows the connect state and every share's host and URL; bb connect off disconnects and clears the pairing. After pairing, bb connect expose <port> run from a thread shares that thread environment's enrolled host. Server-host URLs remain https://<server-label>--<port>.getbb.app; other machines use https://<machine-label>--<port>.getbb.app and 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> and bb connect shares use the same host resolution and accept the same --host override. Their JSON rows include hostId, hostName, port, and url; shares --json also includes the resolved host. 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 connect restores 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).

Pairing the bb mobile app

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-code prints the same code, server URL, connect apex, and expiry; bb connect machine-code --json returns {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

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.

Experiments

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>.

Thread Timeline Window

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

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.

Multi-plugin repositories

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.

Plugin updates

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.

Provider retry plugin

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.

Workflows plugin

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.

Startup Flags

Use launcher flags for per-run startup details:

npx bb-app --data-dir ~/.bb-test --server-port 48886 --host-daemon-port 48887

The 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 48887

The 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.

Source Development

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.

Browser Automation runtime

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.

Agent guidance plugin settings

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.

Machine server access

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.

Machine environment

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.

Modal machines

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.

Repository build caches

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.