Skip to content

Diagnostics commands

Global flags are in the CLI hub.

gdsgate doctor

Runs a read-only health checklist against the local configuration and state, then exits. Every warning or failure carries a one-line fix.

Group Checks
configuration File paths exist and are readable, 0600 on secrets, invariants such as ha needing PostgreSQL, connector-backend validity.
features What is enabled, what is off, and why.
state Store persistence, schema and CA consistency (Authority); transport-identity freshness (node); cached logins (client).

The analysed role comes from --role, installed role metadata, or the configuration's declared sections. An unrelated database in the home directory does not make a client or Connector an Authority. Empty Connector configurations, Proxy configurations with separate ports, and agent hosts retain their own role. Periodic metrics and node health checks use the running component's role.

On a host with several components, select the role or an explicit file:

gdsgate doctor --role proxy
gdsgate --config /etc/gdsgate/authority.toml doctor --role authority

--role accepts authority, proxy, connector, agent-host, client and all. Without an explicit path it selects that role's installed configuration; client selects the account's context. JSON includes role and config_source. Previous-result snapshots are separate per role and configuration file. For a running server, runtime.reload reports the loaded path, generation and last refusal. JSON exposes the same information in runtime_reload; an unreachable channel yields null. See configuration reload. Nothing is changed. A store schema behind this binary is a failure naming gdsgate authority store migrate; doctor does not apply it.

A parameter in secret-reference notation is reported by its source: file, variable or command. The 0600 check applies to the file form only; for a variable or command the report says there is no file to check. doctor does not resolve references; an unresolvable one is reported by the step that resolves it.

Platform-specific checks:

Check Platform
state.client_net (the client helper service can issue a name-resolution rule and a network adapter) Windows
sandbox.console (a sandboxed program at a console can read its screen; see Delegation → The sandbox on Windows) Windows
sandbox.userns_confinement, sandbox.unix_socket Linux
state.connector.catalogue Linux

On Windows the 0600 check reads the file's access-control list; there is no separate mode row.

For a PostgreSQL backend in session_role mode, doctor connects with the backend's service account and runs a SELECT against pg_catalog for each session role: whether it exists, whether the service account can assume it, and what it can write. Grants to PUBLIC and tables the role owns are reported apart from ordinary grants. A profile with read_only = true that can write is a failure.

Condition Result
CockroachDB backend Reported as unchecked for this probe.
Several databases in the cluster Each profile is checked in every database the service account reaches (<resource>.<db> opens any of them), up to twelve per run. The report says when it stopped short.
Database does not answer within five seconds Reported as unchecked.
Backend unreachable Warning.

--json emits a machine-readable report. Exit code 2 if any check fails, 0 when only warnings or info remain; usable as a systemd ExecStartPre= or a container healthcheck.

doctor runs on configurations a service refuses to start on. A profile that stops authority, proxy, connector and all is a config.profile finding here. A --config naming a missing file, or a file that is not valid TOML, fails the command.

The report:

gdsgate doctor · authority · stand01.example.com · 0.10.0

Configuration
  ✓ deployment profile         pilot
  ✓ registration listener      TLS · 127.0.0.1:50050
  ✓ join relay                 stand01.example.com:50064
  ✓ public join                off

Features
  ✓ audit chain anchoring   ✓ JIT approvals   ✓ node revocation   ✓ delegation grants
  ✓ inventory receipts   ✓ Cedar policy   ✓ health check   – external OIDC   – WebAuthn step-up

State
  ✓ store                      postgres://stand@localhost:5432/stand
  ✓ store schema               current
  ! policy file                differs from active version 10
  ✓ CA                         5 · consistent

1 warning · 0 failures

To do
  ! policy file differs from active version 10
    keep the file:    gdsgate authority policy push /etc/gdsgate/policy.cedar
    keep the store:   gdsgate authority policy pull --output /etc/gdsgate/policy.cedar

Details: gdsgate doctor --explain · machine-readable: gdsgate doctor --json
Mark Word Meaning
✓ ok Passed.
! warn Degraded; listed under To do.
✗ fail Failed; listed under To do; exit code 2.
– off / info Feature off, or a fact with nothing to do.
? ? Not checked from here.

The words replace the marks when stdout is not a terminal or the locale is not UTF-8. Colour only on a terminal; NO_COLOR turns it off. --quiet prints the summary and To do only, and nothing when every check passed.

Four or more identical findings (cached logins, CAs) print as one line with a count.

--explain prints under each line its check id, what was checked, the figures behind the verdict, and every member of a folded line. --json carries every check, with the same text in title, detail, remedy and explain.

Each run saves a snapshot to ~/.gdsgate/last-doctor.json and compares it with the previous one. Under --explain a changed check is annotated ↓ worse than last run, ↑ better than last run (failure now a warning) or ↑ recovered (nothing left to do), and the summary adds N worse than last run. A check that covers several items (cached logins, roles) is compared item by item.

Shared service state parent

On Linux, state.service_root checks /var/lib/gdsgate on every doctor run, including client-only configurations and runs as root. It reads metadata without following a link. Expected: a directory with mode 0755; individual role directories and keys retain private permissions.

State Verdict Remedy
Directory, mode 0755 ok None
Directory, mode 0700 or another mode fail, with actual and expected modes sudo chmod 0755 /var/lib/gdsgate, or rerun service install as root
Old shared-layout link fail Rerun service install as root to migrate the recognized layout
Another entry at that path fail Preserve and move the conflicting entry, then install again
Absent parent info A privileged service install creates it

Root can bypass search permissions, so a successful read by the operator does not establish service access. Doctor checks the mode directly and changes no permissions. Change only the parent; a recursive chmod would expose private role state. Verify with another doctor run, then start the service using service commands.

What this node declares against what the cluster says

On a node with [[connector.backends]], one state.connector.catalogue check per entry compares it with the cluster's answer at this node's last registration. doctor reads that answer from the running connector over the control socket in [enroll].state_dir; it connects nowhere else and writes nothing. Linux only; a Windows connector's report has no such row.

Value Status Meaning
ssh_profile=... onward_auth=... ok The cluster serves this resource to the node; the value is what the catalogue declares.
kept back: <reason> fail Held back with the cluster's reason: not_in_catalog, mode_not_confirmed, owned_by_another_node, gate_not_offered. The first two carry the authority resources set command that resolves them.
mode disputed fail The catalogue and this file disagree on onward_auth or ssh_profile; the node serves it under neither. --explain prints both values.
not registered yet warn The entry was added after the last declaration; run reload --role connector, then check admission. An older peer requires restart.
not checked: ... warn No connector running, not registered yet, or the socket unreachable.

A startup gate_not_offered can mean the current process is acquiring its capability receipt. It does not imply a missing catalogue row. For persistent withholding, inspect the receipt/renewal checks and the Authority response: Connector startup diagnostics.

doctor does not write to the catalogue. The authority resources set command a failure names needs an administrative identity.

Recordings waiting for delivery

On a node with central recording, three state.recordings.* checks read the state of the delivery pass. None run under mode = "disk".

Check Reads Reports Status
state.recordings.pending the recordings directory Casts waiting for a pass, total size, age of the oldest. info when anything waits; warn when the directory exists and cannot be read. An empty directory is reported as empty.
state.recordings.stuck .delivery.json Casts the last pass could not deliver, by reason; when that pass ran and what it moved. warn when a pass left anything behind (needs a person); ? when no pass has written a journal yet.
state.recordings.store a handshake with Authority Whether the central store answers. Runs only when pending is non-empty; five-second limit.

A successful store handshake confirms the route and certificates; the last upload attempt and its error are in stuck. Under authority_transport = "tunneled" the store check is not made (the route is the connector's reverse tunnel); the finding is ? and quotes the last pass.

state.recordings.sweep warns under mode = "central" with sweep_interval_secs = 0: no pass runs on the node, including at start-up, and only recordings push delivers recordings.

Recordings a killed connector left behind

On a node with central recording, two checks report what a killed connector left. Each reads one file.

Check Reads Reports
state.recordings.closed_at_start .startup.json What the last start did with recordings the previous run left half-written: closed, lost an unfinished last line, dropped with no event, could not be closed.
state.recordings.late_close .delivery.json Casts the last delivery pass held back for lack of a close event on the chain, split into: no open on the chain for the session, or still no close after one was written.
Check Condition Status What to do
closed_at_start A file could not be closed; it stays as .cast.partial. warn Check permissions on the directory the remedy names.
closed_at_start Start-up closed something. info Each session needs a close event; see late_close.
closed_at_start Recordings directory does not exist. info, nothing recorded yet None.
closed_at_start Directory exists, no .startup.json. ?, no report yet None.
late_close Anything waiting. warn A cast with no open on the chain is not moved by any pass; keep or discard it.
late_close No delivery journal yet. not printed state.recordings.stuck reports the missing journal.
gdsgate --config node.toml doctor
gdsgate --config node.toml doctor --explain
gdsgate --config node.toml doctor --json

gdsgate config schema [--role <role>]

Prints the JSON Schema (draft 2020-12) of the configuration file to stdout. The published copy is reference/config.schema.json.

Flag Default Meaning
--role <role> every key Only the top-level keys this role reads: authority, proxy, connector, client, agent-host.

Each property carries type, allowed values (oneOf of const), default and a one-sentence description. Objects reject keys they do not declare (additionalProperties: false).

Annotation On Value
x-gdsgate-roles every top-level key The roles that read the key and everything under it.
x-gdsgate-profiles keys a profile requires The profiles that refuse to start without the key; see the profile matrix.
gdsgate config schema > config.schema.json
gdsgate config schema --role proxy

Editors that read Taplo directives validate a file against it with a first line of #:schema https://gate.gds.dev/docs/0.11/reference/config.schema.json.

gdsgate config check <file> --role <role>

Checks one configuration file for one role, offline, and prints every problem with its key path and fix. Opens no connection and no store, runs no helper command, and reads no configuration other than <file>.

Flag Default Meaning
<file> required The configuration file.
--role <role> required authority, proxy, connector, client or agent-host.
--profile <profile> the file's [security].profile Judge under dev, pilot or production.
--json off Print a JSON array of {path, severity, message, fix}.
Finding Severity Source
TOML that does not parse error The parser; the only finding, path empty.
Unknown key error Keys no field claims; the fix names the key within two edits of it.
Wrong type or value outside the allowed set error The parser, one per top-level key; the rest of the file is still checked.
Profile refusal or warning error or warning The checks the role runs at start; the text report names the profile matrix row in brackets.
Missing or unreadable file, cross-key rule, invariant error or warning The configuration checks of gdsgate doctor.

For proxy, connector and agent-host the node counts as enrolled when [enroll] names an endpoint (or a join relay) and either a token is supplied ([enroll].token, GDSGATE_ENROLL_TOKEN in proxy.env or connector.env beside the file, which the unit reads, or GDSGATE_ENROLL_TOKEN in the environment of config check) or [enroll].state_dir already holds an identity. [audit].export_path not yet on disk is no finding: Authority creates it on first start. The store checks (replica-keyrings, sealed-row-keys) are not run.

Exit code Meaning
0 No findings.
1 At least one error.
2 Warnings only.
gdsgate config check /etc/gdsgate/authority.toml --role authority
gdsgate config check /etc/gdsgate/authority.toml --role authority --profile production --json
error    audit.export_pth: unknown key: the parser ignores it
         fix: rename it to `audit.export_path`, or remove it
error    store_auto_migrate: invalid type: string "yes", expected a boolean
         fix: give `store_auto_migrate` the type `gdsgate config schema` shows for it
error    store_url [store-postgres]: `store_url` names no PostgreSQL store (sqlite:///var/lib/gdsgate/store.db?mode=rwc): one writer, lost with the host
         fix: `store_url = "postgres://gdsgate@db/gdsgate"`, password in `store_password_file`; `setup prepare --store-url`.
warning  audit.export_path [audit-export]: `[audit].export_path` is unset: no off-cluster copy of the audit log
         fix: `[audit].export_path = "/var/lib/gdsgate/authority/audit.jsonl"` (Windows: `%ProgramData%\gdsgate\state\authority\audit.jsonl`), shipped by a log collector.
3 error(s), 1 warning(s): authority.toml, role authority, profile as declared
[
  {
    "path": "store_url",
    "severity": "error",
    "message": "`store_url` names no PostgreSQL store (...): one writer, lost with the host",
    "fix": "`store_url = \"postgres://gdsgate@db/gdsgate\"`, password in `store_password_file`; `setup prepare --store-url`."
  }
]
Symptom Cause What to do
error (file): TOML parse error at line N The file is not valid TOML. Fix the line named; no other check runs until it parses.
unknown key: the parser ignores it A misspelled or removed key. Rename it as the fix says, or remove it. gdsgate config schema lists the keys.
invalid type: ..., expected ... A value of the wrong type, or a word outside the allowed set. Use the type or value gdsgate config schema gives for the key.
not found: <path> A file the configuration names is absent on this host. Create it, or run the check on the host the file is for.
audit export file: the service cannot write it: <path> gdsgate-authority.service is installed with ProtectSystem=strict and the path is outside its state directory and ReadWritePaths=. Authority starts with the export off. Move [audit].export_path under /var/lib/gdsgate/authority, or add its directory to ReadWritePaths= in a drop-in.
the client-facing listener has no TLS for --role proxy No [proxy].public_cert_file pair and nothing to enrol with. Set the pair, or [enroll].endpoint and [enroll].token.

Reading a failure

Start with --explain: the check id and the source it names. Missing identity, invalid TOML or Cedar, a pending store migration, and an unreachable route each have their own remedy. Remove tokens, private keys and full policy contents from JSON before attaching it to an incident ticket.