searchcode doctor

Two commands. One tells you why an instance will not start; the other produces the document to attach to a support ticket.

searchcode doctor           # run the startup checks and print what passed
searchcode doctor bundle    # print a REDACTED diagnostic bundle as JSON

Both work against a stopped instance. That is the point — the case you need them for is the one where nothing is running, and per-repository failures are read off disk rather than from a running process.

searchcode doctor

Runs the same checks the server runs at startup, in the same order, and reports each one as ok, warn, fail or skip:

Check What it means
config <path> The file parses, every key is known, and validation passes. A failure here skips the rest, because they all depend on it.
data directory <path> It exists, or can be created 0700, and is genuinely writable — a file is created and removed, because a read-only bind mount, a full filesystem and an immutable attribute all pass a permission-bit check and fail the first write.
licence <path> The file named in config is present and readable.
licence contents What this build decided about it, and why. Never a failure: an unverifiable licence is community mode, which is an ordinary way to run.
listen address <addr> The API and MCP address can be bound.
ui listen address <addr> The UI address can be bound.
metrics listen address <addr> The metrics address can be bound, when metrics are enabled.
auth.mode <mode> The mode is coherent and everything it requires is present.
disk usage Free space against instance.diskWarnThresholdPercent.
directory sources Each source path resolves inside directorySources.allowedRoots and is readable.
repository errors Per-repository ingestion failures, read from disk.
outbound connections What this configuration would cause this process to connect to. Normally none.

A failure names the probable cause and the fix, not just the symptom. The shape that matters most:

x509: certificate signed by unknown authority

Probable cause: a TLS-inspecting egress proxy, or an internal git host with
its own certificate authority. The image trust store does not carry it, and
the root filesystem is read-only so it cannot be appended to.

Fix: mount your CA bundle and point git at it — Helm values `caBundle`,
which sets GIT_SSL_CAINFO and SSL_CERT_FILE.

That message exists because the failure is undebuggable from outside the container and the raw error says nothing about the cause.

searchcode doctor bundle

searchcode doctor bundle > bundle.json

Prints JSON on stdout. Attach it to a ticket.

What is in it

Key What
version The exact build: version, commit and build time
config The allowlisted subset of your configuration, as a nested map mirroring the file's shape
config_fields_redacted Every key the allowlist dropped, sorted. In the bundle rather than merely counted, because "what did you not send me" is the first thing a support engineer asks — and the answer is key names from a published schema, which are not sensitive.
secrets_resolved Each secret the loader resolved and where it came from — never a value. "The wrong credential is in use" is diagnosed by seeing which file it was read from.
checks What doctor found, as above
diagnoses Recognised failure shapes, each with its probable cause and the fix

Every key in that table is one some code path assigns. doctor is deliberately a command that runs without a running instance, so the bundle carries only what can be collected locally: it never dials your server to ask for its status, and it keeps no log buffer to quote error lines from. A status block and a recent_errors list were listed here until 2026-09-10 and neither was ever written into a bundle; they were removed rather than filled, because collecting either would have meant the diagnostic opening a connection or storing your logs. What the bundle says about failures it says in diagnoses.

What is redacted, and how

Redaction is an allowlist, never a denylist. Only fields named below are emitted; everything else is dropped whether or not anybody thought about it.

A denylist — "strip anything called password" — fails open on the next field somebody adds, and it fails silently. The tree is filtered through its YAML shape, which is the shape you wrote and the schema describes, so a new configuration field appears as a new key with no special handling and is therefore dropped by default.

On top of that, redaction is keyed on source. The configuration loader knows every value it resolved from a *File or *Env reference, and those values are scrubbed wherever they appear, in whatever field they landed in. Value pattern matching — PEM headers, ghp_ prefixes, PHC hashes, JWTs — is a second pass and a backstop, for values the loader never resolved, such as a licence blob or a password hash read out of SQLite. A pattern-only implementation emits anything that does not look like a secret, which is most of them.

The scrubbed value is replaced with [redacted].

And all of that applies to the whole document, not only to the config block. A value the allowlist drops is withheld from the checks and the diagnoses as well, so the bundle cannot list a field as redacted and then print it a few hundred bytes below. The same applies to the repository names, URLs and directory-source paths this instance knows from its own state on disk, which the configuration does not necessarily declare — on an estate built from a code-host connection it declares none of them.

Those replacements name what was removed, so you can see what is missing and a support engineer can ask for it:

"name": "directory source [redacted: directorySources.sources[].path]"
"detail": "1 reporting an error\n[redacted: repository 4f2a1b3c]: fatal: unable to access ..."

The repository label carries the first eight characters of the repository's on-disk identifier, so a bundle reporting three failures still says which error belongs to which — and you can map one back with an ls of repos/ under your dataDir. The git error itself is kept, because it is the diagnosable part.

Two things in the bundle are not configuration fields and are deliberately kept: the licensed customer name, which is the organisation filing the ticket and is on a licence we issued, and the configuration file path, which the unauthenticated /api/status already publishes.

The configuration fields a bundle may carry

This list is the allowlist itself. Anything not here is dropped.

connections.sources[].token is deliberately absent from that list and always will be. A bundle carries a connection's host, its targets and its filters, and never the credential that reaches them. The token is a *File/*Env reference the loader resolved, so config.Secrets scrubs its value wherever else it appears too — the allowlist decides which fields are emitted, and source-keyed redaction catches the value if one ever leaks into another field.

A trailing .* means the whole subtree, and it is used only where that subtree is a closed set of operational numbers.

Two things worth noticing about what is absent: your repository list and your repository groups are not in it, because repository names are frequently sensitive; and auth.sharedSecretFile is present while the secret it names is not — the path answers "which secret is it reading", which is the useful half, and the value is not in the configuration tree at all.

Check it yourself

searchcode doctor bundle | jq .config_fields_redacted

If your security policy needs the bundle reviewed before it leaves, that is the list to review, and jq . over the whole document is a two-minute read. It is JSON precisely so it can be read by a person and diffed by a machine.

The support template

Two things resolve almost everything:

  1. searchcode doctor bundle output, attached.
  2. The log line, verbatim — it carries the unique code, the request identifier and the context. See Log codes.

If the instance will not start at all, the FATAL: line on stderr is the first thing printed and names the path and the probable cause. Send that one.

Generated from the documentation that ships with searchcode enterprise. Questions: ben@boyter.org