forge obs asks an environment’s telemetry a question and prints the answer as a table for you or, with --json, as one document a script or an agent can parse. The same verbs work against your local stack in development and against a hosted environment, because both go through one query compiler. A query that works locally means the same thing against production.
After you change something, four questions decide what you do next, and each has a command:
The environment argument defaults to
dev. Which backend answers is decided by the environment’s KCL, not by a flag: an environment with no destination reads the local ClickStack that forge env up started, and a hosted environment reads the platform’s store with your credential (under Reliant, the credential helper supplies it). If the local stack is down, the command exits 2 and tells you to run forge env up.
The loop
obs check: did the deploy work?
forge obs check judges one promotion. It takes the window of time after the release went live and compares it with an equal window before, per service, then lists what the release opened: new and regressed issues, alerts still firing, workload restarts. The verdict is PASS, FAIL or INCONCLUSIVE.
--wait polls until the after window has finished, which is how you run it straight after a deploy. Each window stops at the neighbouring promotion and at the present, and the error rate is weighted for tail sampling so dropped healthy spans do not distort it. A local environment has no promotions, so --since (or the start of the last forge env up) names the instant to judge from, and regressed issues, alerts and restarts are reported as not evaluated.
The exit codes
The exit code is the contract. It is the same as theexit_code field in the JSON document on every path, so a shell script and a program reading the document always agree.
Here is the exit-2 case, captured in a scratch project whose local stack was not running. The command did not guess:
ok is false, the exit code is 2, the reason is in plain words, and suggested holds a complete command to run next. The same shape appears on every forge obs verb.
What fails a release
The environment declares the thresholds in its KCL, so the console, the CLI and alerting all judge the same way. They are recorded in the release, and--threshold name=value overrides one for a single call.
--fail-on changes the failing set for one call, and the document says where each effective threshold came from: the release, a default, or your request. --gate-json FILE writes the evidence in a form forge gate record --from FILE attaches to the promotion.
For an environment on Reliant hosting, the platform also judges every promotion itself once the after window closes and stores that verdict once, append-only. A failing verdict raises a ReleaseRegressed alert. The optional promotion health gate (delivery_policy.health_gate) reads that stored verdict, so a release can be held until staging’s verdict for it is pass. The gate is opt-in and off by default, and you choose which environment’s verdict a release must have before it moves on. The CLI exit code tells you what the gate will see, and loosening it locally with --threshold or --allow-inconclusive changes nothing on the server.
obs status: what is wrong right now
When there is no deploy to judge and you only know something is wrong, start withstatus. It reads each section independently and prints them together: requests, error rate and p95 per service; new and regressed issues; alerts firing or pending (hosted); when the newest log and span arrived; and the release currently in force with the last few changes.
unread with the reason under warnings, so “no issues” and “could not read issues” never look alike. A section that was read and is empty is an empty list. Status reports; it does not judge. It exits 0 when everything was read and 2 when any evidence could not be, so use check to gate a release and status to orient yourself.
Captured against the scratch project with the stack down:
Issues, traces, logs and rows
forge env releases list|show|at <env> completes the picture by saying which release was live at any moment, and forge obs issue, trace and db query documents carry the live release, so you can go from an error to the change that shipped it.
Incidents
An issue is one distinct bug, not one log line; how issues are fingerprinted and what states they move through is in Hosted dashboards, alerts and issues. A dead database fails every caller, and each caller is its own call site and so its own issue.forge obs issues marks issues that share a cause and first fired within ten minutes of each other as one incident and collapses them under the first, so an outage shows up as one row. The others still exist with their own counts, and --expand-incidents lists them.
Reading the database: forge db query
forge db query <env> "<sql>" runs one read-only statement and prints the rows, so after a trace says what failed, the rows say what state caused it. The environment is always required, dev included, so a query never reads a database by accident.
- Read-only by construction. Each query runs in a
READ ONLYtransaction with a statement timeout, as a single statement, then rolls back. A lexical guard refuses writes first so the message is readable, and Postgres would refuse them anyway. The defaults are 200 rows and 5 seconds; narrow the query rather than raising the caps. - Secrets stay unreadable. The connection uses a
forge_rorole that holds column-levelSELECTonly. A column markedforge:secret, or one whose name looks like a credential and that nobody has decided, is refused through any expression, such asselect upper(api_key). - Policy. An environment’s
data_accesssetting isopen,auditedordeny. The default for a deployed environment isaudited: a person passes--confirm-env <env>and a--reason, and on hosting the platform records an audit row for the call.
The JSON contract
Everyforge obs verb, and forge db query, prints the same kind of document with --json:
- One document on stdout. It carries
schema(for exampleforge.obs.check/v1),ok,exit_codeanderror, computed from the same error the process exits with. Progress and warnings go to stderr. - Bounded by default. Lists carry
truncatedandnext_page_token, so an agent’s context is not flooded. - Next steps included.
suggestedholds up to three complete, runnable commands. - Projectable.
--fields a,b.ckeeps only the paths you name, to save tokens. A misspelled path is refused with the real list. - No placeholders. A missing key means unknown, never a made-up zero.
- Inspectable.
--explainprints the compiled SQL instead of running it, so you can copy a statement intoforge obs querywhen the structured verbs cannot express what you need.
Credentials
A CI token reads telemetry with theobs:read scope (forge cloud token create --env prod --scopes obs:read). Triage needs obs:triage, and forge env releases against a hosted environment also needs deploy:read. Under Reliant, the credential helper grants these from the signed-in person’s own authority and you never run forge login. A hosted db query on an audited environment is for people only; an organization automation token is refused.
Related topics
Why this helps an agent shows these commands in an agent’s loop. Hosted dashboards, alerts and issues describes the built-in alerts that feedstatus and check. Environments & deploys covers promotion, which is what check judges.