Configuration

One file, searchcode.yaml, plus optional conf.d/ fragments. There is no database of settings, no admin UI that writes config, and no environment variable that overrides a field — configuration is a file you can commit, review and diff.

Configuration reference Every field, with type, default and an example. Generated from the source.
schema.json The JSON Schema. Point your editor at it.

searchcode config example prints the annotated example — every block that exists, with the reasoning beside it. It is the file to start from, and the same file is on Example configuration.

A minimal configuration

This is enough to start an instance with local accounts, two repositories and one rule about who may read them:

instance:
  dataDir: /var/lib/searchcode

auth:
  mode: local

repositoryGroups:
  - name: platform
  - name: payments

repositories:
  - url: https://github.com/acme/platform-api
    groups: [platform]
  - url: https://github.com/acme/billing
    groups: [payments]

authz:
  adminGroups: [platform-admins]
  bindings:
    - name: "Engineers read the platform"
      groups: [engineering]
      repoGroups: [platform]
      role: viewer

Every repository names at least one group, and access is granted over groups, never over repositories. Here engineering reads platform-api, and nobody can read billing, because no binding names payments and anything not granted is denied. A private repository also needs a credentials entry, which names a file or environment variable holding the secret; see Configuration reference.

Four rules that apply to the whole file

No secret is ever inline. There is no field to put one in. A credential is named by a *File or *Env reference and resolved at load into a store that redacts itself under every print verb. That is what makes this file safe to commit to git, safe to render into a ConfigMap and safe to attach to a support ticket.

An unknown key is a startup failure, naming the key and the line. A config that silently drops a misspelled authz block is worse than one that refuses it — the operator believes a control is in force that is not.

Validate before you deploy. searchcode config validate prints every problem rather than the first, names the field of each, and exits non-zero. Put it in CI:

searchcode config validate -f /etc/searchcode/searchcode.yaml

Fail closed. authz.defaultAccess defaults to deny, auth.mode defaults to local rather than open, and an empty authorisation binding set grants nothing. A forgotten assignment denies.

conf.d

Files matched by include are merged over searchcode.yaml. It defaults to conf.d/*.yaml when that directory exists, and paths are relative to the configuration file's own directory.

/etc/searchcode/
  searchcode.yaml
  conf.d/
    repositories.yaml     <- searchcode discover writes this
    bindings.yaml

Merge semantics: lists append, maps merge, later scalars win, in filename order.

That ordering is safe for authorisation specifically because authorisation is allow-only. A principal's access is the union of every binding that matches, with no deny rule, no exceptGroups, no priority and no override — so evaluation is order-independent. If it were not, access would depend on filename sort order, which is not a property anybody should have to reason about.

Reload and restart

The Reload column in Configuration reference is generated from the two tables the product actually enforces:

Silently ignoring a change is how somebody spends a day convinced authorisation is broken when their edit simply never took. Applying it in part is worse: an instance whose dataDir has moved under it is serving from one directory and writing to another.

The file is polled once a second. Reload failures are counted and published on /api/v1/status, because a reload that failed is invisible in the logs of a process that is still serving correctly.

Two settings that get "corrected"

server.writeTimeout is deliberately 0. A blanket write deadline truncates streaming MCP responses and cuts off slow searches over large estates, and both fail as a truncated response rather than as an error — the worst way for a timeout to present. Per-request deadlines are applied in the handlers, where the right budget is known.

Operation timeouts are not in server:. server: is transport only. Search has search.timeout, analysis has analysis.perFileTimeout, ingestion has its own. One timeout knob meaning seven things gets tuned for the wrong reason.

Your editor

The first line of the annotated example is:

# yaml-language-server: $schema=https://searchcode.com/schema/searchcode.schema.json

Your editor fetches that; nothing in this process ever does. On an air-gapped machine, point it at the local copy instead:

# yaml-language-server: $schema=./schema.json

searchcode config schema prints the same document for the build you are running.

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