Contextator
ENTR

Administration & security

Troubleshooting

Exact error messages for startup, sign-in, source, indexing and search problems, matched to their causes and fixes.

Updated:

Start here: the dashboard’s top bar tells you whether the database is up and the model is ready, and a failing project shows its error verbatim in the detail view. The logs are docker compose logs -f.


Startup

The container keeps restarting

The log line above the exit says which process died.

  • “PostgreSQL exited during startup” — the PostgreSQL output above it explains why. Usually a data directory created by another PostgreSQL major version, or a bind-mounted CONTEXTATOR_PGDATA_PATH whose permissions could not be fixed. On Docker Desktop, moving the database back to a named volume is the reliable fix.
  • An invalid configuration — the server prints every problem and exits. Fix .env and start again.

The database was created with EMBEDDING_DIMENSIONS=… but the current config says …

You changed the embedding dimensions. Either restore the old value, or start once with RESET_VECTORS=1 (drops every chunk) and re-index everything. See Embedding Models.

The dashboard says “Model loading” for a long time

The first start downloads 470 MB. Watch docker compose logs -f for downloading model file and then embedding model ready. To download less, set EMBEDDING_DTYPE=q8 (120 MB) before the first start. On an air-gapped host, pre-populate the models volume and set EMBEDDING_OFFLINE=1.

Until the model is ready, indexing and search fail while the rest of the dashboard works.

Could not load the sharp module

The lockfile was generated on another platform. Regenerate package-lock.json on Linux, or run npm install --os=linux --cpu=x64 sharp before building the image.


Signing in

The dashboard sends me to /setup

This instance has no account yet. The setup code is in the log — docker compose logs -f — printed in a box at every start until the first account exists. See Accounts and Permissions.

That setup code is not the one in the server log

You are reading an older box. A generated code is replaced on every restart, so use the newest one, or set SETUP_CODE in .env and restart to fix it in place. Case, dashes and spacing do not matter.

Signing in succeeds, then bounces straight back to /login

The browser is dropping the session cookie. On a plain-HTTP address — a LAN IP or hostname without TLS — set AUTH_COOKIE_SECURE=0 and restart. Behind a proxy that terminates TLS, set it to 1.

Wrong username or password, and I am sure it is right

The message is deliberately the same for an unknown username and a wrong password, so first check the username exists. If it does, the account may be new with a temporary password that has since been reset — ask whoever created it. Too many failed attempts; this account is locked for a while means the lockout is running: it starts at AUTH_LOGIN_WINDOW_MIN minutes and doubles, up to an hour.

Sign-in says too many attempts

Rate limiting, not a lockout on its own account: per IP address, AUTH_LOGIN_MAX_ATTEMPTS failures inside AUTH_LOGIN_WINDOW_MIN minutes get a 429 with a Retry-After. Wait it out, or raise either variable.

Nobody can sign in at all

See I forgot my password. Short version: another admin resets it, or ADMIN_TOKEN does it over the API, or npm run reset-password -- <username> on a source install.

I cannot delete the last root account

By design — the server answers 409 and the dashboard disables the button, so an instance can never lock itself out of its own user management. Promote somebody else to root first. See Accounts and Permissions.

A project I know exists answers 404

Your account is not a member of it. That is deliberate — no access and no such project answer the same way — so ask a root or admin to add you under Members on the project page.


Sources

Directory is outside the allowed document roots

The path must resolve inside ALLOWED_DOC_ROOTS — inside Docker that means /docs/.... Either use such a path, or mount the folder and add it to ALLOWED_DOC_ROOTS, then restart. See Local Directory Source.

A git source shows an authentication error

Test connection reports the remote’s own answer. Check that the token has read access to the repository and has not expired. Leave Username empty for a Bitbucket repository or workspace access token (the default x-token-auth is for those only); a Bitbucket Cloud API token needs your Bitbucket username, or x-bitbucket-api-token-auth, in that field; a GitLab deploy token needs its own generated username, e.g. gitlab+deploy-token-42 — the default oauth2 is refused for one. See Git Repository Source for the full table by provider.

Subdirectory "…" does not exist in the repository

The path is relative to the repository root and is checked against the branch you configured. Confirm both.

Branch "…" was not found on the remote

Wrong branch name — master rather than main is the usual cause.

Adding a private git, Notion or Confluence source fails on SECRET_KEY

Set SECRET_KEY (32+ characters, e.g. openssl rand -hex 32) and restart. It is required only once a source needs to store a token.

A source that used to work now says the token cannot be decrypted

SECRET_KEY changed. If you still have the old key, there is no need to re-enter anything: set SECRET_KEY_PREVIOUS to it alongside the new SECRET_KEY, restart, run npm run rotate-secret (docker exec contextator npm run rotate-secret for a Docker install) to re-encrypt every stored token under the new key, then remove SECRET_KEY_PREVIOUS and restart once it reports nothing left under it. If the old key is gone entirely, re-enter the token for that source instead — it cannot be recovered. See Security.

Notion imports nothing

Nothing is shared with the integration yet. In Notion, share the pages or databases with it (… → Connections). A page shared this way includes its children. See Notion.

Notion says no root could be read

The token is wrong or the root ids are not shared with the integration. Contextator deliberately fails here instead of reporting an empty workspace, which would delete every page already imported.

An upload rejected some files

The response lists what was skipped and why: an extension the source does not index, a dot-directory, a name that is not portable across platforms, an entry that would escape the destination, or a size cap. Adjust the source’s file types, or the limits in Configuration.


Indexing

A project is stuck in indexing

Live job state lives in memory, so a restart mid-run can leave the status behind. Press Re-index — the run is incremental and resumes cheaply, and the status is rewritten.

The project is error but most documents are there

One source failed and the rest indexed. The message reads 2/3 sources synced; <name>: <reason>, and the failing source’s row carries the full error. Fix it and press Sync on that row. Documents from a source that could not be read are never deleted.

Documents disappeared

Either the files really are gone from the source (a deleted file is removed from the index on the next run), or you changed the file-type selection so they are no longer indexed. A failing sync does not delete documents.

A file is not indexed

Check, in order:

  1. Its extension is selected on the source (.md and .mdx by default; .txt, .html/.htm, .csv, .docx and .pdf are opt-in).
  2. It could be converted at all — there is no OCR, so a scanned or encrypted PDF, or a Word file that is all images, is refused by name on the source’s row rather than half-indexed.
  3. It is not inside a dot-directory, node_modules, dist, build, vendor or __pycache__.
  4. It does not match IGNORE_GLOBS.
  5. It is not empty after the content-type transform — an empty document is skipped.
  6. It is not reached through a symlink pointing outside the source.

CHUNK_MAX_TOKENS=… exceeds what … reads

The chunking budget is larger than the embedding model reads usefully. The shipped default fits the shipped model, so seeing this means one of the two was changed on its own. Set CHUNK_MAX_TOKENS to the value the message suggests and re-index. Running a model this build does not know about? State its context window in EMBEDDING_MAX_INPUT_TOKENS. See Configuration.

Indexing is slow

Embedding is CPU-bound and one project is indexed at a time. The first run of a large project is the expensive one; subsequent runs skip unchanged files. EMBEDDING_DTYPE=q8 or the smaller Xenova/all-MiniLM-L6-v2 model both speed it up.


Search and clients

The agent cannot connect

  • The project name in the URL must match exactly — Unknown project "…" means it does not.
  • From another machine, use the server’s address rather than localhost, and check the firewall.
  • A browser-based client needs its origin in ALLOWED_ORIGINS.

search_docs says the project was indexed with another model

The server’s embedding model changed. Re-index the project — the next run is automatically a full one.

The agent finds nothing

  • Confirm the project has documents and chunks in the dashboard.
  • Try list_topics to see what was actually indexed.
  • Ask a fuller question: search is hybrid — meaning and exact wording at once — and a question carries more signal for the meaning half than two keywords do.
  • If nothing at all comes back, the relevance floor may be refusing it: search_docs answers no good match below SEARCH_SCORE_FLOOR (0.82 by default, a cosine similarity measured against the default embedding model). If you changed EMBEDDING_MODEL, the startup log says so — re-measure the floor with npm run eval against your own corpus rather than reusing the shipped default, or set SEARCH_SCORE_FLOOR=0 to turn every project’s own floor off as well. The server logs every gated query at info with the score it saw, and names the projects that carry their own floor in its startup warning.
  • If the documentation is one enormous file with no headings, chunking has little to work with — add headings.

An agent is told no good match for something that is documented

The floor refused a question it should not have. Compare the score the server logged for that query against the floor in effect — a project’s own if it set one, else SEARCH_SCORE_FLOOR. A corpus that scores lower than the rest of the instance (prose more than reference pages) is usually better served by lowering that one project’s floor in the query-log panel, which previews the change before you commit it, than by lowering the server-wide setting.

Results are poor

  • With the local model, try CHUNK_MAX_TOKENS=250 and force a re-index (Indexing).
  • Split unrelated bodies of knowledge into separate projects.
  • Exclude noise (journals, changelogs, templates) with IGNORE_GLOBS.
  • Consider OpenAI embeddings for a large, prose-heavy corpus.

Answers got longer after upgrading

Each excerpt now carries the chunk on either side of it, for more context around the match. SEARCH_NEIGHBOR_CONTEXT=0 restores the old, single-chunk shape, and SEARCH_MAX_RESULT_CHARS caps the whole answer regardless.

A webhook returns 401 invalid_signature

The secret in the repository settings is not the one currently stored. Copy it again from the source dialog, or Regenerate and paste the new one. See Push Webhooks.


Scripts and the admin API

403 csrf_blocked from my own script

A cookie-authenticated write must come from this site (Sec-Fetch-Site, falling back to Origin/Referer) — a script sending the session cookie from another origin trips it. Use Authorization: Bearer $ADMIN_TOKEN, or an API token, instead; bearer requests are exempt from the check. See Security.

After upgrading, /api/* answers 401 setup_required

This instance had no ADMIN_TOKEN set and was, on the version it ran before, effectively open on /api/*. It is now closed by default: open /setup with the code from the log and create the first account, same as any brand-new instance. Projects, sources and indexes already there are untouched. See Accounts and Permissions.


MCP connectors

A member reads a project in /mcp/… they are not a member of

Expected while that project is open or token required: a credential that names nobody is judged by the mode, not by memberships. Switch the project to account required under MCP access and its endpoint starts following the member list. See Accounts and Permissions.

An MCP client suddenly answers 401

The project now requires a credential. Mint a token under MCP access and add --header "Authorization: Bearer …" (or headers in mcp.json) — or, if the project says account required, reconnect a client that can sign in, because a static token is refused there.

An MCP client answers 403 … not a member of this project

The credential is fine and the account behind it is not on the project. Add it under Members, or connect with an account that already is one.

A browser-based connector cannot connect at all

Either the project is open/token required and the connector has no header to send, or MCP_OAUTH=0 on this instance and there is no OAuth flow for it to use. See Connecting AI Clients.

A connector says it was disconnected and has to be approved again

Expected after a password change, after its membership was removed, or after the same credential was presented twice — this server treats that as two parties holding one token and answers by taking the grant down. A connector simply left unused past MCP_OAUTH_REFRESH_TTL_DAYS is a quieter, different case: it is asked to authorize again and nothing is revoked. Approve it again either way.

A connector reports invalid_scope

It asked for an OAuth scope. This server issues none — an account-backed credential reaches exactly what its account may read — so the request is refused rather than granted under a scope nobody honours. The server log names the client that asked.

I lost an MCP token

It cannot be recovered — only a hash is stored. Revoke it under MCP access and mint another.


Getting more detail

docker compose logs -f                    # follow everything
docker compose logs --tail=200 contextator
curl -s http://localhost:3444/api/health | jq

For verbose per-request logging, set LOG_LEVEL=debug and restart. Useful log lines: embedding model ready, indexing started, indexing finished (with counts), indexing finished with source errors, source sync failed, mcp session opened / closed.

If you are stuck, open an issue with: the version from /api/health, the relevant log lines, the source type involved, and what you expected to happen.

Arrow keys to move, Enter to open.