Configuration reference

Every field of searchcode.yaml, with its type, its default and an example.

Three rules apply to the whole file and are worth reading before any single field:

The JSON Schema for the same tree is schema.json; point your editor at it and the fields below autocomplete as you type.

Reading the tables

Column Meaning
Field The dotted path. [] marks a list, so repositories[].url is the url of each entry.
Type The YAML type. A duration is a Go duration string — 30s, 2m, 1h — or a bare 0.
Default What the product uses when the key is absent. — means the zero value carries no meaning of its own; required means the key cannot be omitted at all.
Example A real value, taken from the annotated example configuration where it appears there.
Reload reload: a change takes effect without a restart. restart: a reload carrying a change here is rejected, naming the field, rather than half-applied. —: not in either documented list; a change is picked up only where the component re-reads it, so restart to be sure.

include

Globs, relative to this file's directory, merged over it. Lists append, maps merge, later scalars win. Defaults to conf.d/*.yaml when that directory exists.

Field Type Default Example Reload Description
include list of string — [conf.d/*.yaml] reload Globs, relative to this file's directory, merged over it. Lists append, maps merge, later scalars win. Defaults to conf.d/*.yaml when that directory exists.

instance

Where this instance keeps its state.

Field Type Default Example Reload Description
instance.dataDir string — /var/lib/searchcode restart Clones, the analysis store and caches. Created 0700 at startup. Requires a restart to change.
instance.diskWarnThresholdPercent integer 80 80 restart Warn on /api/v1/status and the admin page at or above this percentage used. Never blocks serving.

server

Transport-level HTTP settings. Operation timeouts live with the features that own them.

Field Type Default Example Reload Description
server.listen string 0.0.0.0:8080 0.0.0.0:8080 restart Address to bind for the API and MCP, host:port. Never put this behind an auth proxy: agents present searchcode-issued bearer credentials. A zero port binds an ephemeral one. Requires a restart to change.
server.uiListen string 0.0.0.0:8081 0.0.0.0:8081 restart Address to bind for the human-facing UI, host:port. Separate from listen so the UI can sit behind an auth proxy while the API and MCP do not. Set to off to run headless: no UI socket is bound and only the API and MCP are served. Empty is an error, not off. Requires a restart to change.
server.readHeaderTimeout duration 10s 10s restart Slowloris defence — the timeout that actually matters. How long a client may take to send its headers.
server.readTimeout duration 30s 30s restart Whole request including body.
server.idleTimeout duration 2m0s 2m0s restart Keep-alive reaping.
server.writeTimeout duration 0 (unset) 0 restart Deliberately unset (0). A blanket write deadline breaks streaming MCP and cuts off slow searches over large estates — both would fail as a truncated response rather than as an error. Per-request deadlines are applied in the handlers instead, where the right budget is known. Leave it unset unless a proxy in front of the listener needs a hard bound.
server.maxHeaderBytes integer 65536 65536 restart Go's 1MB default is generous for a service whose largest header is a bearer token.
server.shutdownTimeout duration 30s 30s restart How long a graceful shutdown may take to drain in-flight requests. Not one of the five transport timeouts: it budgets an operation, not a connection.

license

Licence file location. No feature is licence-gated; repository and user counts are the only meters.

Field Type Default Example Reload Description
license.file string — /etc/searchcode/license.key restart Path to the licence key file. The SEARCHCODE_LICENSE environment variable is an equally supported alternative.

logging

Structured logging. The level is reloadable.

Field Type Default Example Reload Description
logging.level string, one of debug | info | warn | error info info reload debug | info | warn | error. Reloadable — a level change takes effect without a restart.

audit

The audit log.

Field Type Default Example Reload Description
audit.output string, one of stdout | stderr | file | syslog stdout stdout restart Where audit records go. stdout needs no writable path beyond dataDir.
audit.retentionDays integer 400 90 restart How long audit records are kept. 0 keeps everything forever. The default of 400 days covers a 13-month audit cycle.
audit.logSearchQueries boolean true true restart Record the text of search queries. Setting this false still leaves usage counts working.
audit.syslogAddress string — "…" restart Only with output: syslog. network://address, e.g. udp://10.0.0.1:514. Empty uses the host's local syslog socket.

auth

How humans authenticate. Exactly one mode, never combined.

Field Type Default Example Reload Description
auth.mode string, one of proxy | local | open local proxy restart proxy | local | open. Exactly one, never combined. NOT the same setting as mcp.auth.mode. Requires a restart to change.
auth.trustedProxyCIDRs list of string — [10.0.0.0/8] restart Required in proxy mode: the networks whose identity headers are believed. The instance refuses to start without it.
auth.sharedSecretFile string — /run/secrets/proxy-shared-secret restart Required in proxy mode: file holding the secret the proxy sends, proving the request came through it.
auth.sharedSecretHeader string — X-Searchcode-Proxy-Secret restart Required in proxy mode: the header carrying the shared secret.
auth.local.argon2.memoryKiB integer 19456 19456 restart Memory cost in KiB. The parameter that actually costs an attacker money. Defaults to 19456 (19 MiB), the OWASP recommendation. Concurrent logins each need this much.
auth.local.argon2.time integer 2 2 restart Number of passes. Defaults to 2. OWASP treats 19456/2, 12288/3, 9216/4 and 7168/5 as equivalent, so trade memory for passes rather than lowering both.
auth.local.argon2.parallelism integer 1 1 restart Number of lanes. Defaults to 1.
auth.local.loginRatePerMinute integer 10 10 restart Login attempts one source address may make per minute. Applied BEFORE the password hash runs, so it bounds what an unauthenticated caller can make this instance spend. Defaults to 10.
auth.local.initialAdminUsername string — "…" restart Username created on first boot when an initial password reference is set. Unattended installs only; searchcode user add --admin is the interactive path. There is no default password, and no account exists without one of the two.
auth.local.initialAdminPasswordFile string — "…" restart File holding the initial administrator password. A change is forced on first sign-in, because this value persists in the deployment manifest.
auth.local.initialAdminPasswordEnv string — "…" restart Environment variable holding the initial administrator password. A change is forced on first sign-in.
auth.session.insecureCookiesOverPlainHttp boolean false false restart SECURITY DOWNGRADE, off by default. Drops the __Host- prefix and the Secure attribute from the session cookie so a browser keeps it over plain http on a LAN address. The session id then travels in clear text and anyone on the network path can steal it. It exists for throwaway trials on a private network; put TLS in front of the UI listener for anything else. Requires a restart to change, and the instance announces it loudly for as long as it is on.

authz

Authorisation policy: allow-only, no deny rules, order-independent.

Field Type Default Example Reload Description
authz.defaultAccess string, one of allow | deny deny deny reload What applies when no binding matches. Fail closed: deny.
authz.anonymousRole string — viewer reload The role an unauthenticated caller gets. Valid only with auth.mode: open. Defaults to viewer, never admin.
authz.adminGroups list of string — [platform-admins] reload Instance-scoped, not repository-scoped: full administrative access.
authz.auditorGroups list of string — [security-eng] reload Instance-scoped: view_audit_log and view_analytics.
authz.adminUsers list of string — [ops.lead@example.com] reload People, by email address, granted exactly what adminGroups grants. auth.mode proxy only; matched against X-Forwarded-Email, case-insensitively for ASCII and literally otherwise. No wildcards.
authz.auditorUsers list of string — [compliance@example.com] reload People, by email address, granted exactly what auditorGroups grants. auth.mode proxy only. No wildcards.
authz.roles map of list of string — {releng: …} reload Custom roles, from the closed repo-scoped permission set. Instance-scoped permissions come from adminGroups and auditorGroups instead.
authz.bindings list of object — […] reload Grants. Order-independent — a principal gets the union of every binding that matches.
authz.bindings[].name string required Engineers read platform and open source reload What this grant is for. Shown in the admin UI and in audit records.
authz.bindings[].groups list of string — [engineering] reload Identity groups this applies to. "*" means any authenticated principal. A binding needs groups, users, or both.
authz.bindings[].users list of string — […] reload People this applies to, by email address, matched against X-Forwarded-Email. auth.mode proxy only. No wildcards: restrict the domain with the proxy's email_domains and use groups: ["*"] instead.
authz.bindings[].repoGroups list of string — [platform, open-source] reload Repository groups granted. Globs are allowed on this side only. A binding matching nothing is a typo and fails validation.
authz.bindings[].role string — viewer reload A built-in or custom role.

repositoryGroups

The unit authorisation is granted over. Membership is always explicit, never globbed.

Field Type Default Example Reload Description
repositoryGroups list of object — […] reload The unit authorisation is granted over. Membership is always explicit, never globbed.
repositoryGroups[].name string — payments reload The identifier used in repositories[].groups and in bindings.
repositoryGroups[].description string — Payments platform — restricted reload Shown in the admin UI. Free text.

credentials

Credential references used to reach repositories. No secret is ever inline.

Field Type Default Example Reload Description
credentials list of object — […] reload Credential references used to reach repositories. No secret is ever inline.
credentials[].name string required github-ssh reload Referenced by repositories[].credential.
credentials[].type string, one of ssh | https required ssh reload ssh | https.
credentials[].privateKeyFile string — /run/secrets/id_ed25519 reload ssh only: the private key. A path, never a key.
credentials[].knownHostsFile string — /etc/searchcode/known_hosts reload ssh only: the known_hosts file to verify the host against.
credentials[].username string — "…" reload https only: the username. Not a secret, so it is inline.
credentials[].passwordEnv string — "…" reload https only: the environment variable holding the password or token.
credentials[].passwordFile string — "…" reload https only: the file holding the password or token.

repositories

Git repositories to ingest. Usually lives in conf.d/, generated by 'searchcode discover'.

Field Type Default Example Reload Description
repositories list of object — […] reload Git repositories to ingest. Usually lives in conf.d/, generated by 'searchcode discover'.
repositories[].url string required https://github.com/acme/billing reload Clone URL, or an absolute path for a local repository.
repositories[].groups list of string required [payments] reload At least one, and each must be a declared repositoryGroup. An unclassified repository is an error, not a warning.
repositories[].labels list of string — [tier1, pci] reload Categorisation and facets only. A label grants nothing.
repositories[].credential string — github-ssh reload A declared credential name. Omit for a public or local repository.
repositories[].fullHistory boolean — true reload Default true. False costs blame, history and coupling analysis for this repository.
repositories[].depth integer — 10 reload Clone depth, valid only with fullHistory: false. The escape hatch for a fifteen-year monorepo with binary assets in its history.
repositories[].refreshInterval duration — 30s reload How often this repository is fetched. Overrides indexing.refreshInterval. Jittered, so a large estate does not fetch on the hour together.
repositories[].exclude list of string — […] reload Path globs never ingested from this repository, layered over indexing.exclude and the built-in exclusion set. A leading ! un-excludes either of those — "!node_modules" is how the one repository whose vendored tree IS the code gets it back.

directorySources

Plain directories to ingest. The sharpest security boundary in the product.

Field Type Default Example Reload Description
directorySources.allowedRoots list of string — [/mnt/depot] reload Hard allowlist. Every source path must resolve inside one of these. Non-empty if any source is declared.
directorySources.denyPaths list of string — [/mnt/depot/.creds] reload Always refused, even inside an allowedRoots entry. instance.dataDir, this file's own directory and /run/secrets are denied implicitly whether or not they are listed.
directorySources.sources list of object — […] reload The directories to ingest.
directorySources.sources[].path string — /mnt/depot/cobol reload Must resolve inside an allowedRoots entry. Absolute: a relative path is refused rather than resolved against whatever directory the process happens to be started from.
directorySources.sources[].groups list of string — [platform] reload At least one declared repositoryGroup, exactly as for a repository.
directorySources.sources[].unitDetection string, one of single | subdirectories | marker — subdirectories reload single | subdirectories | marker. How one directory becomes one or many searchable units. Defaults to marker. This is a metering input: it decides whether a mount of 2,000 projects counts as one repository or 2,000.
directorySources.sources[].markerFiles list of string — […] reload Extra project markers, layered over the built-in set (.git, go.mod, pom.xml, package.json, Cargo.toml).
directorySources.sources[].maxDepth integer — 4 reload Bounds the marker search, so a deep tree does not turn unit detection into a full walk. Defaults to 4.
directorySources.sources[].changeDetection string, one of mtime | hash | always — mtime reload mtime | hash | always. How a re-scan decides a file changed. mtime costs one stat per file and is right for most sync tooling. Use hash when the export tooling rewrites every mtime on every run, which mainframe and some Perforce export scripts do; it costs a full content read per scan. always re-reads every unit in full on every refresh interval whether or not anything changed, at the same read cost as hash — it is the last resort, not the safe default, and the instance warns at startup when it is set. It does not re-run the deep analysis pass over bytes the re-read proved identical: that pass is served from its cache, keyed on a digest of every file's content and never on a modification time, so nothing rests on the signal you chose this mode to stop trusting.
directorySources.sources[].followSymlinks boolean — false reload A symlink escaping an allowedRoots entry is refused either way. False leaves symlinks unfollowed entirely.
directorySources.sources[].exclude list of string — […] reload Path globs never ingested from this source, layered over indexing.exclude exactly as a repository's are.

discovery

Code-host enumeration, read by the 'discover' CLI only. The server never calls a host.

Field Type Default Example Reload Description
discovery.provider string, one of github | gitlab — github — github | gitlab. Used by 'searchcode discover' only — the CLI, never the server.
discovery.baseUrl string — https://github.example.com/api/v3 — For GHES or self-managed GitLab. Omit for the public host.

connections

Code-host connections: the ONE block that can make this process connect to a code host. Absent, it connects to nothing.

Field Type Default Example Reload Description
connections.egress string, one of any | internal-only any any restart any | internal-only. internal-only refuses a connection naming a public code host (github.com, gitlab.com, …) AT CONFIG LOAD, so a firewalled instance has a switch to point at rather than a policy to review. Requires a restart to change.
connections.keyFile string — /run/secrets/connections.key restart A file holding the key that encrypts tokens pasted into the UI or 'searchcode setup'. Generate one with 'openssl rand -base64 32'. With neither this nor keyEnv set, connections can only be declared under sources with a secret reference — creating one from a form is refused, and says so.
connections.keyEnv string — "…" restart An environment variable holding the same key. Use one of keyFile or keyEnv, never both.
connections.sources list of object — […] reload The connections themselves. Absent or empty, this process connects to no code host at all.
connections.sources[].name string required acme-github reload Stable identity. Per-connection state hangs off it, so renaming is a delete plus a create, not a rename.
connections.sources[].type string, one of github | gitlab required github reload github | gitlab. Both ship together; the provider for each arrives with its own plan.
connections.sources[].url string required https://github.com reload The one host this connection may reach — https://github.com, or your GitHub Enterprise Server or self-managed GitLab. Never derived from a response.
connections.sources[].token object required {env: …} reload REQUIRED on every connection, public organisations included — a metadata-scoped, read-only token. GitHub's and GitLab's listing APIs are not usefully anonymous, so enumeration needs one even where cloning does not. A secret reference — {env: NAME} or {file: /path} — never an inline value. This only LISTS; see credential to CLONE.
connections.sources[].token.file string — "…" restart Path to a file holding the secret. Relative paths resolve against this configuration file's directory. Re-read on every load, so rotating a mounted Kubernetes Secret needs no restart.
connections.sources[].token.env string — ACME_GITHUB_TOKEN restart Name of an environment variable holding the secret. The variable's NAME, never its value.
connections.sources[].credential string — gitlab-https reload A declared credential name, used to CLONE what this connection discovers. Without it the clone goes out ANONYMOUS and every private repository is refused, whatever the token above can do — so a private estate needs this as well, on every host. What differs by host is only which SCOPE that credential needs: on GitHub a second, contents-scoped token; on GitLab the same read_api token can serve. Omit for a public estate, which clones anonymously; the token above is required either way.
connections.sources[].include object required {orgs, self, users: …} reload What to enumerate. Required — there is no 'everything this token can see' mode, because a broadly scoped token must not become a broad estate by default.
connections.sources[].include.orgs list of string — [acme, acme-labs] reload GitHub organisations, or GitLab group paths — a GitLab path nests, so acme/platform/infra is one entry and its subgroups come with it.
connections.sources[].include.users list of string — [ben] reload Other users' accounts, or GitLab personal namespaces. Public repositories only, on GitHub.
connections.sources[].include.self boolean — false reload The token's own account, including private repositories it can see. Needs no name, which makes 'paste a token' a complete configuration.
connections.sources[].exclude list of string — [*-archive] reload Globs over owner/name, applied last so they always win, exactly as 'searchcode discover' applies them.
connections.sources[].skipArchived boolean — true reload Skip archived repositories. Defaults to false — an archived repository is enumerated and indexed like any other.
connections.sources[].skipForks boolean — true reload Skip forks. Defaults to false — a fork is enumerated and indexed like any other.
connections.sources[].groups list of string — [engineering] reload repositoryGroups every repository this connection discovers belongs to. Each must be declared under repositoryGroups.
connections.sources[].syncInterval duration — 1h0m0s reload How often this connection re-enumerates. Sync itself is the connection scheduler's; a connection with no scheduler enumerates nothing.

indexing

The clone and refresh pool.

Field Type Default Example Reload Description
indexing.cloneConcurrency integer 4 4 reload Counting semaphore over concurrent clones.
indexing.window string — 22:00-06:00 reload Optional "HH:MM-HH:MM". Only starts new work inside it; work in flight is never cut off.
indexing.maxRepoSizeBytes integer 5368709120 (5 GiB) 5368709120 reload Checked during clone, not after — a repository over the limit is abandoned rather than cloned then deleted.
indexing.metricsPerRepo boolean true true reload Per-repository metric series. Costs about 2 series and 200-400 bytes of scrape per repository (measured; the range is how long your repository names are), so set false above roughly 2,000 repositories, where a scrape passes half a megabyte and Prometheus stores 4,000 series for this instance alone. It is NOT a fix for a slow scrape: the numbers are read whether or not the series are collapsed. False keeps the repo label and collapses onto repo="all" rather than dropping it, so a dashboard query that groups by it still parses.
indexing.exclude list of string — [examples/**] reload Path globs never ingested, layered OVER a built-in set that is always in force — node_modules, vendor, build output, virtualenvs and tool caches, plus whatever pip's own metadata says "pip install -t" put into a repository. Setting this adds to that set rather than replacing it; a leading ! takes one back ("!node_modules", or "!pip-installed" for the pip rule). Every exclusion is counted and reported, never silent: the built-in set's share is files_skipped{reason="excluded_default"} and yours is reason="excluded".
indexing.refreshInterval duration 1h0m0s 1h0m0s reload How often a repository is fetched, unless it sets its own. Jittered per repository so two thousand repositories do not all fetch on the hour.
indexing.minFreeDiskBytes integer 1073741824 (1 GiB) 1073741824 reload Ingestion pauses while dataDir has less than this much free, and resumes on its own when space returns. Serving is never affected.
indexing.retryBackoff duration 1m0s 1m0s reload Pause after the first failed clone or fetch. Doubles per consecutive failure, capped at retryBackoffMax. The last good state keeps serving throughout.
indexing.retryBackoffMax duration 1h0m0s 1h0m0s reload Ceiling on the exponential backoff, so an unreachable host is retried on a fixed cadence rather than never again.

analysis

The analysis pass. Deliberately a separate pool from indexing.

Field Type Default Example Reload Description
analysis.inventoryConcurrency integer 8 8 restart Phase A (line counts and languages). Outranks every repository's phase B.
analysis.deepConcurrency integer 4 4 restart Phase B (security, quality, technology).
analysis.fullResyncInterval duration 168h0m0s 168h0m0s restart How often the whole estate is re-analysed from scratch.
analysis.perFileTimeout duration 10s 10s restart Watchdog on a single file. A file that exceeds it is reported as a gap, never silently skipped.
analysis.maxEdgesPerRepo integer 500000 500000 restart On breach: keep counts, drop edges, report a gap.
analysis.niceValue integer 10 10 restart Scheduling priority of the analysis workers.
analysis.skipMinified boolean true true restart Skip a file whose longest line is over 5,000 bytes BEFORE parsing it, rather than letting it burn the whole perFileTimeout. Defaults to true. The skip is counted and reported with reason 'minified', never silent.
analysis.history.churnDays integer 90 90 restart Default hotspot window in days. 0 disables churn and ranks by complexity alone, which is stated in the response. A caller may pass its own days parameter, capped at 365; this instance default is not capped, because how far back an estate is worth reading is the operator's decision.
analysis.history.maxAgeDays integer — 0 restart How far back the walk itself goes. 0 walks all history, bounded by maxCommits — coupling is a question about a repository's whole life, so the default does not window it.
analysis.history.maxCommits integer 5000 5000 restart Commit cap for one walk. A repository with more commits than this is answered from the newest maxCommits, and the truncation is stated in the response rather than applied silently.
analysis.history.timeout duration 30s 30s restart Budget for one walk. Overrun is NOT fatal: what was read is kept and the response says the history behind it is partial.
analysis.history.bulkCommitFiles integer 50 50 restart Commits touching more than this many files are excluded from coupling, and the exclusion is stated in the response. Without it, one reformat commit couples every file to every other and the whole feature is worthless.
analysis.history.minSupport integer 2 2 restart Default floor under how many commits a coupled pair must share. One shared commit is a coincidence — most obviously the initial import.
analysis.history.minConfidence number 0.3 0.3 restart Default floor under the conditional probability that changing A also changed B, between 0 and 1.

Search execution budget.

Field Type Default Example Reload Description
search.timeout duration 30s 30s reload Covers queue time PLUS execution, not execution alone — the number an operator cares about is how long a user waits.
search.concurrency integer 4 4 reload A semaphore that serialises. There is no queue limit and no rejection: a slow search is better than a refused one.
search.ranking.ranker string, one of bm25 | tfidf | simple | structural bm25 bm25 reload bm25 | tfidf | simple | structural. The scoring model itself. Defaults to bm25. A code_filter forces structural regardless, because the filter's meaning is a region weighting the other three models do not have.
search.ranking.docPenalty number — 0 reload 0 to 1. Demotes prose files — Markdown, reStructuredText, plain text — and SCALES WITH TERM COUNT: a one-term query gets no penalty at all, because a README hit is frequently the right answer to one word. 0 disables it.
search.ranking.commentWeight number 1 1 reload 0 to 1. What a match inside a comment is worth against a match in code. 1 disables it. This is the knob aimed at comments mentioning a thing outranking the code implementing it.
search.ranking.stringWeight number 1 1 reload 0 to 1. The same for a match inside a string literal. 1 disables it, which is the default: a string literal is frequently the answer.
search.ranking.declarationBoost number 1.6 1.6 reload 1 to 10. Multiplies a file that DECLARES a query term over one that merely uses it. 1 disables it. Term frequency cannot tell a one-line header declaration from eight call sites, and gets them backwards.
search.ranking.minTermMatchRatio number 1 1 reload 0 to 1. The fraction of a query's terms a file must contain. 1 is AND and is the default. Below 1 this changes WHAT MATCHES rather than what ranks: a saved query starts returning different files. Only a plain word-word-word query is relaxed; anything with NOT, a regex, an OR or a lang: filter is left alone.
search.ranking.relaxFromTerms integer 3 3 reload Term count at which minTermMatchRatio starts to apply. Defaults to 3 — relaxing two terms to one is a different query, not a relaxation.
search.index.backend string, one of cs | caisson caisson caisson reload cs | caisson. cs brute forces every repository. caisson consults the per-repository index where one exists and falls back to cs where one does not. Defaults to caisson: on a measured 358-repository estate it answered a query matching one file in 5ms against brute force's 887ms, and never more slowly — a query matching every file in the estate, where an index can narrow nothing, came out level. Set cs to turn every index off; what was already built is retained, so reverting a revert costs no re-warm.
search.index.minFiles integer 1 1 reload Searchable files a repository must have before an index is built for it. Defaults to 1 — every repository holding anything gets an index. The obvious per-repository reasoning for a high threshold is sound and its aggregate is not: brute force costs about 2.5ms PER REPOSITORY before it matches anything, so several hundred repositories that are each individually fast cost most of a second to skip, and they are simultaneously the cheapest ones to index because they hold almost no documents. RAM is the reason to raise it: a repository costs bloomSize*8 bytes per 64 documents with one block as the floor, so 32KB each at the default bloomSize whatever their size, and a full estate of 358 repositories measured 24.7MB. Re-evaluated on every refresh AND on every config reload, so lowering it warms the estate without a restart and raising it releases the memory.
search.index.bloomSize integer 8192 8192 reload Filter length in 64-bit words per block of 64 documents, which fixes memory at bloomSize/8 bytes per document — 1024 at the default of 8192. This is the number that governs an estate's RAM, and RAM is the binding constraint. Changing it rebuilds every index. Raised from 4096 on measurement (docs/verification/bloom-sweep.md): false positives come almost entirely from the small tail of files whose filter is over 90% full, and doubling the filter empties that tail — a literal absent from the corpus went from 72 candidate reads to 9. The cost is exactly linear in memory and there is no latency cost, because the scan runs once per block of 64 documents rather than once per word.
search.index.buildConcurrency integer 2 2 reload How many repositories are indexed at once in the background. Deliberately below the search concurrency: warming must never compete with serving, because cs answers correctly throughout and the only cost of warming slowly is that it finishes later.
search.symbols.backend string, one of parsed | heuristic parsed parsed reload parsed | heuristic. parsed serves the outline from the declarations the analysis pass extracted with a tree-sitter grammar — exact, with a real kind — and falls back to the heuristic for any language this build carries no grammar for. heuristic is the pre-plan-41 behaviour everywhere, including its coverage gaps. Defaults to parsed.
search.symbols.crossRepoCeiling integer 1200 1200 reload How many candidate FILES an estate-wide definitions or references lookup will answer before it DECLINES and says so. It bounds the ANSWER and not the search: above it the query returns no locations plus a gap naming the count, because a name matched in thousands of files cannot be resolved to one answer by matching it and fifty rows out of four thousand look like an answer without being one. Defaults to 1200, from the measurement in docs/verification/41b-01-ceiling.md — on a real 31-repository estate the ubiquitous names (Error 5332 files, New 3774, Validate 2969, Config 1727) are all above it, the names people navigate to (Handler 481, ProcessConstants 28) are all below it, and nothing lands in between. Raising it will NOT make cross-repository navigation faster: the latency is the search, and search.index is the knob for that. There is no unlimited.

api

The HTTP API.

Field Type Default Example Reload Description
api.rawContentOrigin string — "…" restart Serve raw repository bytes from a separate origin, so hostile file content cannot script against the UI origin. Empty by default.
api.apiKeys.ttl.min duration 1h0m0s 1h0m0s restart Shortest lifetime a key may be issued with.
api.apiKeys.ttl.default duration 720h0m0s 720h0m0s restart Lifetime when the caller asks for none.
api.apiKeys.ttl.max duration 2160h0m0s 2160h0m0s restart Longest lifetime a key may be issued with.
api.apiKeys.requireName boolean true true restart Refuse a key created without a client name. Defaults to true: an unnamed key is unauditable, and naming it is what lets the audit log say WHICH agent acted.
api.apiKeys.allowNoExpiry boolean false false restart Permit a key that never expires. False by default, because a non-expiring credential is a finding in any audit.
api.apiKeys.rateLimit.requestsPerMinute integer 5000 5000 restart Requests per minute for one credential. Zero is unlimited, which is the right default for an instance that has not thought about it: a ceiling nobody chose that starts refusing agent calls is a worse first day than no ceiling.
api.apiKeys.rateLimit.anomalyMultiplier integer 10 10 restart Alert when a credential exceeds this multiple of its OWN rolling baseline. A key that suddenly enumerates the whole estate looks nothing like itself; a global threshold has to be set for the busiest key and therefore never fires for the others. Zero disables it.
api.apiKeys.rateLimit.window duration 1m0s 1m0s restart Accounting window, and the anomaly detector's sampling interval. Defaults to 1m so requestsPerMinute means what it says.
api.prettyJson boolean true true reload Indent JSON response bodies so a person running curl can read them. Defaults to true. HTTP responses only: application logs and audit records stay one object per line whatever this is set to. Turn it off for a bandwidth-sensitive integration; MCP tool results are unaffected either way, because the tool renderer formats the decoded object itself.

mcp

The MCP listener.

Field Type Default Example Reload Description
mcp.auth.mode string, one of oauth | apikey | both both both restart oauth | apikey | both. This vocabulary is deliberately disjoint from auth.mode's.
mcp.auth.tokenTtl duration 8h0m0s 8h0m0s restart How long an OAuth access token minted for an MCP client is valid. It is how long a revocation this instance cannot see takes to bite: a refresh never passes the sign-in proxy, so it is checked against the identity path instead - in local mode the account, which a disable ends at once; in proxy mode the last time the proxy vouched for the person in a browser, and a refresh is refused once that is more than seven days old. Refresh tokens survive a restart; access tokens do not, and a client refreshes silently. Defaults to 8h.
mcp.oauth.publicUrl string — "…" restart Base URL an MCP client reaches this instance's API and MCP listener on, e.g. https://searchcode.example.com. Becomes the OAuth issuer and the protected-resource identifier, and a presented token is checked against it. Defaults to the address the request arrived on.
mcp.oauth.uiUrl string — "…" restart Base URL a BROWSER reaches the UI listener on. The authorization endpoint is published under it, because that is where the user's session lives. Defaults to publicUrl, then to the address the request arrived on.
mcp.maxBytesPerCall integer 100000 100000 restart Hard ceiling on the bytes one tool call may return. This is the control that does the protecting; truncation is always stated in the text the model reads. Defaults to 100000.
mcp.serverName string searchcode-private searchcode-private restart The key the /account page renders its ready-to-paste MCP client configuration under, and the name used in the claude mcp add line. It is a name local to the reader's client file and must be unique within it; pasting over an existing entry replaces it silently. Letters, digits, dot, dash and underscore, up to 64 characters. Defaults to searchcode-private, which deliberately does not collide with the name searchcode.com publishes for the hosted product. Requires a restart to change.
mcp.maxResultsPerCall integer 25 25 restart DEFAULT number of results a tool returns, not a cap: a caller asking for more gets more, up to the API's hard maximum of 1000 rows a call. A low limit here would make this tool worse than cs at the discovery task the product exists for; the maximum exists because max_results with nothing behind it answered one request with 87.9 MB. A request above it is clamped rather than refused, and a clamp that actually withheld rows says so and names the offset that reaches the rest. Defaults to 25.

analytics

Who-is-doing-the-work reporting. The default names nobody; attributeAuthors is the operator's own privacy decision and is off.

Field Type Default Example Reload Description
analytics.concentrationFloor integer — 3 reload Distinct contributors a file needs before the exact count and top share are reported instead of the band "few". 0 uses the default of 3. The right value depends on your team size: in a team of four, "one contributor" identifies somebody whether or not a name is printed.
analytics.attributeAuthors boolean — false reload Report WHICH PEOPLE did the work, not just how concentrated it was. OFF by default. Turning it on makes per_user_breakdown read true on /api/v1/usage, because that is exactly what it is. Works-council rules in Germany and France prohibit developer monitoring, so this is the customer's decision and never a default.
analytics.attributeUsage boolean — false reload Show a per-person usage table on the admin page: names and record counts over the last seven days, read from this instance's own audit records. OFF by default. NAMES AND COUNTS ONLY, and never on /api/v1/* or MCP. Needs audit.output: file, since on stdout or syslog there is nothing to read back. A SEPARATE decision from attributeAuthors, which is about who wrote the code rather than who uses this.

advisories

The offline security advisory data. Shipped inside the binary, side-loaded from a signed bundle, NEVER fetched.

Field Type Default Example Reload Description
advisories.bundle string — "…" restart Path to a signed advisory bundle, side-loaded for an air-gapped site. Empty uses the database compiled into the binary. Requires a restart to change.
advisories.warnIfOlderThanDays integer 30 30 reload Warn on /api/v1/status, in the admin UI, in metrics and in every vulnerability response once a SIDE-LOADED bundle is older than this. 0 turns that sentence off. It does NOT change advisory_database.stale, which is fixed: a database is stale when it is absent, when it is the compiled-in seed at any age, or when a side-loaded bundle is more than 30 days old. The compiled-in seed always says so, because its problem is that it is a seed rather than that it is old. Reloadable.

serviceAccounts

Non-human principals. The only place a machine credential is declared.

Field Type Default Example Reload Description
serviceAccounts list of object — […] — Non-human principals. The only place a machine credential is declared.
serviceAccounts[].name string — platform-dashboard reload Shown in audit records as the acting principal.
serviceAccounts[].groups list of string — [platform] reload REPOSITORY groups this account may reach, named directly — NOT identity groups, and not matched against authz.bindings. Every other groups: in this file is an identity group, which is why this one says so. Read LIVE from this file on every request, unlike a human key's frozen groups, so revoking access here is immediate.
serviceAccounts[].role string — viewer reload A built-in or custom role.
serviceAccounts[].tokenFile string — /run/secrets/searchcode-dashboard-token reload File holding the bearer token. A reference, never a value.
serviceAccounts[].tokenEnv string — "…" reload Environment variable holding the bearer token.

branding

White-labelling. Unconditional, on every tier.

Field Type Default Example Reload Description
branding.productName string — Acme Code Search reload Replaces "searchcode" in the UI chrome and the page title.
branding.logoLight string — brand/logo-light.png reload Path to the logo shown on a light background. Relative to this file's directory, and confined to it: a path outside, or inside instance.dataDir or /run/secrets, is refused. The file is read ONCE at load and served from memory, and it must be a raster image — the type is sniffed, never taken from the extension, and SVG is refused because it carries scripts.
branding.logoDark string — brand/logo-dark.png reload Path to the logo shown on a dark background. Same boundary and same accepted types as logoLight.
branding.favicon string — brand/favicon.ico reload Path to the favicon. Same boundary and same accepted types as logoLight.
branding.primaryColour string — #2563eb reload A hex colour: #rgb, #rgba, #rrggbb or #rrggbbaa. Hex only, because it is interpolated into a stylesheet — this is a CSS injection surface, and a colour NAME cannot be told apart from a misspelling.
branding.hideSearchcodeBranding boolean false false reload Remove the "powered by searchcode" footer.
branding.supportUrl string — https://acme.example.com/support reload Where an error page tells the user to go for help.
branding.footerHtml string — "…" reload Sanitised before rendering. This is a stored-XSS surface.

observability

Metrics and tracing.

Field Type Default Example Reload Description
observability.metrics.enabled boolean true true restart Serve the Prometheus endpoint. Defaults to true; the listener is separate, so turning it off is a decision about scraping rather than about exposure.
observability.metrics.listen string 0.0.0.0:9090 0.0.0.0:9090 restart Address to bind for /metrics, host:port. Separate from server.listen so a NetworkPolicy can expose it to Prometheus without exposing it publicly — repository names appear in labels. Requires a restart to change.
observability.tracing.enabled boolean false false restart Off by default. One of the three things that can make this process open an outbound connection — the others are a connections.sources entry and a remote syslog audit sink — and it points at your own collector, never at a vendor.
observability.tracing.endpoint string — "…" restart The customer's own OTLP collector. Never a vendor endpoint — searchcode never phones home.
observability.tracing.sampleRate number 1 1 reload Fraction of requests traced, 0.0 to 1.0. Defaults to 1.0, which only matters once enabled is true.
observability.pprof.enabled boolean false false restart Serve Go's profiling handlers under /debug/pprof on the METRICS listener. Off by default. ESCALATION: anything that can scrape metrics can then dump the heap, and a heap dump of this process contains customer source code — so turning this on makes metrics access equivalent to source-code access. Turn it on for as long as it takes to collect a profile and turn it off again. Requires a restart to change.

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