Troubleshooting

Doctor output, Homebrew SQLite on macOS, model pulls, stale embeddings, switching backend or Bun version, resident background issues, and Windows llama.cpp startup.

First stop: gno doctor

gno doctor

Start here: gno doctor runs the health checks GNO ships, including embedding-fingerprint (current fingerprint, pending/stale chunks, mixed stored groups) and collection-roots (collection folders that are missing or unreadable right now). Read its output carefully before anything else.

For embedding freshness, look for the embedding-fingerprint check. It reports the current fingerprint, pending/stale chunks, legacy empty-fingerprint vectors, and mixed stored fingerprint groups.

A slow or stuck command

Set GNO_PHASE_TRACE=1 to see where a slow or stuck CLI command spends its time. Each startup phase (write lease, index open, migrations, collection walk, each file, the file processor) prints one line to stderr with the milliseconds since the process started; stdout is unchanged, so --json output stays valid.

GNO_PHASE_TRACE=1 gno update
# gno-phase 461 4856 sync: file a.md
# gno-phase 473 4856 sync: record adapter selected

The last line a stuck command printed names the phase it is in. Include those lines when you report a hang.

Slow first request or a model failure

The first model request, and the first one after five minutes without model use, has to load the model again, so it is slower. Status polling does not keep models loaded. Check the model timeout settings and compare the same query, model, collection, and options before and after the idle period. Query expansion has its own time limit on a cold start and falls back to a search without expansion when it runs out; raising model timeouts does not extend that limit.

If a model fails to start, exits, or returns an invalid response, GNO reports a structured error and does not retry the request for you. Save the full error, stderr, model URIs, and GNO version, then run the request again. In hybrid results, meta.vectorsUsed: false means no vector search succeeded, and a fully lexical fallback shows meta.mode: "bm25_only". A vector search that ran and found nothing returns vectorsUsed: true with no hits, so a failed model load never looks like an empty semantic result.

If you see Unsupported node-llama-cpp simulator source, reinstall GNO so it uses the node-llama-cpp version it ships with, and do not patch installed native files. When you measure memory, include the separate model process; the main GNO process RSS does not include model allocations.

Rare or missing filtered results

Filters apply before GNO limits the candidate list, so a rare match is not pushed out by more common ones. Check collection/path, tags, dates, category, author, exclusions, and memory visibility before raising limits. Vector and hybrid language filters match the language of each chunk; a language filter on lexical-only search is not supported yet. Thresholds, deduplication, or limited coverage can leave fewer than K results. Use gno query "your query" --explain to see fallback and ranking details.

Collection root could not be resolved

gno update or the watcher printed [SOURCE_AVAILABILITY_UNKNOWN] Collection root could not be resolved (...) for a collection. Its folder was missing when the sync ran: an unmounted drive, an offline Google Drive or other sync folder, or a network share that was down. GNO kept the collection's documents searchable and changed nothing.

While the folder is away, gno status names the collection, its folder, and the error (rootError in --json), gno doctor reports a collection-roots warning and is not healthy, and the Web UI shows a Folder access item in the Health Center and a note on the collection's card. These checks run each time you look, so they clear as soon as the folder is back.

Bring the folder back (mount the drive, start the sync client) and run gno update again to pick up changes made meanwhile. If the folder moved for good, change the collection's path in the config file and run gno update, or remove the collection with gno collection remove.

“This index holds collections the config does not list”

A command opened an index that contains documents of collections its config does not list, and refused so nothing is deleted. This happens when you remove a collection by editing the config file, or when a different config (--config other.yml, or an SDK client given a config but no dbPath) points at your normal index. gno status lists them under Collections not in this config. A running gno serve, gno daemon, gno mcp, or SDK client refuses requests the same way (REST: 409 STALE_COLLECTIONS).

Cloud placeholders skipped under sourceAvailability local

Opt-in sourceAvailability: local (collection config) is separate from egressPolicy. It skips any file that the cloud provider would have to download first. Receipts may show CLOUD_PLACEHOLDER, CLOUD_PARTIAL, DATALESS_DIRECTORY, or a SOURCE_AVAILABILITY_* code when GNO cannot confirm a file is local. Files you already indexed under a folder GNO cannot check stay in the index. Local mode works on macOS with Google Drive (My Drive and individual Shared drives), iCloud Drive, and OneDrive, including SharePoint libraries synced at the top level of OneDrive. If you get SOURCE_AVAILABILITY_UNSUPPORTED for a collection at the Google Shared drives folder itself, create one collection per Shared drive instead. Cloud filesystems on Windows and Linux are not supported. In this mode GNO never pins, evicts, or downloads files. To index a skipped file, make it available offline in the provider first. A collection that reports SOURCE_AVAILABILITY_UNSUPPORTED on every sync is retried less and less often by a running server or daemon, but it never indexes in that state: remove sourceAvailability: local from it, or set it to any. See Configuration → Source availability.

Retrieval activation is blocked or degraded

Check gno doctor, gno status --json, or the dashboard Health Center. A ready collection has completed a bounded, corpus-derived lexical search and returned its expected document. Semantic search may still be pending independently while models or embeddings are prepared.

Do not edit GNO’s search tables directly. Changes made outside GNO may not be detected. If you suspect them, rebuild the collection with gno index <collection> --no-embed.

Error: database is locked or exit code 4

GNO writers wait their turn on one shared write lease, so CLI and MCP writes that overlap do not fail with Error: database is locked.

Exit code 4 means another writer held the lease past --lock-wait (default 120s). This is contention, not corruption — retry the command. Raise --lock-wait for long reindexes, or pass --no-wait in scripts that prefer failing fast. Reads do not take the write lease, but resident reader admission still has bounded capacity. A full reader queue returns HTTP 429; shutdown or unavailable admission returns HTTP 503.

chunks deferred by index contention means rerun gno embed after the other writer finishes. If you run a GNO version older than 1.38, upgrade.

A running gno serve or gno daemon uses the same lease for file watching and background embedding, and pauses that work while a CLI writer holds it. A CLI command next to a busy resident may wait and, at worst, exit 4. A raw database is locked error means a writer outside the lease: an older GNO version or another tool with the index open for writing.

Resident uses CPU, embeddings stay pending, or --stop needs SIGKILL

Run gno status (or gno serve --status / gno daemon --status) and look for Background issues. Each detached resident gets one status request with a 500ms budget, so status never waits on a hung process.

gno status --json carries the same data as backgroundIssues (absent when nothing is wrong). The serve and daemon logs are append-only across runs, so repeated GNO server running / Shutting down... blocks are separate starts and stops, not a restart loop.

Captures and document saves (Web UI and PUT /api/docs/:id) also take the write lease. One that cannot get it in time returns the busy error (409 LOCKED for REST saves) and writes nothing; try again once the other writer finishes.

A server or MCP client keeps running an old Bun

A running gno serve or gno daemon keeps the Bun it started with, and a client registration keeps the Bun path written when it was installed. gno status lists both under Bun runtime, and gno doctor reports them as resident-runtime and mcp-runtime, each with its fix:

GNO needs Bun 1.4.1 or later. While indexing, a message GNO cannot read back fails only that file with PROCESSOR_MESSAGE_FAILED and one log line naming it; indexing continues with the next file.

A write timed out or a retry reports a request error

Check the request ID first with gno request-status <id> (or GET /api/requests/:requestId). Committed: do not resend. Pending or not found: repeat the identical call with the same ID.

Codes and semantics: Retry-safe writes.

Vector search fails on macOS

Symptom: gno vsearch or hybrid queries fail with a SQLite extension loading error.

Cause: the stock Apple SQLite doesn’t support loading extensions. GNO needs the Homebrew build.

brew install sqlite3
gno doctor

Models fail to download

Symptom: the first gno query or gno ask stalls during a model pull.

Solutions:

Results feel stale after editing

Re-scan the index:

gno update

Or run gno daemon in the background for continuous indexing.

A deleted file you restore unchanged comes back on the next sync with its existing embeddings and one reactivate event in the change journal. A changed title or text, or a different embedding model, needs new embeddings. Run gno doctor and a normal gno embed before reaching for gno embed --force. If doctor reports a missing or corrupt vector index, follow its repair step; GNO does not fall back to older vectors. Interrupted embedding runs resume where they stopped.

Update reports an invalid PDF

The PDF is incomplete or damaged. GNO isolates it as a non-fatal CORRUPT document, continues indexing the collection, and keeps the Web UI available. It does not retry the unchanged file on every update.

qpdf --check /path/to/file.pdf

Replace or re-export the PDF, then run gno update or Update All. A changed source hash makes GNO try the repaired file again.

A large spreadsheet never finishes, or is stopped with TIMEOUT or MEMORY_LIMIT

GNO 2.8.2 could spend hours and tens of gigabytes on one large Excel workbook (for example a pivot report with a sheet of tens of thousands of rows) and write nothing to the index; 2.8.1 also used tens of gigabytes on such a file. Converting a sheet went through an HTML table whose cost grew with the square of its rows and used about 20 KB per cell, and 2.8.2 added Markdown code detection over the converted table, which grew with the square of its cells. Current versions build each sheet's table directly from the sheet data, never run code detection on converted files, and parse very large Markdown tables without the table rules, so such a workbook indexes in seconds. A Markdown note that is mostly one such table also indexes in time proportional to its size: a note of 23 columns by 40,000 rows takes a few seconds, including rows with an unmatched ], an unclosed [[ or an unclosed <!--, which used to stop it at the per-file budget.

Each file is also indexed under a per-file budget covering conversion and the processing after it, and is stopped mid-step when it runs over. A file still converting after 10 seconds is named on stderr by gno update and gno index. A file that runs past its time budget, or pushes GNO's memory past the memory budget, is stopped and recorded as TIMEOUT or MEMORY_LIMIT; the rest of the collection still indexes. gno update output, gno status and gno audit name the file, and the next update tries it again.

To let a legitimately large file finish, raise the conversion budget:

conversion:
  timeoutMs: 300000
  maxMemoryMb: 12288

To skip it instead, add it to the collection's exclude list.

Results feel off after switching models

Vector-based search uses the embedding model that was active when the index was built. Doctor shows this through the embedding-fingerprint check. After switching embeddings, re-embed:

gno doctor
gno embed # re-embed stale/pending chunks
gno embed notes # or one collection

gno embed retries transient embedding failures within the same run. If doctor still reports stale, legacy, or mixed vectors after a normal embed, force a full refresh:

gno embed --force

gno embed --force uses the same same-run retry path. If a run still fails, rerun with verbose output so GNO prints sample failures and the retry hint:

gno --verbose embed --force

Check which GPU backend is detected

Before forcing a backend, see what node-llama-cpp actually detects. This works on Linux, Windows, and macOS and reports the active GPU backend (CUDA, Vulkan, or Metal), available VRAM, and which prebuilt binary is in use:

bunx --bun node-llama-cpp inspect gpu

(npx --no node-llama-cpp inspect gpu works too.) If CUDA or Vulkan shows as available here but GNO still runs on CPU, the backend is being selected or initialized incorrectly — see below. If it is not available here, the GPU driver or toolchain is the problem, not GNO.

Switching backend or Bun version

Vector identity is the embedding model weights, formatter, dimensions, context size and truncation policy. Changing GNO_LLAMA_GPU / NODE_LLAMA_CPP_GPU, the Bun version (for example a scheduled CLI and the desktop app on different Bun releases), the node-llama-cpp version or the CPU thread count does not start a new partition. Those details are recorded as provenance.

The first time a runtime meets an existing partition, GNO re-embeds up to 8 stored chunks and compares them with the stored vectors. Every sampled chunk must reach cosine 0.99. With the default model, Bun version, thread count, and GPU or CPU switches pass this check, so they resume the backlog in the same partition. GNO remembers the result for each partition and runtime.

GNO_LLAMA_GPU=false gno embed --yes # resumes the backlog

A runtime below the threshold is incompatible. Its queries use lexical retrieval only and carry a vector_runtime_incompatible warning, status lists it under the partition, and gno embed states that it would build a separate partition, the full chunk count and an estimate, then asks for confirmation. Non-interactive runs need gno embed --new-partition; --yes alone never confirms.

Later versions of that runtime reuse the confirmed partition: GNO measures every partition of the same vector space and picks the first compatible one. The same confirmation applies when a vector-defining setting (for example GNO_EMBED_CONTEXT_SIZE) changes for a model that already has vectors. Status counts the runtime-independent partition and lists which runtimes read each partition.

After you upgrade, GNO converts an existing index once, without re-embedding. The most complete partition that passes the check becomes the runtime-independent one and the others are marked shadow. If two are equally complete, GNO converts nothing and status and embed report the tie; drop all but one.

gno status # lists partitions and their drop commands
gno vec drop 9a8b7c6d5e4f

Windows model startup hangs

Symptom: gno index, gno embed, or gno doctor appears stuck while loading node-llama-cpp, often around a Vulkan backend load test.

GNO defaults to prebuilt local-model backends, times out backend initialization, and retries CPU on Windows when automatic GPU backend selection fails. Run diagnostics first:

gno doctor

To force CPU mode for a constrained Windows machine, run:

GNO_LLAMA_GPU=false gno embed --yes

If CPU embedding still consumes too much memory, keep the adaptive default or set an explicit small context pool. On CPU-only systems, GNO defaults to one context on low-memory Windows machines and at most two contexts elsewhere:

GNO_EMBED_CONTEXTS=1 gno embed --yes

Advanced CPU tuning is available when you want to trade throughput, memory, and per-context parallelism:

GNO_EMBED_CONTEXTS=2 GNO_EMBED_THREADS=4 gno embed --yes
GNO_EMBED_CONTEXT_SIZE=512 gno embed --yes

Only opt into source builds when you intentionally have Visual Studio Build Tools and a working native toolchain:

GNO_LLAMA_BUILD=autoAttempt gno doctor

Session archive problems

Binding mismatches, partial or quarantined imports, a busy archive, and sessions missing from search are covered in the session archive troubleshooting section, with every code in its error-code table.

Automation problems (a hook that says accepted but nothing was archived, not running: no daemon, a missing hook entry, retries and backoff) are covered in Failures, retries, and recovery. The quickest checks:

gno --config ~/gno-sessions/archive.yml --index sessions sessions status        # state and recovery action
gno --config ~/gno-sessions/archive.yml --index sessions sessions automation run <profile>
GNO_SESSIONS_HOOKS=off claude                                                  # run Claude Code with the hook disabled

Still stuck?