Skip to content

ctx pull

ctx pull brings the server’s page changes into .contexo/ without ever overwriting a local edit. Pages you have not touched are updated, new server pages are created, and anything you changed is left alone and listed as skipped, with the command that deals with it.

At the start of a session, before you or your agent start editing pages, so you begin from what the team knows. Again whenever ctx status lists pages under TO PULL.

Pull is safe to run at any time. It is not a fetch-and-overwrite: a page you edited is never replaced, even when the server has a newer version. Such a page is skipped, and if both sides changed it, it becomes a conflict for ctx resolve to settle. Skips are normal results, and pull still exits 0.

Terminal window
ctx pull
ctx pull --json
FlagTypeDefaultDescription
--fullboolfalseaccepted for compatibility; pull always reconciles every page
--jsonboolfalseprint the ctx.pull.v1 JSON document

--json prints the ctx.pull.v1 document on stdout and nothing else. See JSON output and exit codes.

--full is accepted for compatibility and changes nothing: pull now compares every page with the server on every run. In human mode it prints a note saying so; with --json it is silent.

ctx pull also accepts the global flag:

FlagTypeDefaultDescription
--rootstringproject root directory (default: current directory)

Pull looks at every page’s state (see ctx status) and does one of three things.

Written. An incoming page (you have not changed it, the server has a newer version) is overwritten with the server’s version, keeping the file’s permissions. An incoming_new page (on the server, not here) is created. They appear under written with kind update or create.

Adopted. A page in conflict or unverified whose content already equals the server’s version is adopted: the server’s version becomes its base and the conflict, if any, is cleared. Your file is not touched. These appear under adopted.

Skipped. Everything pull will not write is listed under skipped with a reason, and the human output says what to do:

Reason Why What the human output says
modified You changed the page and the server did not. kept your local edits; run ctx push
conflict Both sides changed it (or both created it). conflicts with the server version; run ctx resolve <path>, then --theirs or --ours
unverified Its base could not be verified, and your file differs from the server’s version. base not verified; run ctx repair
invalid The file does not parse or validate. invalid page; fix it, then pull again
deleted_local You deleted it. Deletes are not synced, and pull never brings a page back on its own. deleted locally; ctx resolve <path> --theirs restores it
case_collision Two paths differ only by letter case, which some file systems cannot hold side by side. differs only by letter case from another page; rename one of them
changed_during_pull The file changed while pull was running, or the page vanished from the server mid-pull. changed during the pull; run ctx pull again

Only a conflict skip leaves a record behind: pull stores the server’s version of the page under .contexo/.sync/ as a conflict, so ctx status --offline and the VS Code extension still know about it later. The other skips write nothing.

unchanged and new pages are left alone.

The normal case when nothing changed on the server:

Terminal window
ctx pull
ctx pull
Already up to date

--full is still accepted, and says it is no longer needed:

Terminal window
ctx pull --full
ctx pull --full
note: --full is no longer needed; ctx pull always reconciles every page with the server
Already up to date

After a teammate pushed three pages, two of which you had also edited. The JSON run writes the one new page and skips the two conflicts; a second, human run shows the advice for each skipped page:

Terminal window
ctx pull --json
ctx pull
ctx pull --json, then ctx pull
{
  "schema": "ctx.pull.v1",
  "server_url": "https://contexo.example.com",
  "repo_id": "acme-api",
  "head": "e5a3b9c807162534435261708f9e0d1c2b3a4958",
  "written": [
    {
      "path": "wiki/concepts/webhook-retries.md",
      "sha": "7d25c6bf018a3e4d5c6b7a8990f1e2d3c4b5a697",
      "kind": "create"
    }
  ],
  "adopted": [],
  "skipped": [
    {
      "path": "wiki/concepts/stripe-subscription.md",
      "state": "conflict",
      "reason": "conflict",
      "server_sha": "f0c47d2196385a7b4c3d2e1f0a9b8c7d6e5f4a3b"
    },
    {
      "path": "wiki/entities/billing-worker.md",
      "state": "conflict",
      "reason": "conflict",
      "server_sha": "3b8e1a0947c2f6d85e90a1b2c3d4e5f60718293a"
    }
  ],
  "reindexed": true,
  "warnings": []
}

$ ctx pull
Pulled 0 page(s), adopted 0; HEAD=e5a3b9c8
Skipped 2 page(s):
  wiki/concepts/stripe-subscription.md: conflicts with the server version; run ctx resolve wiki/concepts/stripe-subscription.md --theirs|--ours
  wiki/entities/billing-worker.md: conflicts with the server version; run ctx resolve wiki/entities/billing-worker.md --theirs|--ours
  1. Checks that .contexo/ exists, then resolves the server, token and repo id. The server is the one your credentials belong to: their server_url wins; project credentials without one use server_url from .contexo/config.json; global credentials without one fail with server_unresolved (exit 5 with --json) rather than send your token to a URL taken from the project. When config.json names a different server, pull uses the credentials’ server and adds a server_url_mismatch warning.
  2. Takes .contexo/.sync/lock, waiting up to CONTEXO_LOCK_TIMEOUT (default 10 seconds) if another ctx process, often an agent’s push or pull, holds it. After that it fails with busy (exit 12).
  3. Lists the repo’s pages once, repairs missing or stale base snapshots over that listing (like ctx repair), and classifies every page, taking recorded conflicts into account.
  4. Fetches everything it needs before writing anything: the pages changed since your last pull in one request, and any still missing either in one more bulk request (when there are more than 8) or one by one, at most 4 at a time. The server’s bytes are cleaned of terminal escapes and other control codes, bidirectional overrides and zero-width characters, exactly as ctx show --at server prints them. If any fetch fails, nothing is written.
  5. Acts on each page in path order. Right before writing or adopting a page, it re-reads your file; if the file changed since step 3, the page is skipped as changed_during_pull.
  6. Saves the new bases, the conflict records and .contexo/.sync/state.json, including last_pull_sha (the server’s HEAD). When at least one page was written, it regenerates .contexo/index.md and .contexo/tags.md so the always-loaded index reflects what arrived; a failure there becomes a reindex_failed warning, not an error.

Every request carries the X-Contexo-Client header (ctx-cli, unless CONTEXO_CLIENT says otherwise), so the server’s activity feed shows what pulled.

With --json, each failure is a one-line envelope on stderr with the exit code shown; without it, a message and exit 1. The --json exit codes are 0, 4, 5, 7, 8, 11 and 12 (and 1 and 3).

pull: not authenticated, with try ctx auth login: no credentials. Run ctx login. Exit 4.

pull: server URL unresolved: the credentials have no server_url, with try ctx auth login --server <url>. Exit 5.

pull: repo not configured, with try ctx remote set-repo <id>: see ctx remote. Exit 5.

no .contexo/ directory in <root>: not a project. cd to its root, pass --root, or run ctx init. Exit 5.

forbidden (HTTP 403): your token works, but you are not a member of that repo. Ask an owner for an invite and run ctx join, or fix the id with ctx remote set-repo. Exit 4. From the real binary:

{"schema":"ctx.error.v1","exit_code":4,"error":{"code":"forbidden","message":"pull: sync: list pages (403): {\"error\":\"not a member of this repo\"}","advice":[],"retryable":false,"http_status":403}}

repo_not_found (HTTP 404): the server has no repo with that id. Exit 8.

busy: another ctx process held the lock past CONTEXO_LOCK_TIMEOUT. Exit 12; retry when it finishes.

network, server_error, rate_limited: exit 7, safe to retry. Nothing was written.