ctx status
ctx status compares each page three ways (your working file, its base, which is the version you last synced, and the server’s current version) and lists what to push, what to pull, what to resolve and what to fix. It also shows which server, repo and identity this project uses.
When you’d reach for it
Section titled “When you’d reach for it”It is the first thing to run when something isn’t syncing: the top half answers “is this project pointed at the right thing?” and the rest answers “what is out of step, and what do I run about it?”. Run it before a push in an unfamiliar checkout, and after a teammate says they pushed.
Online, it fetches one listing of the repo’s pages per run, plus a few page reads only when base snapshots need repairing. --offline makes no request at all, which is what you want in a script that runs often or on a plane.
Synopsis
Section titled “Synopsis”ctx statusctx status --offlinectx status --jsonctx status --json --offline| Flag | Type | Default | Description |
|---|---|---|---|
--json | bool | false | print the ctx.status.v1 document |
--no-drift | bool | false | alias of --offline |
--offline | bool | false | make no network request |
--json prints the ctx.status.v1 document instead of the summary: every page with its state and its local, base and server sides, plus the counts, the server’s capabilities, the pending agent captures and any warnings. It follows the rules on JSON output and exit codes.
--offline makes no network request, takes no lock and writes nothing. See Online and offline.
--no-drift is the older name of --offline and still works. It is hidden from ctx status --help; the table above lists it only because the docs generator does not filter hidden flags.
ctx status also accepts the global flag:
| Flag | Type | Default | Description |
|---|---|---|---|
--root | string | project root directory (default: current directory) |
Page states
Section titled “Page states”Every page is in exactly one of nine states. The human output groups the ones that need you into sections, each followed by the command that deals with them; the VS Code extension shows the same states as Source Control groups.
| State | Meaning | Listed under | VS Code group |
|---|---|---|---|
unchanged |
Your file equals its base, and the server is still at that version. | (counted only) | none |
modified |
You changed the file; the server is still at your base. | TO PUSH |
Changes |
new |
A page you created that has never been synced, or one the server no longer has (note missing_on_server; your next push re-creates it). |
TO PUSH |
New |
incoming |
You have not touched the file, and the server has a newer version. | TO PULL (“changed on server”) |
Incoming |
incoming_new |
A page on the server that you do not have and never synced. | TO PULL (“new on server”) |
Incoming |
conflict |
Changed on both sides since the base (both_modified), or created on both sides with no base at all (both_added). |
CONFLICTS |
Conflicts |
deleted_local |
You deleted a synced page. Deletes are not synced: push never sends it and pull never brings it back. | DELETED LOCALLY |
Changes |
invalid |
The file does not parse, fails validation, or sits at the wrong path for its slug and type. It is never pushed. | INVALID (never pushed) |
Invalid |
unverified |
The base snapshot is missing or stale and could not be repaired on this run. | UNVERIFIED |
none (a decoration in the tree) |
The counts add two totals: ahead is modified plus new, and behind is incoming plus incoming_new. In the extension’s badge, deleted_local, invalid and unverified are never counted.
What to run:
CONFLICTS:ctx resolve <path> --oursto keep yours,--theirsto take the server’s. Seectx resolve.TO PUSH:ctx push.TO PULL:ctx pull.INVALID: fix the file (the line shows which check failed), then runctx statusagain.UNVERIFIED:ctx repair. Until the base is verified, push skips these pages, and pull only adopts one whose file already equals the server version.DELETED LOCALLY:ctx resolve <path> --theirsrestores the page.
Reading the human output
Section titled “Reading the human output”The first line is the project’s connection state: ● connected when everything works, or ○ with the reason it doesn’t: not authenticated, server unresolved, no repo configured, token rejected, server unreachable, server error, config unreadable, or offline with --offline.
── REMOTE
server: the server push and pull will use, followed by where that choice came from:(from credentials),(from config)or(from default). See Which server it reports.repo: therepo_idfrom.contexo/config.json, or(none — run 'ctx remote set-repo <id>').user: the name and email stored with your credentials, which the server records as the author of your pushes.auth: the auth state and the kind of token. The state isauthenticatedwhen the server accepted the token on this run,rejectedwhen it answered 401, anduncheckedwhen no request was made (offline) or the request failed for another reason; with no token at all the line readsno (run 'ctx auth login'). The token kind ispat(actxp_personal access token),session(from the browser login),legacy, orinvite_key, which belongs toctx joinand will not work for sync.
When the server cannot be used, a ▲ server: line explains why and a try line names the fix.
── KNOWLEDGE
pages: the total, then the non-zero states, most urgent first.incomingandincoming_neware counted together asincoming.last pull: the short sha of the server HEAD at your last pull, or(never).captures: how many agent sessions are pending distillation, and how many of them are fresh (written to in the last 6 hours). When a fresh one would makectx pushrefuse your changed concept or analysis pages, a warning says so; see the distill guard.
Warnings follow, then one section per group that needs action: CONFLICTS, TO PUSH, TO PULL, INVALID (never pushed), UNVERIFIED, DELETED LOCALLY (deletes are not synced). When there is nothing to do, the last line is ✔ in sync with the server, or ✔ no local changes (server not checked) when status could not or did not ask the server.
Outside a project, human status prints Not initialized (run 'ctx init') and exits 0. With --json the same case is a not_initialized envelope and exit 5.
Online and offline
Section titled “Online and offline”Online (the default, when you are logged in and a repo is configured), status lists the repo once: GET /v1/repos/<repo>/pages, the full listing of the repo’s pages with each page’s last commit. There is no caching and no conditional request; every online status fetches the listing fresh. The server’s version and capabilities come from the headers of that same response.
With the listing in hand, status also repairs base snapshots that are missing or stale, the same repair ctx repair runs. Inside status it is capped at 20 seconds and is skipped, never waited for, when another ctx process holds the lock. Pages it could not verify stay unverified, and a base_repair_skipped warning names them. This repair is the only thing online status ever writes, and it writes only under .contexo/.sync/.
If the listing fails (a network error, a 401, a 5xx), status does not fail: it reports the problem under remote.error in the JSON, or as the ▲ server: line, carries on as if offline, and exits 0. A repo the server does not have yet is not an error either: remote.repo_exists is false.
Offline (--offline, or when the server cannot be used at all), status makes no request, takes no lock and writes nothing. remote.source is none, and the server’s side of a page is unknown, so incoming and incoming_new cannot be detected. The one thing status still knows about the server is the conflicts ctx recorded earlier: a push refused with a 409, a pull that skipped a page as a conflict, or a repair that found one. Those pages stay conflict offline, and their remote side carries only the server’s sha. There is no remote cache: nothing else about the server survives between runs.
When the remote cannot be used, remote.error holds the first of these that applies: not_authenticated (no credentials), server_unresolved (global credentials without a server_url) or repo_not_configured (no repo_id), each with the command that fixes it.
Status itself fails only when there is no .contexo/ (not_initialized, exit 5 with --json) or the local state cannot be read at all.
Which server it reports
Section titled “Which server it reports”Status reports the same server that push and pull use, chosen like this:
- Credentials are read from the project’s
.contexo/credentials.json, otherwise from your global credentials. - If the credentials carry a
server_url, that server is used (from credentials). - Project credentials without one fall back to
server_urlin.contexo/config.json(from config). - Global credentials without one are not sent anywhere: the server is unresolved (
server_unresolved; runctx auth login --server <url>). - With no token at all, status shows
config.json’sserver_url, or the hosted default (from default), and sends nothing.
When .contexo/config.json names a different server than the one your credentials belong to, ctx keeps using the credentials’ server and never sends your token to the other one. The REMOTE block prints a warning naming both URLs and how to log in to the configured server if you trust it, and --json carries the warning server_url_mismatch.
Examples
Section titled “Examples”Before ctx init has run:
ctx statusNot initialized (run 'ctx init')Freshly initialized, not logged in. The server shown is the hosted default, and nothing was asked of it:
ctx statuscontexo ○ not authenticated
── REMOTE
server https://api.contexo.live (from default)
repo (none — run 'ctx remote set-repo <id>')
auth no (run 'ctx auth login')
▲ server: status: not authenticated
try ctx auth login
── KNOWLEDGE
pages 0
last pull (never)
captures none pending
✔ no local changes (server not checked)Logged in, one page pushed, nothing to do:
ctx statuscontexo ● connected
── REMOTE
server https://contexo.example.com (from credentials)
repo acme-api
user dev <dev@example.com>
auth authenticated (legacy)
── KNOWLEDGE
pages 1 · 1 unchanged
last pull (never)
captures none pending
✔ in sync with the serverAfter a teammate pushed changes to two pages you had also edited, plus a new page. Two conflicts with the commands that settle them, and one page to pull:
ctx statuscontexo ● connected
── REMOTE
server https://contexo.example.com (from credentials)
repo acme-api
user dev <dev@example.com>
auth authenticated (legacy)
── KNOWLEDGE
pages 4 · 2 conflict · 1 incoming · 1 unchanged
last pull a4b2c1d3
captures none pending
── CONFLICTS
wiki/concepts/stripe-subscription.md both_modified
wiki/entities/billing-worker.md both_modified
keep yours ctx resolve <path> --ours
take theirs ctx resolve <path> --theirs
── TO PULL
wiki/concepts/webhook-retries.md new on server
run ctx pullThe JSON document for a project with one modified and one new page:
ctx status --json{
"schema": "ctx.status.v1",
"generated_at": "2026-07-28T09:14:07Z",
"offline": false,
"root": "~/projects/acme-api",
"contexo_dir": "~/projects/acme-api/.contexo",
"project": {
"repo_id": "acme-api",
"config_server_url": "https://contexo.example.com",
"dashboard_url": "",
"last_pull_sha": "a4b2c1d3e5f60718293a4b5c6d7e8f9012345678"
},
"server": {
"url": "https://contexo.example.com",
"url_source": "credentials",
"version": "dev",
"capabilities_known": true,
"capabilities": [
"pages.at",
"push.expect_absent",
"write.path_sha"
]
},
"auth": {
"state": "authenticated",
"credentials_source": "project",
"token_kind": "legacy",
"user_name": "dev",
"user_email": "dev@example.com"
},
"remote": {
"source": "live",
"fetched_at": "2026-07-28T09:14:07Z",
"repo_exists": true,
"head": "b7e9f204c8a1d35e6f70819a2b3c4d5e6f708192",
"page_count": 1
},
"counts": {
"unchanged": 0,
"modified": 1,
"new": 1,
"incoming": 0,
"incoming_new": 0,
"conflict": 0,
"deleted_local": 0,
"invalid": 0,
"unverified": 0,
"ahead": 2,
"behind": 0
},
"pages": [
{
"path": "wiki/concepts/stripe-subscription.md",
"state": "modified",
"type": "concept",
"slug": "stripe-subscription",
"tags": [
"stripe",
"billing",
"proration"
],
"author": "dev",
"agent": "claude",
"updated": "2026-07-24T16:31:00Z",
"reasoning_summary": "How trials, proration and webhooks fit together in acme-api.",
"local": {
"file": "wiki/concepts/stripe-subscription.md",
"hash": "sha256:d2d5406cf1ff46eee2577caf20c3509270c93ea29c4a765fe3227446cbe55cde"
},
"base": {
"sha": "b7e9f204c8a1d35e6f70819a2b3c4d5e6f708192",
"hash": "sha256:460dd05a3b5e41a66eff13f4af9ff5e88390e98b55645cd9ba093768d22423e5",
"synced_at": "2026-07-28T09:14:07Z",
"stale": false
},
"remote": {
"sha": "b7e9f204c8a1d35e6f70819a2b3c4d5e6f708192",
"author": "dev",
"email": "dev@example.com",
"time": "2026-07-28T09:14:07Z",
"message": "ctx push (1 pages)"
},
"notes": []
},
{
"path": "wiki/entities/billing-worker.md",
"state": "new",
"type": "entity",
"slug": "billing-worker",
"tags": [
"billing"
],
"author": "dev",
"agent": "claude",
"updated": "2026-07-25T11:20:00Z",
"local": {
"file": "wiki/entities/billing-worker.md",
"hash": "sha256:8bae15f3fa732a04b87f2c50d5e25834e1f0009bce68dde191409c51a947b828"
},
"notes": []
}
],
"captures": {
"pending": 0,
"fresh": 0,
"blocks_push": false,
"sessions": []
},
"warnings": []
}The same project offline, after the conflict and a pull. No request was made: remote.source is none, the server capabilities are unknown, and the two conflicts are still known from what ctx recorded, with only the server’s sha on their remote side:
ctx status --json --offline{
"schema": "ctx.status.v1",
"generated_at": "2026-07-28T09:14:07Z",
"offline": true,
"root": "~/projects/acme-api",
"contexo_dir": "~/projects/acme-api/.contexo",
"project": {
"repo_id": "acme-api",
"config_server_url": "https://contexo.example.com",
"dashboard_url": "",
"last_pull_sha": "e5a3b9c807162534435261708f9e0d1c2b3a4958"
},
"server": {
"url": "https://contexo.example.com",
"url_source": "credentials",
"capabilities_known": false,
"capabilities": []
},
"auth": {
"state": "unchecked",
"credentials_source": "project",
"token_kind": "legacy",
"user_name": "dev",
"user_email": "dev@example.com"
},
"remote": {
"source": "none",
"page_count": 0
},
"counts": {
"unchanged": 2,
"modified": 0,
"new": 0,
"incoming": 0,
"incoming_new": 0,
"conflict": 2,
"deleted_local": 0,
"invalid": 0,
"unverified": 0,
"ahead": 0,
"behind": 0
},
"pages": [
{
"path": "wiki/concepts/stripe-subscription.md",
"state": "conflict",
"conflict_kind": "both_modified",
"type": "concept",
"slug": "stripe-subscription",
"tags": [
"stripe",
"billing",
"proration"
],
"author": "dev",
"agent": "claude",
"updated": "2026-07-24T16:31:00Z",
"reasoning_summary": "How trials, proration and webhooks fit together in acme-api.",
"local": {
"file": "wiki/concepts/stripe-subscription.md",
"hash": "sha256:b466741e47980041e9d1dbc835cd11ab7fbc556de2355bbeb937d7f8ccfbe2d6"
},
"base": {
"sha": "d2f0e617a3b5c7d9e1f2031425364758697a8b9c",
"hash": "sha256:d2d5406cf1ff46eee2577caf20c3509270c93ea29c4a765fe3227446cbe55cde",
"synced_at": "2026-07-28T09:14:07Z",
"stale": false
},
"remote": {
"sha": "f0c47d2196385a7b4c3d2e1f0a9b8c7d6e5f4a3b"
},
"notes": []
},
{
"path": "wiki/concepts/webhook-retries.md",
"state": "unchanged",
"type": "concept",
"slug": "webhook-retries",
"tags": [
"billing"
],
"author": "bob",
"agent": "claude",
"updated": "2026-07-26T08:05:00Z",
"local": {
"file": "wiki/concepts/webhook-retries.md",
"hash": "sha256:c766c671da23b495fe1cca2f37d833f77fef8d9f1514a9a851641d9d5be36526"
},
"base": {
"sha": "7d25c6bf018a3e4d5c6b7a8990f1e2d3c4b5a697",
"hash": "sha256:c766c671da23b495fe1cca2f37d833f77fef8d9f1514a9a851641d9d5be36526",
"synced_at": "2026-07-28T09:14:07Z",
"stale": false
},
"notes": []
},
{
"path": "wiki/entities/billing-worker.md",
"state": "conflict",
"conflict_kind": "both_modified",
"type": "entity",
"slug": "billing-worker",
"tags": [
"billing"
],
"author": "dev",
"agent": "claude",
"updated": "2026-07-25T11:20:00Z",
"local": {
"file": "wiki/entities/billing-worker.md",
"hash": "sha256:e6a61f7de94e56acdcc074437d1700b4b9d6b8a06a140721eb5d9718718b9701"
},
"base": {
"sha": "c1d8a3f50e2b4c6d8f90a1b2c3d4e5f607182930",
"hash": "sha256:8bae15f3fa732a04b87f2c50d5e25834e1f0009bce68dde191409c51a947b828",
"synced_at": "2026-07-28T09:14:07Z",
"stale": false
},
"remote": {
"sha": "3b8e1a0947c2f6d85e90a1b2c3d4e5f60718293a"
},
"notes": []
},
{
"path": "wiki/entities/invoice-service.md",
"state": "unchanged",
"type": "entity",
"slug": "invoice-service",
"tags": [
"billing"
],
"author": "dev",
"agent": "claude",
"updated": "2026-07-26T09:40:00Z",
"local": {
"file": "wiki/entities/invoice-service.md",
"hash": "sha256:4eb24f13bfe77a17a43c54bfd0b0c716f87db53939f5362d791edfd84ead9d87"
},
"base": {
"sha": "e5a3b9c807162534435261708f9e0d1c2b3a4958",
"hash": "sha256:4eb24f13bfe77a17a43c54bfd0b0c716f87db53939f5362d791edfd84ead9d87",
"synced_at": "2026-07-28T09:14:07Z",
"stale": false
},
"notes": []
}
],
"captures": {
"pending": 0,
"fresh": 0,
"blocks_push": false,
"sessions": []
},
"warnings": []
}See also
Section titled “See also”ctx push: what to do aboutTO PUSHctx pull: what to do aboutTO PULLctx resolve: what to do aboutCONFLICTSctx repair: what to do aboutUNVERIFIED- JSON output and exit codes: the
--jsonrules and the error envelope - Drift and conflicts: base snapshots and how pages get out of step
