--- title: 'Configuration reference' description: 'Global flags, environment variables, on-disk state and the JSON output contract.' position: 3 --- # Configuration reference > Global flags, environment variables, persistent state on disk, the > `--json` envelope and the semantic exit codes. Everything you need to > drive the CLI from a script (or from a coding agent inside an IDE) > without parsing prose. ## Global flags Every command accepts these. The CLI reads them from `process.argv` before subcommand parsing, so they work wherever you put them on the line. | Flag | Env equivalent | Default | Description | | ------------- | --------------------- | ------- | ------------------------------------------------------------------------------------------------------------ | | `--json` | `STACKBONE_JSON=1` | off | Emit machine-readable JSON wrapped in `{ "schema_version": 1, ... }`. Errors get `error` + `exit_code` keys. | | `-y`, `--yes` | (none) | off | Skip every confirmation prompt. **Required in CI / non-TTY contexts.** | | `--verbose` | `STACKBONE_VERBOSE=1` | off | Stream the raw firehose of log lines and `docker compose` output instead of the per-stage spinner UI. | `--version` (or `-v`) is a root shortcut rather than a per-command flag. Alone on the line it prints two lines and exits `0`: the CLI's own version, then the agent protocol contract version this binary advertises. ```sh $ stackbone --version stackbone-cli 0.3.3 contract 15 ``` ## Environment variables The CLI reads these from the parent shell: | Variable | Effect | | ------------------------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `STACKBONE_JSON` | `=1` ⇒ same as `--json`. | | `STACKBONE_LOG_LEVEL` | Log level on stderr (`trace`, `debug`, `info`, `warn`, `error`, `fatal`). Defaults to `warn` on a terminal, so the per-stage spinners carry the UI, and to `info` under `--json` or `--verbose`. An explicit value wins over both. | | `STACKBONE_API_URL` | Control-plane the CLI talks to. Wins over the project's `.stackbone/project.json` and your global config. Point it at your own deployment when you run Stackbone in-house. | | `STACKBONE_FRPC_BIN` | Absolute path to a `frpc` binary. Used by `stackbone dev` for the relay tunnel. | | `STACKBONE_FRPC_VERSION` | Override the pinned `frpc` release the auto-fetcher downloads. | | `STACKBONE_DOCKER_BIN` | Path to a Docker-compatible binary `stackbone dev` drives. Defaults to `docker`. Set it for Podman or a non-standard install. | | `STACKBONE_DEV_PORT_OFFSET` | Non-negative integer that shifts every port `stackbone dev` binds by `offset × 10`, so two projects can run side by side. See below. | | `STACKBONE_POSTGRES_URL` | Connection string the `stackbone db migrate` verbs run against. Wins over the connection a running `stackbone dev` records for them. Set it to migrate a database that is not your local dev one. | | `STACKBONE_VERBOSE` | `=1` ⇒ same as `--verbose` (raw firehose of log + `docker compose` output). | | `STACKBONE_CORS_ALLOW_ORIGINS` | CSV of origins added to the `stackbone dev` Studio CORS allowlist. | | `MODEL_PROVIDER_API_KEY` | Your own model-provider key ([OpenRouter](https://openrouter.ai/docs), or any OpenAI-compatible gateway). When set in your shell it wins over the provider configured in the Studio **Model provider** screen. | | `MODEL_PROVIDER_BASE_URL` | Override the OpenAI-compatible base URL the model client uses. Defaults to OpenRouter cloud. Enough on its own for a keyless local gateway (Ollama, LM Studio). | | `OPENROUTER_API_KEY` | **Deprecated** alias for `MODEL_PROVIDER_API_KEY`, read only when the neutral name is unset. | | `OPENROUTER_BASE_URL` | **Deprecated** alias for `MODEL_PROVIDER_BASE_URL`, read only when the neutral name is unset. | | `STACKBONE_NO_UPDATE_CHECK` | `=1` ⇒ silence the "a new release of the Stackbone CLI is available" banner. The check is a background local-cache read that never blocks a command, and it is already suppressed in CI, on a non-TTY, and under `--json`. | Either model-provider variable also opens the first-run gate. Export one and `stackbone dev` never stops for setup: it goes straight to your workflows and agents. See [The first run](/docs/cli/guides/local-development#the-first-run). ### Running two projects at once One `stackbone dev` session owns a fixed set of ports, so a second one on the same machine collides. `STACKBONE_DEV_PORT_OFFSET` shifts the whole set: the API port, and the Postgres, Redis and MinIO containers behind it. ```sh STACKBONE_DEV_PORT_OFFSET=1 stackbone dev # API on :4252, Postgres on :5443, … ``` Each step of the offset is worth 10 ports. `0` (the default) keeps the ports you already know. An explicit `--port` always wins for the API port, but the containers still shift, so pass the offset rather than only overriding `--port`. `stackbone dev` **also injects** the env vars your agents and workflows read through the ambient `stackbone` client (`STACKBONE_POSTGRES_URL`, `WORKFLOW_REDIS_URL`, `STACKBONE_AGENT_ID`, …). See [SDK integration → how the CLI connects to the SDK](/docs/sdk/reference/cli-integration#how-the-cli-connects-to-the-sdk). ## State on disk ### Per-machine (global) Lives under `~/.stackbone/`: | Path | What it stores | Permissions | | -------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------- | | `~/.stackbone/credentials.json` | Map keyed by control-plane URL → `SessionInfo`. Lets one user stay logged into local + staging + prod simultaneously; only one is `active` at a time. | `0600` (owner read/write). | | `~/.stackbone/config.json` | Global preferences: `defaultApiUrl`, `telemetry` (opt-in PostHog, default `false` until V1). | `0644`. | | `~/.stackbone/update-check.json` | The newest published CLI version seen on the last registry check, and when that check ran. Only ever read to print the upgrade banner. | `0600` (owner read/write). | ### Per-machine (cache) | Path | What it stores | | ------------------------- | --------------------------------------------------- | | `~/.cache/stackbone/bin/` | Auto-downloaded binaries (today: `frpc-`). | ### Per-project Lives at the project root: | Path | Tracked? | What it stores | | ---------------------------------------------------------------------------- | ------------------------------------------------ | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `stackbone.config.ts` | **Optional** | An _optional_ workflow + build override. By default `stackbone dev` **derives** the workspace registry by convention (scanning every `deep-agents//index.ts` and every `workflows/.workflow.ts` file), so most projects need no config file at all. When this file is present (default-exporting `defineWorkspace({ agents, workflows })` from `@stackbone/sdk`) its `workflows` **win** over the convention scan; deep agents keep coming from the `deep-agents/` scan unless you also list them under `deepAgents`, which you only need to hide one from Studio with `$internal: true`. `agents` and `workflows` are required fields: keep `agents: []`. It also carries `build.external` (see [Keeping a package out of the bundle](#keeping-a-package-out-of-the-bundle)). See [Workflows](/docs/sdk/workflows/overview). | | `.stackbone/project.json` | **No** (gitignored by `stackbone init`) | `{ schemaVersion, organizationId, agentId, controlPlaneUrl }`, plus `localDevInstallationId` once `stackbone dev` has registered one. Identifies which organization this workspace points at, and which installation a verb targets when you pass no `--agent`. | | `.stackbone/dev/session.json` | **No** (gitignored) | Written by `stackbone dev` while it runs: where the emulator listens and the connection string of the dev database. It is how a verb with no `--agent` finds the local installation, and how `stackbone db migrate` finds the database without you exporting anything. | | `.stackbone/*.d.ts` | **No** (gitignored) | Generated editor types (`config.d.ts`, `agents.d.ts`, `workflows.d.ts`, `connect.d.ts`) that give the ambient `stackbone` client typed autocompletion. Regenerated by `stackbone dev`; `config.d.ts` also by `stackbone config types` (no control plane needed). | | `stackbone.config.json` | **Yes** | `{ schemaVersion: 1, studio: { corsOrigins } }`, the team-shared knobs. Reviewable in PRs. **Different file** from `stackbone.config.ts` above: the `.json` holds CLI/Studio knobs, the `.ts` holds the workspace manifest. | | `agent.yaml` | **Yes** | Manifest. See [agent.yaml reference](/docs/cli/reference/agent-yaml). | | `.claude/`, `.windsurf/`, `.agents/`, … | **No** (gitignored by `stackbone init` / `link`) | Per-coding-agent skill directories the upstream `skills` registry materialises. See [Agent skills overview](/docs/home/get-started/coding-agents). | | `.mcp.json`, `.cursor/mcp.json`, `.vscode/mcp.json`, `.gemini/settings.json` | **Yes** | The `stackbone-docs` MCP server entry `init` / `link` write for the coding agents you tick. Committable on purpose: a teammate who clones the repository inherits the documentation server. See [coding agents](/docs/home/get-started/coding-agents). | ### Keeping a package out of the bundle `stackbone dev` and `stackbone build` compile your deep agents the same way: each `deep-agents//index.ts` is bundled into a single `index.mjs` next to it, with your own dependencies **inlined**. The local run and the deployed container behave alike because production resolves no dependencies a second time. A few packages break when you bundle them: ones with a native binary, a WASM asset, or that read their own files from disk at runtime. Name those in `build.external` and they stay bare imports, resolved from the box's `node_modules` at boot: ```ts // stackbone.config.ts import { defineWorkspace } from '@stackbone/sdk'; export default defineWorkspace({ agents: [], workflows: [], build: { external: ['sharp'] }, }); ``` The list applies to **both** paths. `stackbone build` records the full effective set in the bundle manifest and warns about it at the end of the build, so whoever packages the image knows which packages to install alongside it. Nothing installs them for you. The image carries a `node_modules` decided at image build time, so add these packages yourself, at the same versions this workspace uses. Miss one and the agent fails to start with `ERR_MODULE_NOT_FOUND`. `@stackbone/sdk`, `deepagents` and `@langchain/*` are always external, because exactly one copy of each has to exist per process. You never list them. The compiled `index.mjs` is a build artifact: `stackbone init` already gitignores `deep-agents/*/index.mjs`. ## Output contract ### Human mode (default) Free-form text on stdout. May span multiple lines, may include `@clack/prompts` chrome and ANSI colour codes. **Never parse this.** Logging (debug, trace, deprecation warnings) goes to stderr through Pino regardless of mode. ### JSON mode Trigger it with `--json` or `STACKBONE_JSON=1`. Every command emits a single JSON line on stdout wrapped in: ```jsonc // success { "schema_version": 1, ...payload } ``` Errors go on stderr and include the original exit code: ```jsonc { "schema_version": 1, "error": { "code": "auth", "message": "No active session for https://api.stackbone.ai", "suggestion": "Run `stackbone login`", }, "exit_code": 2, } ``` `schema_version` is an integer that **only** bumps on backward-incompatible shape changes. If a coding agent sees a value greater than the one it was written against, it should refuse to parse and surface the version mismatch. ### Paginated lists A cursor-paginated list command (`runs list`, `hitl list`, the `storage`, `rag` and `db table` listings) takes `--limit ` and `--cursor `, and its JSON payload carries an `items` array plus `nextCursor` and `prevCursor` (either may be `null`). To walk forward, pass the previous page's `nextCursor` value back as `--cursor`. Some lists add domain-specific fields alongside `items` (for example `common_prefixes` on a storage listing); on a paginated list the three keys are always present and always shaped the same way. A short list (`stackbone list`, `stackbone workflows list`) returns everything at once, with no cursor keys. ```jsonc { "schema_version": 1, "items": [ /* … */ ], "nextCursor": "eyJ…", "prevCursor": null, } ``` ## Exit codes | Code | Name | When | | ---- | ----------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `0` | ok | Command succeeded. | | `1` | generic | Catch-all error. Anything that isn't auth / project / not-found / permission. | | `2` | auth | Not logged in or session expired. Protected commands never auto-trigger login: run `stackbone login` first. | | `3` | no project | The command needs a target it can't resolve: the cwd has no `.stackbone/project.json`, or it targets the local-dev agent but `stackbone dev` is not running (`dev_not_running`). | | `4` | not found | The target does not exist: an unknown workspace, agent, run or object, or an agent with no registered deployment (`runtime_not_configured`). | | `5` | permission denied | Caller is authenticated but unauthorised for the resource. | `error.code` in the JSON envelope carries the same decision at a finer grain. Five names match the rows above one for one (`generic`, `auth`, `no_project`, `not_found`, `permission`). The rest fold into one of those exit codes and keep a name of their own, so a caller can tell two failures with the same integer apart: | `error.code` | Exit | Raised when | | ------------------------------ | ---- | ----------------------------------------------------------------------- | | `auth_required` | `2` | The dev tunnel needs a session and there is none. | | `dev_not_running` | `3` | The verb targets your local installation but `stackbone dev` is not up. | | `runtime_not_configured` | `4` | The agent has no registered deployment, so no runtime can answer. | | `workspace_tunnel_cap_reached` | `5` | Your workspace already holds the maximum number of live tunnels. | | `unsupported_platform` | `1` | The CLI started in a native Windows shell (PowerShell or CMD). | | `tunnel_relay_unhealthy` | `1` | The tunnel relay is down and cannot grant a tunnel. | | `cli_upgrade_required` | `1` | The control plane refuses this CLI version. Upgrade it. | | `eval_below_threshold` | `1` | `stackbone eval` ran a suite to the end and it missed the pass mark. | Branch on the integer for the coarse answer, on `error.code` when you need the distinction. Neither needs the message parsed: ```sh # In a shell script if ! out=$(stackbone whoami --json 2>err); then case "$(jq -r '.error.code' < err)" in auth) stackbone login ;; not_found) echo "stale session" ;; *) echo "unexpected: $out" ; exit 1 ;; esac fi ``` The six exit codes are stable across CLI releases, so scripts and coding agents can pin to them. A new `error.code` name can appear inside an exit code that already exists, so treat a name you do not recognise as its integer. ## Putting it together A coding-agent-friendly invocation looks like: ```sh STACKBONE_JSON=1 stackbone metadata ``` It produces one line describing the organization state. ## Not on this page: the `stackbone config` document This page is about **CLI and on-disk configuration**: flags, env vars, and the files that live in your project. It is **not** about the versioned `AGENT_CONFIG` document your running agents and workflows read at runtime through the ambient client (`stackbone.config.get(...)`). That runtime config is its own surface, edited with the `stackbone config` command family (`get`, `set`, `versions`, `rollback`, `types`) and shaped by a `config.schema.ts` in your project. See the [config module reference](/docs/sdk/platform/config).