stackbone db
This page has two halves with two different targets. The Explorer verbs follow the usual target resolution and pagination rules; every verb here emits the shared JSON envelope under
--json.
The stackbone db ... family runs the same migration engine that
stackbone dev runs on boot, so you can drive it from the
shell. migrate up is advisory-locked and atomic per file: a failed file rolls
back on its own and leaves the journal at the last good entry. The journal
records every applied tag.
The two halves target different databases. The migrate verbs run the
migration engine against the workspace database (the connection comes from
STACKBONE_POSTGRES_URL, or is discovered from a running stackbone dev
session for this project) and they take no --agent. The read-only Explorer
verbs (query, schemas, table) talk to a running installation's runtime
and do take --agent, like the rest of the runtime surface.
By default the schema lives at src/schema.ts and migrations under
.stackbone/migrations. A workspace's agents and workflows all talk to the one
install Postgres, so they share this root schema. An optional root agent.yaml
can override either path (database.schema, database.migrations). See
Project manifests. agent.yaml is optional here: with
none present the migrate verbs use the convention defaults.
The verbs that talk to the database (migrate up and migrate status) read the
connection string from STACKBONE_POSTGRES_URL when it is set. If it is not
set, they discover it from a running stackbone dev session for this project:
dev records its database connection while it runs, so you do not have to
export the variable in a separate shell. With neither the variable set nor a
live dev session the command exits 3 (no project).
| Command | Purpose |
|---|---|
stackbone db migrate up |
Apply every pending migration (advisory-locked, atomic per file). Supports --target <tag>. |
stackbone db migrate create <name> |
Generate a new SQL migration by diffing src/schema.ts against the journal. |
stackbone db migrate status |
Classify each migration as applied, pending or drifted (informational). |
stackbone db query [sql] |
Run one read-only SELECT against the installation. SQL from the positional, --file <path> or stdin. |
stackbone db schemas |
List the schemas and tables visible to the installation, with row estimates. |
stackbone db table <schema> <table> |
Browse rows of one table with cursor pagination (--limit, --cursor, --order asc|desc). |
stackbone db migrate up
Apply every pending migration under .stackbone/migrations against the
workspace database. Uses an advisory lock so concurrent invocations are safe,
and the journal records each applied tag. Takes no --agent.
| Flag | Type | Description |
|---|---|---|
--target |
string | Stop after applying the migration with this tag (inclusive). Useful for staged rollouts. |
JSON payload
{
"schema_version": 1,
"applied": [{ "tag": "0001_init", "appliedAt": "2026-05-16T10:00:00Z" }],
"skipped": [{ "tag": "0000_bootstrap", "appliedAt": "2026-05-15T09:00:00Z" }],
}appliedAt is best effort: it comes from a second read of the journal. If that
read does not come back, the command emits the entry without the field rather
than failing.
Exit codes: 0 ok, 3 no project (no database connection,
STACKBONE_POSTGRES_URL unset and no running stackbone dev session to
discover it from), 1 generic (migration failure, atomic per-file rollback
already ran).
stackbone db migrate create
stackbone db migrate create <name> generates a new SQL migration by diffing
src/schema.ts against the migrations journal. Wraps drizzle-kit generate
with an in-memory config so you do not need a drizzle.config.ts in the
project. It never opens a database connection, so it works with no
STACKBONE_POSTGRES_URL and no running stackbone dev. Takes no --agent.
The name positional becomes the migration filename suffix. Must match
^[a-z0-9]+(?:_[a-z0-9]+)*$ (lowercase letters, digits and single underscores,
e.g. add_users, add_users_email).
Renames need an interactive terminal. A rename looks like one table or
column removed and another added. The migration engine cannot tell a rename from
a drop-and-create, so it asks you to confirm. Run stackbone db migrate create
in your terminal (without --json or --yes) so you can answer that prompt.
With --json, with --yes, in CI, or under the stackbone dev auto-migrate
watcher there is no one to answer. The command then fails with a message telling
you to re-run it in your terminal instead of writing no migration.
JSON payload
{
"schema_version": 1,
"migration_file": ".stackbone/migrations/0002_add_users.sql",
}Exit codes: 0 ok, 4 not found (the resolved schema file does not exist
on disk), 1 generic (invalid name, agent.yaml parse error, drizzle-kit
failure).
stackbone db migrate status
Classify each migration as applied, pending or drifted (informational
only: it reports drift instead of failing on it). Takes no --agent.
JSON payload
{
"schema_version": 1,
"entries": [
{ "tag": "0000_bootstrap", "state": "applied", "appliedAt": "2026-05-15T09:00:00Z" },
{ "tag": "0001_init", "state": "pending" },
],
}Exit codes: 0 ok (regardless of drift), 3 no project, 1 generic
(cannot reach the DB).
stackbone db query
stackbone db query [sql] runs one ad-hoc read-only SELECT against the
targeted installation. The SQL comes from the positional argument, then
--file <path>, then stdin, in that precedence order.
| Flag | Type | Description |
|---|---|---|
--agent |
string | Installation id to target. Defaults to the local-dev installation linked to this project. |
--file |
string | Read the SQL from this file instead of the positional argument. |
The installation's runtime enforces a single SELECT and rejects anything else
server-side. It runs the statement as a stackbone_viewer role inside a
transaction with a statement timeout. It also caps results: the runtime wraps a
query that does not end in its own LIMIT at 1000 rows, and sets
truncated: true when the query hits that ceiling.
JSON payload
{
"schema_version": 1,
"columns": [
{ "name": "id", "data_type": "integer" },
{ "name": "email", "data_type": "text" },
],
"rows": [{ "id": 1, "email": "a@b.com" }],
"truncated": false,
"duration_ms": 12,
}Exit codes: 0 ok · 3 no project or dev_not_running · 1 generic (no
SQL provided, unreadable or empty --file, non-SELECT SQL rejected by the
runtime).
stackbone db schemas
List the schemas and tables visible to the targeted installation, with cheap row estimates.
| Flag | Type | Description |
|---|---|---|
--agent |
string | Installation id to target. Defaults to the local-dev installation linked to this project. |
JSON payload
{
"schema_version": 1,
"schemas": [
{
"name": "public",
"tables": [{ "name": "users", "estimated_rows": 1200 }],
},
],
}estimated_rows is null when the runtime has no estimate for that table.
Exit codes: 0 ok · 3 no project or dev_not_running · 1 generic.
stackbone db table
stackbone db table <schema> <table> browses rows of one table with cursor
pagination. Both positionals must be unquoted Postgres identifiers (start with a
letter or underscore, then letters, digits or underscores, max 63); the CLI
validates them before making the network call.
| Flag | Type | Description |
|---|---|---|
--agent |
string | Installation id to target. Defaults to the local-dev installation linked to this project. |
--cursor |
string | Opaque cursor from a previous page (nextCursor). |
--limit |
number | Maximum number of rows to return (1-200, default 50). |
--order |
enum | Sort order on the cursor column: asc or desc (default asc). |
JSON payload Each column carries full metadata here, more than the name and
type db query returns.
{
"schema_version": 1,
"columns": [
{
"name": "id",
"data_type": "integer",
"is_nullable": false,
"is_primary_key": true,
"ordinal_position": 1,
},
],
"rows": [{ "id": 1, "email": "a@b.com" }],
"cursor_column": "id",
"nextCursor": "eyJpZCI6MX0",
"prevCursor": null,
}cursor_column names the primary-key column the cursor encodes. It is null
when the table has no single-column primary key and the browse fell back to
LIMIT/OFFSET pagination, so the order is best effort.
Exit codes: 0 ok · 3 no project or dev_not_running · 1 generic (bad
schema or table identifier, non-integer --limit, --order other than asc or
desc).