Skip to content

Diagnostics commands

Common global behavior is documented in the CLI hub. The complete diagnostic command contract follows.

gdsgate doctor

Run a read-only health checklist against the local configuration and state, then exit. Findings are grouped into configuration (file paths exist and are readable, 0600 on secrets, semantic invariants such as ha needing PostgreSQL, connector-backend validity), features (what is enabled, what is silently off, and why), and state (store persistence, store schema and CA consistency for Authority, transport-identity freshness for a node, cached logins for a client). Every warning or failure carries a one-line fix.

A parameter that accepts the secret-reference notation is reported by where its value comes from: a file, a variable, or a command. The 0600 check applies only to the file form; a value read from a variable or a command has no file to check, and the report says so instead of guessing. Nothing behind a reference is read for this: an unresolvable command or variable is reported at the point that actually tries to resolve it, not by doctor.

Applicable checks are detected from the config: Authority checks run when a store is configured, node checks when enrolment or connector backends are set, client checks against ~/.gdsgate. Nothing is mutated, so it is safe as a preflight: a store schema that is behind this binary is reported as a failure naming gdsgate authority migrate instead of applied.

Most checks read local files. One does not: for a PostgreSQL backend in session_role mode, doctor connects with the backend's own service account and asks the database what each session role can do, whether the role exists, whether the service account can assume it, and what it can write, separating a grant made to PUBLIC, which reaches every role, and a table the role owns, with full rights and no ACL entry, from an ordinary grant. A profile declared read_only = true that can write is a failure; the query is a SELECT against pg_catalog and nothing is written.

That catalog grant probe is PostgreSQL-only: it queries pg_catalog and does not run against a CockroachDB catalog. The connector's session-role path still supports both PostgreSQL and CockroachDB over the PostgreSQL wire protocol; a CockroachDB backend is reported as unchecked for this doctor probe rather than skipped in silence. Every other check covers every kind.

Grants live per database, and a session is not confined to the one in the DSN, since <resource>.<db> opens the same backend against another database, so each profile is checked in every database of the cluster the service account can reach, up to twelve per run, and the report says when it stopped short. A database that does not answer within five seconds is reported as unchecked rather than clean, and an unreachable backend is a warning rather than a failure: somebody else's database being down is not this host's misconfiguration.

--json emits a machine-readable report. The exit code is 2 if any check fails, and 0 when only warnings or info remain, so it slots into a systemd ExecStartPre= or a container healthcheck.

A configuration a service refuses to start on is exactly when the checklist is wanted, so doctor is not gated on the settings a service is gated on. A profile no process could publish stops authority, proxy, connector and all; here it is a config.profile finding, listed beside everything else the same run found. The one thing it does need is a configuration to read: a --config that names a missing file, or a file that is not valid TOML, fails the command, since there would be nothing to report on but defaults nobody is running.

Findings that read identically are printed as one line with a count. A host with a few hundred cached logins has a state.login finding for each of them, and the report is a line instead of a page:

! login expired · 6 of them
#   ↳ re-run `gdsgate login`
#      `gdsgate doctor --explain` names them

--explain lists what each folded line stands for, in place of the count. It has no effect on --json, which carries every check either way.

Each run saves a snapshot to ~/.gdsgate/last-doctor.json and compares against the previous one, so a check whose status changed since the last run is annotated: ↓ worse than last run, ↑ better than last run for a failure that is now only a warning, or ↑ recovered when nothing is left to do, with an N worse than last run line in the summary. Each finding is compared with itself: a check that covers several things, one cached login of many or one role of two, carries which one it is about, so one login recovering says nothing about the login beside it.

gdsgate --config node.toml doctor
gdsgate --config node.toml doctor --explain
gdsgate --config node.toml doctor --json

Reading a failure

Start with the check id and named source of truth. Missing identity, invalid TOML or Cedar, a store migration, and an unreachable route have different remediations. Capture JSON without including tokens, private keys, or complete policy contents in an incident ticket.