Skip to main content

Settings File

Bossanova reads global settings from a JSON file on disk:

  • macOS: ~/Library/Application Support/bossanova/settings.json
  • Linux: $XDG_CONFIG_HOME/bossanova/settings.json (defaults to ~/.config/bossanova/settings.json)
  • Profile override: set BOSS_SETTINGS_PATH to an absolute path to select a specific settings file.

The file is optional. When it's absent, defaults apply. Both boss and bossd read the same file. Use BOSS_SETTINGS_PATH when you want a dev build and an installed build to run in parallel without sharing local state.

Press s from the home screen to open the Settings view. It hosts the global toggles (such as the Skip permissions checkbox) and is also the gateway to the Repos (r), Cron (c), and Trash (t) screens.

Bossanova settings view

Example

{
"worktree_base_dir": "/Users/you/work/worktrees",
"default_agent": "claude",
"poll_interval_seconds": 120,
"plugins": [
{
"name": "claude",
"path": "/opt/homebrew/libexec/plugins/bossd-plugin-claude",
"enabled": true,
"config": {
"dangerously_skip_permissions": "true"
}
},
{
"name": "codex",
"path": "/opt/homebrew/libexec/plugins/bossd-plugin-codex",
"enabled": true
},
{
"name": "repair",
"path": "/opt/homebrew/libexec/plugins/bossd-plugin-repair",
"enabled": true
}
],
"repair": {
"skills": { "repair": "boss-repair" },
"cooldown_minutes": 1,
"poll_interval_seconds": 5,
"sweep_interval_minutes": 1
}
}

Cloud-sync settings (orchestrator URL, WorkOS client ID, daemon ID) are configured via environment variables. See Environment overrides below.

Top-level fields

FieldTypeDefaultDescription
worktree_base_dirstring~/.bossanova/worktreesDirectory where per-session git worktrees are created. Auto-created on load.
app_data_dirstringplatform defaultAbsolute directory for local daemon data: bossd.db, bossd.lock, profile plugin discovery, and default socket placement.
socket_pathstringderived from data directoryAbsolute path to the local bossd Unix-domain socket. If unset and app_data_dir is set, defaults to app_data_dir/bossd.sock.
default_agentstringclaudeName of the default agent plugin used for new sessions.
skills_declinedboolfalseSet after the user declines the one-time skills install prompt so it's not shown again.
poll_interval_secondsint120How often the Terminal UI (TUI) polls for PR display status, in seconds.
pluginsarrayauto-discoveredPlugin binaries to load (see below). If unset, bossd auto-discovers bossd-plugin-* binaries next to its own.
repairobjectdefaults belowRepair plugin configuration.
tmux_deliveryobjectdefaults belowComposer-readiness deadlines for message delivery into an agent pane. See tmux_delivery fields.
daemon_path_extraarray[]Directories prepended to the PATH written into the generated bossd service file. See Daemon PATH.
subagent_dispatch_grantstringalwaysWhich chats receive the bounded subagent-dispatch grant in their system prompt. See subagent_dispatch_grant below.

Daemon PATH

A service manager never sources your interactive shell config, so the daemon does not inherit the PATH your terminal has. A toolchain installed by nodenv, nvm, asdf or volta — or a claude from the native installer in ~/.local/bin — is therefore invisible to bossd unless its directory is on the service PATH.

The generated service file sets this PATH by default:

~/.nodenv/shims:~/.local/bin:/usr/local/bin:/usr/bin:/bin:/opt/homebrew/bin

on macOS (the LaunchAgent plist), and the same two shim directories prepended to the installing shell's PATH on Linux (the systemd unit). The bossd service and the MCP service render this from the same helper, so they can never disagree about where node lives.

daemon_path_extra

Set daemon_path_extra to put your own directories at the front of that PATH:

{
"daemon_path_extra": ["~/.asdf/shims", "/opt/custom/bin"]
}

Entries must be absolute or ~/-rooted. Anything else is dropped when the service file is rendered: a relative path, an empty entry, an entry containing a : (the PATH separator itself), a \, or one containing a newline or an XML-special character (<, >, &, "), which would otherwise corrupt the generated plist or inject a directive into the systemd unit. Duplicates are removed, keeping the first occurrence, so listing a baseline directory moves it to the front rather than repeating it.

A directory containing a space is fine — the systemd unit quotes its PATH value, and the plist stores it as XML text.

The key is prepend-only by design — there is deliberately no full-replacement override. The baseline entries can never be removed, so a typo here costs you one tool rather than a daemon that cannot run git.

Takes effect on the next daemon restart

Settings are read when the service file is rendered, so an edit is inert until you run boss daemon restart.

boss daemon doctor reports both PATHs and tells them apart: the one in the installed service file, which is what the running daemon actually has, and the one the next restart will write. When they differ it says so and points at the restart. It resolves node and claude under the installed PATH — not your shell's, which is exactly the check that passes while the daemon is broken.

plugins[] entries

FieldTypeDescription
namestringPlugin name (matches the suffix after bossd-plugin-).
pathstringAbsolute path to the plugin binary.
enabledboolWhen false, the plugin is loaded-but-inert.
versionstringOptional version string, informational.
configobjectPlugin-specific string key/value pairs.

claude plugin config keys

KeyTypeDefaultDescription
dangerously_skip_permissionsstring "true" / "false""false" (omit for default)Pass --dangerously-skip-permissions to the Claude Code CLI invoked by the claude plugin. Off by default. Toggle via boss settings --skip-permissions / --no-skip-permissions, or in the boss TUI settings view.

repair fields

FieldTypeDefaultDescription
skills.repairstringboss-repairSkill invoked to attempt repair.
cooldown_minutesint1Minimum gap between repair attempts on the same session.
poll_interval_secondsint5Poll interval for repair status checks.
sweep_interval_minutesint1How often the plugin sweeps for sessions needing repair.

tmux_delivery fields

Before bossanova types into an agent's pane it waits for the composer prompt to appear. Delivery fails rather than typing into a pane that isn't ready, because keystrokes sent early are swallowed or land in the wrong widget.

There are two deadlines because the two delivery paths have different ceilings, and each is configured independently — setting one never moves the other.

FieldTypeDefaultDescription
session_start_ready_deadline_secondsint45How long each attempt of a session start or resume waits for the composer prompt. This covers tmux spawn, interactive login-shell init, agent exec, node boot, and first paint. The start path makes up to two attempts, and each one reserves a further two seconds for an interstitial-dialog check, so the worst-case wall clock is a little over twice the value you write. Not clamped — raise it as needed.
send_ready_deadline_secondsint5How long a send into an already-running agent waits for the composer prompt. Clamped to 20 seconds, whatever you write.

Values of zero or below are ignored and the default applies; a settings file written before this block existed keeps both defaults, so there is nothing to migrate.

Raise session_start_ready_deadline_seconds when sessions fail to start on a host with a slow shell profile — measured login-shell init alone has ranged from under a second to twelve seconds on affected machines.

It is a per-attempt budget, not a total. The start path retries the readiness wait once before giving up, so a start that is genuinely going to fail spends roughly twice this value — about 94 seconds at the default of 45 (each attempt also reserves two seconds for an interstitial-dialog check on its way out). Size the knob against the boot you want to survive, then expect a little over twice that before a doomed start reports failure. The send deadline is not retried: one attempt, always.

An attempt can also be shortened — not by this setting, but by whatever context the start is running under. If the caller has less time left than a full attempt needs, the wait is trimmed to fit rather than skipped, and the timeout message says so: shortened from 45s to stay inside the caller's context. Read that clause as pointing somewhere other than this file: the number you configured was not the constraint, and something above the start ran out first.

Switching a chat's account used to be the commonest concrete source, and is no longer one. A switch respawns the pane under the new account and then waits for its composer, and bossanova gives that whole operation a budget of its own, so a failing switch cannot run unbounded after the request that asked for it. That budget is derived from this setting rather than fixed: it is whatever you configure here plus a further 45 seconds, covering the interstitial-dialog check an attempt reserves, the work the switch does before the respawn even starts (mid-turn check, pane kill, transcript probe, account binding write), and a margin on top.

So the longest a doomed switch waits before reporting failure is session_start_ready_deadline_seconds + 45 seconds — 90 seconds at the 45-second default, and 345 seconds if you raise the setting to 300. Raising this value does help on the switch route: the budget rises with it instead of clamping the wait below the number you wrote. The cost is proportional, and it is the deliberate trade — the more room you buy a slow host, the longer a switch that is going to fail takes to say so.

The repair-driven start path has the same derived protection around its plugin-to-daemon StartChatRun RPC. Its ceiling is session_start_ready_deadline_seconds + max(session_start_ready_deadline_seconds, 4 seconds): 90 seconds at the 45-second default, and 600 seconds if you raise the setting to 300. Raising this value therefore also lengthens how long an automated repair attempt may wait on an unresponsive daemon. That is intentional; a fixed cap would put the silent readiness clamp back on the slow hosts this setting exists to support.

That number is the daemon's budget. The result path has a separate reporting bound: the same 120-second ceiling applies to a switch sent through the cloud relay and to the local path's daemon socket write timeout plus the TUI's slow-RPC deadline. The crossover readiness (config.SwitchBudgetCrossoverReadiness(), 75 seconds at today's reserves) is a nominal, budget-only comparison of session_start_ready_deadline_seconds + 45 seconds against that 120-second result ceiling. It excludes the bosso work that happens before the daemon switch budget starts: cloud/account access, ownership and chat lookup, command dispatch, and transport scheduling. Values near 75 seconds are therefore not a hard user-visible flip point.

When the bosso relay result ceiling does stop waiting first, the switch may still be running, the request did not cancel or tear it down, and the timeout text says the caller stopped waiting without receiving a daemon verdict. Re-check the chat's account to see where it landed. The relayed switch path is the remote client (boss --remote) and gateway proxy path, not a web-UI-only button. The local TUI path is bounded by the same 120-second value, but it still uses the generic slow-RPC wording from the local client deadline interceptor when that caller-side ceiling wins. A /boss switch typed into a chat still runs inside the ordinary 30-second chat-message ceiling, which it is already past at the default; that route deliberately keeps the generic chat-message timeout wording because bosso cannot cheaply single out switches there without moving the command parser.

The budget still funds one full attempt and declines a second once the first has used most of it, so above roughly 33 configured seconds a switch reports after 1 of 2 attempts where an ordinary session start would have tried twice. That is the switch's budget doing its job, not this setting being too small. Below that threshold both attempts are still funded, which costs a doomed switch a few extra seconds and nothing else.

The other sources of a shortened attempt are unchanged: a request deadline, or a cancelled parent.

The send deadline is clamped because that delivery runs inside a request the cloud relay bounds at 30 seconds. A readiness wait that outlives the relay returns an ambiguous result the caller must not retry: a retry would type the message into the composer a second time. The 20-second ceiling leaves the relay ten seconds of headroom.

One case is served by the shorter deadline even though it is a cold start: sending to a chat that is asleep, with wake enabled. The wake only launches (or resumes) the agent — it delivers nothing and never waits for the composer — so the session-start deadline is not spent there. Your message is typed in afterwards, by the ordinary send path, which is therefore waiting on a pane that is still booting while holding only the send deadline. If that combination fails for you, wake the chat first and send once it is live: the wake itself does not time out on the composer, so the pane has as long as it needs, and the separate send then meets a pane that is already up.

Takes effect on the next daemon restart

Both deadlines are read once, when the daemon builds its tmux client, so an edit here is inert until you run boss daemon restart.

subagent_dispatch_grant

Most boss skills mandate subagent dispatch as part of their protocol — boss-review runs its lenses and round extensions as subagents, boss-plan dispatches its drafting and reviewer extensions, boss-repair and boss-epic fan out the same way. Coding-agent harnesses ship a standing system-prompt line that tells the agent not to dispatch subagents unless the user asked for it. That restriction comes from the harness, not from bossanova, and it has no off switch — so without a counter-instruction those skills quietly collapse their dispatched work into a single context and can still report a clean run.

Bossanova therefore appends a bounded dispatch grant to the system prompt it builds for each chat. The grant authorises only the dispatches the running skill's protocol already mandates, requires every dispatch to be awaited rather than backgrounded, and treats a skill's documented inline fallback as a clean result. It is not a general widening of tool access.

subagent_dispatch_grant selects which chats receive it:

ValueEffect
alwaysDefault. Unattended sessions and attended chats both receive the grant.
unattendedOpt out for attended chats. Only unattended sessions (cron, boss-epic children, detached runs) get it.
{
"subagent_dispatch_grant": "unattended"
}

The key is absent from a fresh settings.json and behaves as always. An unrecognised value also resolves to always — a typo must not withdraw the shipped default, and it never fails daemon startup. Because that fallback would otherwise discard an intended opt-out in silence, bossd logs a warning naming the offending value each time it spawns or wakes a chat; check bossd's log if an opt-out does not seem to be taking effect.

Edit this key in settings.json directly — it is not yet exposed in the boss settings form or the update_settings API, so the TUI neither shows nor overwrites it. Unrelated settings changes made through the TUI preserve it.

Unattended sessions receive their grant in every configuration — unattended is an opt-out for attended chats only, and cannot be used to withdraw authority from an autonomous run.

The setting is read when a chat is spawned, so a change takes effect for chats started afterwards. Chats already running keep the grant they were launched with; start a new chat — or wake a sleeping one, which re-spawns it — to pick up the change.

Development profile

To run a development build beside the Homebrew build, keep a profile in the repo-local .config/ directory:

mkdir -p .config/data

Put this settings content in .config/settings.json, replacing /path/to/bossanova with your repo path:

{
"app_data_dir": "/path/to/bossanova/.config/data",
"socket_path": "/path/to/bossanova/.config/bossd.sock",
"default_agent": "claude"
}
export BOSS_SETTINGS_PATH="/path/to/bossanova/.config/settings.json"

With that environment, the development boss CLI reads .config/settings.json, starts or dials the daemon at .config/bossd.sock, and stores daemon data in .config/data. The Homebrew install can continue using the standard macOS Application Support paths.

If BOSS_SOCKET is set in your shell, it overrides the socket selected by socket_path. Unset BOSS_SOCKET when using profile files unless you are intentionally debugging a single socket path.

Environment overrides

Cloud-sync settings (orchestrator URL, WorkOS client ID, daemon ID) are configured exclusively via environment variables. Other settings that have a settings.json field can also be overridden by env var. Precedence (highest wins): environment variable → settings.json → hardcoded default.

boss (TUI / CLI)

VariableNotes
BOSS_WORKOS_CLIENT_IDWorkOS client used by boss login; override when pointing at a staging orchestrator
BOSS_CLOUD_URLoverrides the authenticated cloud orchestrator URL used by boss login and remote CLI calls
BOSS_SKIP_SKILLSany non-empty value suppresses the first-run skill-install prompt (persistent equivalent: skills_declined in settings.json)
BOSS_SETTINGS_PATHabsolute path to the settings file; selects a profile before boss chooses the daemon socket or reads other settings
BOSS_SOCKETexplicit socket override for tests and one-off debugging; for normal profiles prefer BOSS_SETTINGS_PATH plus socket_path
BOSS_DAEMON_SKIP_LAUNCHCTLany non-empty value skips launchctl calls in boss daemon install/uninstall/status
BOSS_REPORT_URLoverrides the bug-report submission URL
BOSS_AUTH_E2E_EMAILe2e tests only: pre-seeds an authenticated identity so login flows can be exercised in CI; built only under the e2e build tag
BOSS_AUTH_E2E_NEEDS_RELOGINe2e tests only: flags the seeded identity as retained-but-needing-boss login; unset, empty, or an explicit falsey value (0/false/no/off) means no flag, refresh_token_rejected selects that reason and any other non-empty value selects refresh_outcome_unknown. Without BOSS_AUTH_E2E_EMAIL it seeds the identity [email protected]; built only under the e2e build tag
BOSS_HOST_E2E_RECONNECTe2e tests only: stages the --host tunnel-dropped wait screen before the TUI dials, so the reconnecting state can be captured on a harness with no network; unset, empty, or an explicit falsey value (0/false/no/off) means no seed, and any other value is used verbatim as the ssh destination shown on screen; built only under the e2e build tag
BOSS_HOST_E2E_RECONNECT_POLLSe2e tests only: how many probes report the seeded tunnel as still down before it recovers unaided (default 3); a malformed or negative value falls back to the default rather than failing the run, and it has no effect without BOSS_HOST_E2E_RECONNECT; built only under the e2e build tag

bossd (daemon)

VariableNotes
BOSSD_ORCHESTRATOR_URLURL bossd syncs with (default: https://orchestrator.bossanova.dev); set to "" for local-only mode
BOSSD_DAEMON_IDstable identifier this daemon registers under (defaults to machine hostname); each value creates a separate daemon record, so change carefully
BOSSD_USER_JWTbypass the keychain and pass a WorkOS JWT directly; used in CI

XDG and path variables

VariableWhat it affects
XDG_CONFIG_HOMEWhere settings.json is read from on Linux (macOS uses ~/Library/Application Support/)
XDG_STATE_HOMEWhere rotated log files live
XDG_RUNTIME_DIRNot used for Bossanova's daemon socket; set socket_path in settings or use BOSS_SOCKET for an explicit override
HOMEUsed to resolve ~/.claude/skills/ and ~/.bossanova/

GitHub App integration

Bossanova receives GitHub PR, check, status, review, and comment events through the GitHub App webhook endpoint on the orchestrator.

Configure the GitHub App with these URLs:

GitHub App settingValue
Homepage URLhttps://app.bossanova.dev/github/setup
Setup URLhttps://app.bossanova.dev/github/setup
User authorization callback URLhttps://app.bossanova.dev/github/setup
Webhook URLhttps://orchestrator.bossanova.dev/webhooks/github

The Homepage URL, Setup URL, and User authorization callback URL must match BOSSO_GITHUB_APP_CALLBACK_URL. Enable Request user authorization during installation and Redirect on update. Set the GitHub webhook secret to the same value as BOSSO_GITHUB_APP_WEBHOOK_SECRET.

Required repository permissions:

PermissionAccess
Pull requestsRead and write
ChecksRead and write
ContentsRead-only
MetadataRead-only
StatusesRead and write

Subscribe to these events:

  • pull_request
  • check_run
  • check_suite
  • status
  • push
  • issue_comment
  • pull_request_review

Required environment variables:

Terraform Cloud variableKubernetes runtime envSource
TF_VAR_bosso_github_app_idBOSSO_GITHUB_APP_IDGitHub App settings page, App ID
TF_VAR_bosso_github_app_slugBOSSO_GITHUB_APP_SLUGGitHub App URL slug
TF_VAR_bosso_github_app_private_keyBOSSO_GITHUB_APP_PRIVATE_KEYGitHub App private key PEM, stored as one escaped env value
TF_VAR_bosso_github_app_webhook_secretBOSSO_GITHUB_APP_WEBHOOK_SECRETWebhook secret configured on the GitHub App
TF_VAR_bosso_github_app_callback_urlBOSSO_GITHUB_APP_CALLBACK_URLFrontend setup route, for example https://app.bossanova.dev/github/setup
TF_VAR_bosso_github_app_client_idBOSSO_GITHUB_APP_CLIENT_IDGitHub App settings page, Client ID
TF_VAR_bosso_github_app_client_secretBOSSO_GITHUB_APP_CLIENT_SECRETGitHub App generated client secret

Mark the private key, webhook secret, and client secret as sensitive in Terraform Cloud. Terraform stores the desired GitHub App values for configuration wiring and exposes the runtime values through the kubernetes_secret_bosso output. Do not pass secret values as command-line arguments; process argv can be inspected.

cd infra/kustomize
./pull-secrets.sh production

For staging, run ./pull-secrets.sh staging. The generated .env-bosso file is gitignored and must not be committed.

Bosso GKE Runtime

Bosso runs on GKE with Cloud SQL Postgres, Redis, Google Artifact Registry, and the Google external ALB. Terraform writes the Kubernetes secret values:

BOSSO_DB_DRIVER=postgres
BOSSO_DATABASE_URL=postgres://...
BOSSO_MULTI_INSTANCE=true
BOSSO_REDIS_URL=redis://bs-redis-service.<namespace>.svc.cluster.local:6379/0
BOSSO_ROUTING_PROVIDER=kubernetes
BOSSO_WEBHOOK_ROUTING_URL=http://bs-bosso-service.<namespace>.svc.cluster.local:80

Generate each BOSSO_INTERNAL_ROUTING_TOKEN as a high-entropy secret, for example with openssl rand -base64 32, and set the same value on every bosso instance in that environment. The token authenticates internal routed delivery when one bosso instance forwards webhook or command work to the instance that owns the target daemon stream.

Use this token only for internal routed delivery between bosso instances; it is separate from the GitHub App webhook secret.

Kubernetes sets BOSSO_INSTANCE_ID from the pod name. Local or manual multi-instance runs must set BOSSO_INSTANCE_ID on each process to a distinct stable value.

Webhook behavior:

  • GitHub signs webhook payloads with the app webhook secret.
  • The orchestrator verifies the signature, then routes the event by installation_id to the WorkOS user that completed setup.
  • Pull request events trigger a targeted refresh for the affected repository and PR.
  • The polling fallback backs off for 5 minutes on repositories that recently delivered webhooks.