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¶
runic init [DIRECTORY] [--force]
Scaffold a new runic migration environment.
Arguments
DIRECTORYDirectory to create (default:
runic).
Options
--forceOverwrite 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 defaultrunic; commit this file so the rest of the team can omit--config)
Example
$ 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¶
runic revision -m MESSAGE [OPTIONS]
Create a new migration revision script.
Required options
-m,--message TEXTShort description used in the filename and docstring.
Options
--config PATHPath to
env.py(default:runic/env.py).--head TEXTExplicitly set
down_revisionto this revision ID instead of the current head.--rev-id TEXTUse a specific revision ID instead of a random hex string.
--branch-label TEXTAssign a branch label to this revision (stored in
branch_labels).--depends-on TEXTAdd a cross-branch dependency (may be repeated).
--autogenerateDiff the live schema against
target_manifestset inenv.pyand fill inupgrade/downgradebodies automatically. Requires a database connection andtarget_manifestto be set. See Autogenerate.--previewPrint the revision file content that would be created without writing it to disk. Useful for reviewing the generated script before committing.
$ runic revision --preview -m "add order index" # Would create: a1b2c3d4 """add order index ... """ ...
--formatRun
ruff formaton the generated file after creation (requires ruff onPATH).
Example
$ 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¶
runic upgrade [TARGET] [--config PATH] [--preview]
[--validate-on-migrate] [--installed-by TEXT]
Apply migrations up to TARGET (default: head).
Arguments
TARGETRevision ID, unique prefix,
head(default), or relative+N.
Options
--config PATHPath to
env.py.--previewPrint operations without executing them. Version node is not updated.
--validate-on-migrateBefore 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=Trueinenv.py(the default).--installed-by TEXTOverride the user/system recorded as having applied this upgrade. If omitted, the value is resolved from the
RUNIC_INSTALLED_BYenvironment variable, then falls back to the OS username. Has no effect whentrack_installed_by=Falseinenv.py.
Examples
$ 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¶
runic downgrade [TARGET] [--config PATH] [--force] [--preview]
Revert migrations down by one step, or to TARGET.
Arguments
TARGETOptional. Revision ID, unique prefix,
base, or relative-N. Defaults to-1(undo the most recently applied revision).
Options
--config PATHPath to
env.py.--forceCross
irreversible = Truemarkers.--previewPrint operations without executing them.
Examples
$ 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¶
runic current [--config PATH]
Print the currently applied revision ID and message.
Options
--config PATHPath to
env.py.
Output
$ runic current
7b3d9e2f — add email fulltext index
$ runic current
<none> # no revision applied
history¶
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 PATHPath to
env.py.--verboseInclude
create_dateanddown_revisionfor each entry.--range START:ENDRestrict 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
$ 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
Note
(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¶
runic heads [--config PATH]
Print all head revisions (revisions not referenced as down_revision by
any other revision).
Example
$ runic heads
7b3d9e2f add email fulltext index (single head)
When multiple heads exist (a branch was created):
$ runic heads
c1d2e3f4 add vector index (MULTIPLE HEADS — use merge to resolve)
7b3d9e2f add email index (MULTIPLE HEADS — use merge to resolve)
branches¶
runic branches [--config PATH]
Print every branch-point revision — revisions that two or more other
revisions declare as their down_revision.
Example
$ runic branches
3f9a12c1 add person email index ['7b3d9e2f', 'c1d2e3f4']
stamp¶
runic stamp TARGET [--config PATH] [--purge]
Set the version pointer without running any migration code.
Arguments
TARGETRevision ID,
base(clear the pointer), orheads(stamp all current heads at once).
Options
--config PATHPath to
env.py.--purgeClear the existing version node before stamping.
Examples
$ runic stamp 3f9a12c1
Stamped: 3f9a12c1
$ runic stamp base
Stamped: <none>
$ runic stamp heads # after a merge, stamp both heads
Stamped: heads
show¶
runic show REV [--config PATH]
Print full metadata for a single revision.
Arguments
REVRevision ID or unique prefix.
Example
$ 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¶
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
REVRevision ID or unique prefix to test.
Options
--config PATHPath to
env.py. Used to obtain the database connection when--urlis not given.--url TEXTFalkorDB URL (e.g.
falkor://localhost:6379). Takes precedence overenv.py.--graph TEXTGraph name when using
--url(default:test).
Output
$ 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¶
runic merge R1 R2 -m MESSAGE [--config PATH] [--branch-label LABEL]
Create a merge revision combining two branch heads. See Branching and Merging.
Arguments
R1,R2Revision IDs or unique prefixes of the two heads to merge.
Required options
-m,--message TEXTDescription for the merge revision.
Options
--config PATHPath to
env.py.--branch-label TEXTBranch label for the resulting merge revision.
Example
$ runic merge 7b3d9e2f c1d2e3f4 -m "merge feature-x into main"
Created revision: runic/versions/fa2b3c4d_merge_feature_x_into_main.py
validate¶
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 PATHPath to
env.py.
Output
# 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¶
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
SCRIPTOne or more
.pyfiles.
Options
--config PATHPath to
env.py.
Example
$ 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¶
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.
LOCALCount local revision files and list heads. Does not require a database connection — safe for offline or CI use.
REMOTEShow only what the database knows (the currently applied revision ID). Requires a database connection.
Options
--config PATHPath to
env.py.--mode TEXTCOMPARE|LOCAL|REMOTE(default:COMPARE).
Examples
# 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¶
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 PATHPath to
env.py.
Example
# 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.