Troubleshooting
Top errors you’ll hit and how to fix them. Run ctx doctor first — it
catches most of these automatically and suggests fixes.
1. Hub unreachable (http://localhost:3001)
Section titled “1. Hub unreachable (http://localhost:3001)”Symptom: any ctx command that talks to the Hub fails immediately.
Exit code 69.
Cause: the Hub isn’t running, isn’t on the port you think it is, or is bound to a different interface.
Fix:
ctx serve --http # start it# ... or in another terminal / systemd service / dockerctx status # verifyIf you have the Hub running on a different port or host:
export CTX_SERVER=http://my-hub:3001ctx statusOr pass --server explicitly.
2. ctx: command not found
Section titled “2. ctx: command not found”Cause (macOS / Linux): ~/.local/bin isn’t on your PATH, or the
install script didn’t run to completion.
Fix (macOS / Linux):
export PATH="$HOME/.local/bin:$PATH"# add the above to your ~/.zshrc or ~/.bashrcVerify the binary exists:
ls -la ~/.local/bin/ctxCause (Windows): PATH changes made by the installer only take effect in new PowerShell windows. Your current shell still has the old PATH.
Fix (Windows): close your PowerShell window and open a fresh one. Or manually add to the current session:
$env:Path += ";$env:LOCALAPPDATA\ctxone\bin"Verify the binary exists:
Get-Item "$env:LOCALAPPDATA\ctxone\bin\ctx.exe"If missing, re-run the installer:
curl -sSL https://raw.githubusercontent.com/ctxone/ctxone/main/install.sh | sh3. No memories found for '<topic>' but you know the fact is there
Section titled “3. No memories found for '<topic>' but you know the fact is there”Cause A — wrong branch. You’re reading from main but the fact was
written to another branch.
ctx branches # see which branch has your factctx --branch <name> recall "<topic>"Cause B — search vs recall mismatch. ctx recall tokenizes the query
and drops stopwords. “the status” becomes ["status"] since “the” is a
stopword. Try a more specific single word or use ctx search which does
literal substring matching.
Cause C — the fact is on a pinned path. Pinned memories are split into
/title and /body fields. recall dedups these, but search doesn’t —
check both:
ctx search "<term>" # literalctx ls /memory/pinned # see pinned pathsCause D — the fact was forgotten. Check the commit history:
ctx log -n 100 | grep -i "<term>"If it was deleted, you can still see the old commit in blame but the
current state won’t have it.
4. ctx init wrote a config but Claude Code / Cursor still doesn’t see CTXone
Section titled “4. ctx init wrote a config but Claude Code / Cursor still doesn’t see CTXone”Cause A — the AI tool needs a restart. Most MCP clients load config on
startup. Restart Claude Code / Cursor / VS Code after running ctx init.
Cause B — wrong config scope. ctx init writes project-level configs by
default (.mcp.json in cwd). If you want global, add --global.
Cause C — path mismatch. Check where ctx init actually wrote:
ctx init --dry-runCopy the path. Open it in an editor. Verify the mcpServers.ctxone entry
points at your actual ctxone-hub binary.
Cause D — the Hub binary moved. If you reinstalled, the old .mcp.json
may point at a stale path. Re-run ctx init to refresh.
5. Ratio stuck near 1.0x — no savings
Section titled “5. Ratio stuck near 1.0x — no savings”Cause A — graph is tiny. With 5 facts total and recalling 4 of them, flat ≈ sent. Savings don’t kick in until the graph is bigger than what any given recall returns.
Cause B — overly broad recall. ctx recall "project" on a
project-heavy graph matches every fact. Try a more specific query.
Cause C — too much pinned content. If you’ve pinned several long docs,
pinned content alone eats the budget. Review with ctx pinned and unpin
(via ctx forget) anything that’s not actually critical.
See TOKEN_SAVINGS.md for the full breakdown.
6. branch not found: <name> when writing
Section titled “6. branch not found: <name> when writing”Cause: you tried to write to a branch that doesn’t exist yet. Branches must be created explicitly.
Fix:
ctx branch <name> # create itctx --branch <name> remember "..." # now writes workOr create it from a specific ref:
ctx branch <name> --from main7. ctx prime reports “No sections found in ”
Section titled “7. ctx prime reports “No sections found in ””Cause: the markdown file has no H1 or H2 headings. ctx prime only
splits at # and ## (not ### or deeper).
Fix: either add headings, or accept that the whole file becomes one “Intro” section.
If you want deeper headings to count, open an issue — we can extend the parser.
8. Cargo build failing with thiserror not found in workspace.dependencies
Section titled “8. Cargo build failing with thiserror not found in workspace.dependencies”Cause: you ran cargo build from the root without initializing the
engine/ git submodule.
Fix:
git submodule update --init --recursivecargo build --workspaceOr clone recursively from the start:
git clone --recursive https://github.com/ctxone/ctxone.git9. ctx tail shows nothing when I’m writing in another terminal
Section titled “9. ctx tail shows nothing when I’m writing in another terminal”Cause A — wrong branch. ctx tail reads the branch you specified (or
main by default). If the writes are going to a different branch, tail won’t
see them.
ctx tail --branch <other_branch>Cause B — polling interval. Default is 2000ms. Writes within that window
show up on the next poll. Lower with --interval 500.
Cause C — the write failed. Check the exit code of the ctx remember
command in the other terminal.
10. Postgres Hub errors: database "ctxone" does not exist
Section titled “10. Postgres Hub errors: database "ctxone" does not exist”Cause: the Postgres database itself isn’t created. CTXone creates the schema on init but expects the database to already exist.
Fix:
-- Connect to postgres as a superuserCREATE DATABASE ctxone;CREATE USER ctxone WITH PASSWORD 'secret';GRANT ALL PRIVILEGES ON DATABASE ctxone TO ctxone;Then:
export DATABASE_URL=postgres://ctxone:secret@localhost:5432/ctxonectx serve --http --storage postgresThe Hub will create its tables on first run.
Schema migrations
Section titled “Schema migrations”CTXone tracks its own schema version separately from the underlying storage (SQLite / Postgres / in-memory). Whenever we change the shape of what CTXone writes to the graph — path conventions, structured field layouts, session formats — the schema version is bumped and migrations run on Hub startup.
What happens on startup
Section titled “What happens on startup”The Hub reads /ctxone/schema_version from the graph and compares it to
the version baked into the running binary.
- Fresh graph: writes the current version, no data to migrate.
- Graph at current version: no-op (logged at
debug). - Graph behind current version: runs each pending migration in order
and bumps the recorded version. Each migration logs at
info. - Graph ahead of current version: Hub refuses to start. This protects
you from silent data corruption if you downgrade
ctxone-hubbut keep an old graph.
Example startup logs for a fresh install:
INFO ctxone_hub::migrations: fresh graph; initializing schema version to=1INFO ctxone_hub::migrations: migration 001: initialize schema version=1INFO ctxone_hub::migrations: migrations complete version=1And for a re-boot against the same graph:
DEBUG ctxone_hub::migrations: schema version is current, no migrations needed version=1Hub refuses to start: “graph schema version X is newer than this Hub”
Section titled “Hub refuses to start: “graph schema version X is newer than this Hub””You downgraded ctxone-hub but kept a graph written by a newer version.
Options:
- Upgrade back: re-run
install.shorinstall.ps1to get the latest Hub, ordocker pull ghcr.io/ctxone/ctxone:latest. - Start fresh: delete
~/.ctxone/memory.db(or%APPDATA%\ctxone\memory.dbon Windows) and re-run. You lose the graph but get a clean start on the old binary. - Keep both: point the older Hub at a different
--pathso the two graphs don’t interfere.
Inspecting the schema version
Section titled “Inspecting the schema version”ctx get /ctxone/schema_versionOutputs the integer version. The write is recorded under the
ctxone-migration agent with a Migrate intent, so ctx blame /ctxone/schema_version shows exactly when each version bump happened.
Enabling verbose logs
Section titled “Enabling verbose logs”The Hub uses the tracing crate. Set RUST_LOG before starting it to
control verbosity:
# Default — info-level startup and recall telemetryctx serve --http
# Debug — also see prime/forget/remember request detailsRUST_LOG=debug ctx serve --http
# Trace — every field of every span, useful for deep debuggingRUST_LOG=trace ctx serve --http
# Scoped — only enable debug for CTXone's own codeRUST_LOG=ctxone_hub=debug ctx serve --http
# Combined — debug CTXone, info HTTP request traces from tower-httpRUST_LOG=ctxone_hub=debug,tower_http=info ctx serve --httpAll logs go to stderr, so they never corrupt the stdio MCP channel when the Hub runs as an MCP server.
In HTTP mode, every recall call emits an info-level line with the
topic, tokens sent, and savings ratio — useful for watching memory earn
its keep in real time. Writes log at debug level.
Rate limiting
Section titled “Rate limiting”The Hub enforces a per-peer-IP token-bucket rate limit when running in
HTTP mode. The default is 600 requests per minute per IP, which is
permissive enough that real agents and even heavy ctx prime imports
stay well under the limit, but catches runaway loops and abuse.
When a client exceeds the limit, the Hub returns:
HTTP/1.1 429 Too Many RequestsRetry-After: <seconds>X-RateLimit-*: <headers>Well-behaved clients should honor Retry-After. The Python client
(ctxone) does this via requests, and the Rust CLI falls through
to the normal error handler.
Changing the limit
Section titled “Changing the limit”CLI flag (highest priority):
ctxone-hub --http --rate-limit-rpm 120 # 120 req/min per IPctxone-hub --http --rate-limit-rpm 0 # disable entirelyEnvironment variable (used when no flag is given):
CTXONE_RATE_LIMIT_RPM=60 ctxone-hub --http0 disables rate limiting entirely — useful in trusted single-tenant
deployments behind their own network ACLs, or when running in tests.
”I’m getting 429s on legitimate traffic”
Section titled “”I’m getting 429s on legitimate traffic””Usually it’s one of three things:
- Behind a reverse proxy that collapses client IPs to the proxy’s
own IP. The rate limiter sees every request as coming from one
client and throttles hard. Configure the proxy to forward the real
client IP via
X-Forwarded-For(a future release will add support for trusted forwarded headers — today you can disable rate limiting and let the proxy handle it). - Running a parallel batch job from a single IP. Bump the limit
with
--rate-limit-rpm 3000or disable it with--rate-limit-rpm 0during the batch. - Tests that hammer the endpoint. The library default
(
HubConfig::default()) is0specifically so in-process tests don’t get throttled. If you’re writing your own Rust tests, build the router viahttp::router_with_config(..., HubConfig { rate_limit_rpm: 0 }).
Per-session token tracking
Section titled “Per-session token tracking”The Hub tracks tokens-used and tokens-saved per session when
clients send the X-CTXone-Session header. Without the header, all
usage rolls up under the "default" session.
Endpoints
Section titled “Endpoints”| Endpoint | What it returns |
|---|---|
GET /api/stats/tokens | Aggregate roll-up across every session (backward compat) |
GET /api/stats/tokens/{session_id} | Stats for a specific session, 404 if unknown |
GET /api/stats/sessions | List every known session with its stats |
The aggregate endpoint uses "_aggregate" as the session ID in its
response body so it’s unambiguous that you’re looking at a roll-up.
Graph size (total_graph_size_chars) is not summed across
sessions — it’s the same graph for everyone, so the aggregate takes
the maximum observed value.
Setting the session ID
Section titled “Setting the session ID”Python client:
from ctxone import Hub
hub.recall("licensing") # counts toward alice's sessionOr via environment:
export CTX_SESSION_ID=alice@example.compython my-agent.pyRaw HTTP:
"http://localhost:3001/api/memory/recall?topic=licensing"Open WebUI plugin: the ctxone.integrations.openwebui Tool and
Filter automatically set X-CTXone-Session to the Open WebUI user’s
email (or name/id), so a multi-user self-hosted install gets per-user
stats for free.
”My session isn’t showing up in /api/stats/sessions”
Section titled “”My session isn’t showing up in /api/stats/sessions””Sessions are created lazily the first time a request with that header
arrives at a read endpoint that records usage (recall, context).
Write endpoints (remember, forget, prime) don’t record tokens,
so writing to a new session ID without ever reading from it leaves
the session invisible to the registry. Do one recall to materialize it.
Per-tool agent IDs
Section titled “Per-tool agent IDs”ctx blame shows a “who” column for every fact in the graph — the
agent ID that was stamped on the commit. By default that’s
"ctxone", which is useful for “this came from CTXone” but tells
you nothing about which tool wrote it. T2 makes agent IDs
per-tool so you can tell Claude Code’s writes from Cursor’s from
Open WebUI’s.
How the agent ID is resolved
Section titled “How the agent ID is resolved”Highest priority wins:
- HTTP request:
X-CTXone-Agent: <name>header on the request - MCP stdio:
--agent-id <name>flag passed when spawningctxone-hubas a subprocess (orCTX_AGENT_IDenv var) - Fallback:
"ctxone"
Setting the agent ID from clients
Section titled “Setting the agent ID from clients”Python client:
from ctxone import Hubhub = Hub(agent_id="my-script") # or CTX_AGENT_ID env varhub.remember("...") # blame shows "my-script"Raw HTTP:
curl -H "X-CTXone-Agent: my-script" \ -H "Content-Type: application/json" \ -d '{"fact":"..."}' \ http://localhost:3001/api/memory/rememberMCP stdio (what AI coding tools use): ctx init writes
MCP config files like .mcp.json and .cursor/mcp.json with
--agent-id <slug> already baked in — Claude Code writes show up
as claude-code, Cursor as cursor, VS Code as vs-code, etc.
If you have an old config that predates T2, re-run ctx init to
upgrade it.
Open WebUI plugin: the bundled Tool and Filter automatically
set both X-CTXone-Agent and X-CTXone-Session to the user’s
email (or name/id/fallback), so a multi-user install gets per-user
attribution for free.
”I ran ctx init a while ago and blame still shows ctxone”
Section titled “”I ran ctx init a while ago and blame still shows ctxone””Your MCP configs predate T2. Re-run:
ctx initwhich will rewrite .mcp.json / .cursor/mcp.json / .vscode/mcp.json
/ Codex’s config.toml etc. with --agent-id <tool-slug> in the
args, then restart your AI tool so it picks up the new config.
Still stuck?
Section titled “Still stuck?”- Run
ctx doctor— it catches most infrastructure problems automatically. - Check the Hub logs. If running via
ctx serve, errors print to stderr in that terminal. UseRUST_LOG=debugfor more detail. - Open an issue at https://github.com/ctxone/ctxone/issues with: what you
tried, what you expected, what you got, and the output of
ctx --versionandctx doctor.