Skip to content

CLI Reference

The runic CLI is installed as the runic command. Every command accepts --config <path> (default: runic/env.py) to override the location of env.py.

Config auto-resolution — if the default runic/env.py does not exist, runic checks for a .runic marker file in the current directory and resolves the path from it. runic init <custom-dir> writes this file automatically.

Commands that do not require a database connection (init, revision, heads, branches, show, merge, info --mode LOCAL) read the script directory from --config's parent directory and never execute env.py.

Commands that do require a connection (upgrade, downgrade, current, history, stamp, test, check, validate, run, info) execute env.py which calls context.configure().


init

bash
runic init [DIRECTORY] [--force]

Scaffold a new runic migration environment.

Arguments

DIRECTORY Directory to create (default: runic).

Options

--force Overwrite the directory if it already exists.

Creates

  • <DIRECTORY>/env.py — connection script template
  • <DIRECTORY>/script.py.mako — migration file template
  • <DIRECTORY>/versions/ — empty directory for revision scripts
  • .runic — config pointer (written only when DIRECTORY is not the default runic; commit this file so the rest of the team can omit --config)

Example

bash
$ runic init
Created runic environment at runic/
  runic/env.py
  runic/script.py.mako
  runic/versions/

$ runic init migrations
Created runic environment at migrations/
  migrations/env.py
  migrations/script.py.mako
  migrations/versions/
  .runic  (config pointer commit this file)

$ runic init migrations --force   # overwrite existing directory

revision

bash
runic revision -m MESSAGE [OPTIONS]

Create a new migration revision script.

Required options

-m, --message TEXT Short description used in the filename and docstring.

Options

--config PATH Path to env.py (default: runic/env.py).

--head TEXT Explicitly set down_revision to this revision ID instead of the current head.

--rev-id TEXT Use a specific revision ID instead of a random hex string.

--branch-label TEXT Assign a branch label to this revision (stored in branch_labels).

--depends-on TEXT Add a cross-branch dependency (may be repeated).

--autogenerate Diff the live schema against target_manifest set in env.py and fill in upgrade/downgrade bodies automatically. Requires a database connection and target_manifest to be set. See autogenerate.

--preview Print the revision file content that would be created without writing it to disk. Useful for reviewing the generated script before committing.

```bash
$ runic revision --preview -m "add order index"
# Would create: a1b2c3d4
"""add order index
...
"""
...
```

--format Run ruff format on the generated file after creation (requires ruff on PATH).

Example

bash
$ runic revision -m "add person email index"
Created revision: runic/versions/3f9a12c1_add_person_email_index.py

$ runic revision -m "new feature index" --branch-label feature-x
Created revision: runic/versions/a1b2c3d4_new_feature_index.py

upgrade

bash
runic upgrade [TARGET] [--config PATH] [--preview]
              [--validate-on-migrate] [--installed-by TEXT]

Apply migrations up to TARGET (default: head).

Arguments

TARGET Revision ID, unique prefix, head (default), or relative +N.

Options

--config PATH Path to env.py.

--preview Print operations without executing them. Version node is not updated.

--validate-on-migrate Before applying any pending revisions, verify that the checksums of all already-applied scripts still match their stored values. Aborts if any mismatch is found. Requires track_checksums=True in env.py (the default).

--installed-by TEXT Override the user/system recorded as having applied this upgrade. If omitted, the value is resolved from the RUNIC_INSTALLED_BY environment variable, then falls back to the OS username. Has no effect when track_installed_by=False in env.py.

Examples

bash
$ runic upgrade               # apply all pending revisions
Upgraded to: 7b3d9e2f

$ runic upgrade 3f9         # apply up to a specific revision (prefix ok)
Upgraded to: 3f9a12c1ab4e

$ runic upgrade +1            # apply the next revision only
Upgraded to: 7b3d9e2f

$ runic upgrade --preview
CREATE RANGE INDEX: CREATE INDEX FOR (n:Person) ON (n.email) params=None

$ runic upgrade --validate-on-migrate --installed-by "ci-bot"
Upgraded to: head

downgrade

bash
runic downgrade [TARGET] [--config PATH] [--force] [--preview]

Revert migrations down by one step, or to TARGET.

Arguments

TARGET Optional. Revision ID, unique prefix, base, or relative -N. Defaults to -1 (undo the most recently applied revision).

Options

--config PATH Path to env.py.

--force Cross irreversible = True markers.

--preview Print operations without executing them.

Examples

bash
$ runic downgrade                  # undo last applied revision (default: -1)
Downgraded to: 3f9a12c1ab4e

$ runic downgrade base             # revert all
Downgraded to: base

$ runic downgrade 3f9              # revert to a specific revision (prefix ok)
Downgraded to: 3f9a12c1ab4e

$ runic downgrade -2               # undo the last two revisions
Downgraded to: 3f9a12c1ab4e

$ runic downgrade base --force     # force past irreversible markers

current

bash
runic current [--config PATH]

Print the currently applied revision ID and message.

Options

--config PATH Path to env.py.

Output

bash
$ runic current
7b3d9e2f add email fulltext index

$ runic current
<none>   # no revision applied

history

bash
runic history [--config PATH] [--verbose] [--range START:END]

Print all revisions, newest first. Requires a database connection — the currently applied revision is marked (head) in the output.

Options

--config PATH Path to env.py.

--verbose Include create_date and down_revision for each entry.

--range START:END Restrict output to an inclusive revision range. Either side may be omitted (--range :7b3d9e2f = from base to that revision; --range 3f9a12c1: = from that revision to head).

Example

bash
$ runic history
7b3d9e2f         (head)                add email fulltext index
3f9a12c1                               add person email index

$ runic history --verbose
7b3d9e2f         (head)                add email fulltext index
    create_date:   2026-05-30 10:00:00+00:00
    down_revision: 3f9a12c1ab4e

3f9a12c1                               add person email index
    create_date:   2026-05-30 09:00:00+00:00
    down_revision: None

INFO

(head) marks the currently applied revision in the database, not the tip of the file-based revision chain. Use runic heads to see which revision is at the tip of the chain.


heads

bash
runic heads [--config PATH]

Print all head revisions (revisions not referenced as down_revision by any other revision).

Example

bash
$ runic heads
7b3d9e2f  add email fulltext index  (single head)

When multiple heads exist (a branch was created):

bash
$ runic heads
c1d2e3f4  add vector index  (MULTIPLE HEADS use merge to resolve)
7b3d9e2f  add email index   (MULTIPLE HEADS use merge to resolve)

branches

bash
runic branches [--config PATH]

Print every branch-point revision — revisions that two or more other revisions declare as their down_revision.

Example

bash
$ runic branches
3f9a12c1  add person email index  ['7b3d9e2f', 'c1d2e3f4']

stamp

bash
runic stamp TARGET [--config PATH] [--purge]

Set the version pointer without running any migration code.

Arguments

TARGET Revision ID, base (clear the pointer), or heads (stamp all current heads at once).

Options

--config PATH Path to env.py.

--purge Clear the existing version node before stamping.

Examples

bash
$ runic stamp 3f9a12c1
Stamped: 3f9a12c1

$ runic stamp base
Stamped: <none>

$ runic stamp heads   # after a merge, stamp both heads
Stamped: heads

show

bash
runic show REV [--config PATH]

Print full metadata for a single revision.

Arguments

REV Revision ID or unique prefix.

Example

bash
$ runic show 3f9
Revision ID:   3f9a12c1ab4e
Revises:       <base>
Message:       add person email index
Create Date:   2026-05-30 09:00:00+00:00
Irreversible:  False
Snapshot:      False
Branch Labels: []
Depends On:    []
Path:          runic/versions/3f9a12c1ab4e_add_person_email_index.py

test

bash
runic test REV [--config PATH] [--url URL] [--graph GRAPH]

Round-trip test a revision: upgrade → downgrade → upgrade on an ephemeral copy of the graph, then report node/index/constraint counts at each phase.

Arguments

REV Revision ID or unique prefix to test.

Options

--config PATH Path to env.py. Used to obtain the database connection when --url is not given.

--url TEXT FalkorDB URL (e.g. falkor://localhost:6379). Takes precedence over env.py.

--graph TEXT Graph name when using --url (default: test).

Output

bash
$ runic test 3f9a12c1
runic test 3f9a12c1ab4e
─────────────────────────────────────────────
Phase A (upgrade):    ✓  nodes=0  indices=1  constraints=1
Phase B (downgrade):  ✓  nodes=0  indices=0  constraints=0
Phase C (idempotency):✓  nodes=0  indices=1  constraints=1
─────────────────────────────────────────────
PASSED

The test runs on a throw-away graph named <graph_name>__test_<rev_id>_<token> which is deleted regardless of whether the test passes or fails.


merge

bash
runic merge R1 R2 -m MESSAGE [--config PATH] [--branch-label LABEL]

Create a merge revision combining two branch heads. See branching.

Arguments

R1, R2 Revision IDs or unique prefixes of the two heads to merge.

Required options

-m, --message TEXT Description for the merge revision.

Options

--config PATH Path to env.py.

--branch-label TEXT Branch label for the resulting merge revision.

Example

bash
$ runic merge 7b3d9e2f c1d2e3f4 -m "merge feature-x into main"
Created revision: runic/versions/fa2b3c4d_merge_feature_x_into_main.py

validate

bash
runic validate [--config PATH]

Verify that the local files for all applied revisions still match the checksums recorded at apply-time. Exits 0 when all checksums are valid; exits 1 and lists mismatches otherwise.

Requires track_checksums=True in env.py (the default). Revisions applied before checksum tracking was introduced are silently skipped.

Options

--config PATH Path to env.py.

Output

bash
# All good:
$ runic validate
All checksums valid.

# A script was modified after being applied:
$ runic validate
  x 3f9a12c1ab4e (add person email index): checksum mismatch — script was modified after being applied
$ echo $?
1

run

bash
runic run SCRIPT [SCRIPT ...] [--config PATH]

Execute one or more Python migration scripts against the database without recording them in the migration chain. Useful for one-off operational tasks (data patches, manual seed loads) where you explicitly do not want a revision record.

Each SCRIPT must be a .py file that defines an upgrade(op) function. The function receives the same GraphOperations object as a normal migration.

Arguments

SCRIPT One or more .py files.

Options

--config PATH Path to env.py.

Example

bash
$ runic run patches/backfill_user_roles.py
Executed: backfill_user_roles.py

$ runic run patch_a.py patch_b.py
Executed: patch_a.py
Executed: patch_b.py

info

bash
runic info [--config PATH] [--mode MODE]

Show migration status. Three modes are available:

COMPARE (default) Compare the live database state against local revision files. Shows the current revision, how many revisions are applied vs total, and lists any pending migrations. Requires a database connection.

LOCAL Count local revision files and list heads. Does not require a database connection — safe for offline or CI use.

REMOTE Show only what the database knows (the currently applied revision ID). Requires a database connection.

Options

--config PATH Path to env.py.

--mode TEXTCOMPARE | LOCAL | REMOTE (default: COMPARE).

Examples

bash
# Default COMPARE view:
$ runic info
Database : my_graph
Current  : 7b3d9e2f  add email fulltext index
Applied  : 2 of 3
Pending  : 1

Pending migrations:
  c1d2e3f4  add vector index

# Offline — no database needed:
$ runic info --mode LOCAL
Local revisions : 3
Heads           : 1
  c1d2e3f4  add vector index

# Database state only:
$ runic info --mode REMOTE
Applied : 7b3d9e2f  add email fulltext index

check

bash
runic check [--config PATH]

Exit with code 1 if the live graph schema has drifted from the target_manifest defined in env.py. Exits 0 when the schema is up-to-date.

Intended for CI pipelines to catch uncommitted schema changes.

Options

--config PATH Path to env.py.

Example

bash
# Schema is up-to-date:
$ runic check
Schema up-to-date.

# Schema has drifted:
$ runic check
Pending schema changes (run `runic revision --autogenerate -m "..."` to generate):
  + op.create_range_index("Order", "placed_at")
$ echo $?
1

See autogenerate for how to configure target_manifest.

runic - Graph schema migrations and OGM for Cypher-based graph databases. · Impressum