Debugging
Most Rank debugging falls into three layers:
- compile-time diagnostics from
rank check - graph and dependency inspection from
rank deps - runtime evaluation tracing from
rank serve --trace-evalorrank <entry> --trace-eval-log <path>
Start with the cheapest layer that can falsify your current hypothesis.
Compile-time checks first
If the problem is about types, imports, manifest wiring, or provider admission, start with:
rank check src/main.rank
This catches parse, name-resolution, type, manifest, and security-admission problems before any host-side execution starts.
If you are working from the repository source tree via npm run rank -- ..., use the same command as a fast compile pass, then confirm external-input examples with npm run rank -- <entry> or npm run rank -- serve <dir>. The current dev-path check command can still report browser-host external-input errors for programs that do work under the packaged CLI or real serve runtime.
Inspect the dependency graph
When the question is “why does this program need that input or provider?”, use:
rank deps src/main.rank
rank deps src/main.rank --format json
rank deps src/main.rank --format json --explain <id>
This is especially useful for confirming:
- which
Env<T> {}orHTTP::Fetch<T> {}nodes are present - which provider exports are in play
- where a dependency edge originates in the entry graph
Request-time server failures
rank serve already logs request-time handler diagnostics and unexpected exceptions locally before returning stable HTTP error codes such as HANDLER_EVALUATION_FAILED.
For validation-heavy local development, --dev also includes more response detail in supported runtime error bodies:
rank serve . --dev
That is useful when the failure is a bad request contract. It is not enough when you need to see the evaluation path that led to a provider call, binding failure, or commit mutation outcome.
Evaluation trace
Evaluation tracing is opt-in and structured. It emits newline-delimited JSON events with a stable event shape owned by the compiler.
Bare runs
Use a file sink so stdout still contains only the evaluated result:
rank src/main.rank --trace-eval-log .rank/eval.ndjson
The CLI creates parent directories as needed and truncates the target file at the start of each run.
Server runs
Use stdout when you want to watch live request handling:
rank serve . --trace-eval
Request-time events inherit request metadata automatically, so concurrent requests can be separated by executionId and requestId.
Redaction behavior
The current trace surface is intentionally conservative about values:
- successful trace events carry execution metadata, not full evaluated value payloads
- when a traced failure includes diagnostic notes, values already marked with
@sensitivestay redacted as<sensitive> - this applies to the same diagnostic renderers used by runtime annotation failures such as
@constraintand@unique
If you need raw sensitive values in trace diagnostics during a trusted local debugging session, opt in explicitly:
rank src/main.rank --trace-eval-log .rank/eval.ndjson --emit-sensitive
rank serve . --trace-eval --emit-sensitive
Outside that opt-in path, trace output keeps sensitive values redacted by default.
Reading trace events
Each line is one JSON object. Common fields include:
schemaVersiontimestampexecutionIdrequestIdroutekindphasestatussourcePathmodulePathbindingNameproviderdurationMsmessagediagnosticdetail
Typical event kinds in the current slice:
binding.evaluate_startedbinding.evaluate_succeededbinding.evaluate_failedfunction.enteredfunction.exitedfunction.failedprovider.invoke_startedprovider.invoke_succeededprovider.invoke_failedcommit.mutation_startedcommit.mutation_succeededcommit.mutation_rejectedcommit.mutation_unknown
Example trace line:
{"schemaVersion":1,"timestamp":"2026-05-13T12:04:53.100Z","executionId":"7bb4f25e-8cfd-4d2a-8c60-9c104fabc123","requestId":"7bb4f25e-8cfd-4d2a-8c60-9c104fabc123","route":"root::main::ListTodosRoute","kind":"provider.invoke_failed","phase":"provider","status":"failed","provider":"aws::DynamoDB::Query","message":"Provider aws invocation failed with ConditionalCheckFailedException: The conditional request failed."}
Practical workflows
Show only the high-signal fields from a CLI trace log:
jq -c '{kind, phase, status, bindingName, provider, route, message}' .rank/eval.ndjson
Focus on one live request from rank serve --trace-eval:
rank serve . --trace-eval | jq -c 'select(.requestId == "<request-id>")'
Watch commit mutation outcomes only:
jq -c 'select(.kind | startswith("commit."))' .rank/eval.ndjson
Current scope
The trace surface is intentionally narrow:
- it is for operational debugging, not a step debugger
- it traces semantic execution seams rather than every AST node
- it supplements stable CLI and HTTP outputs rather than replacing them
- it is currently strongest around bindings, functions, providers, and commit mutations
If the question is dependency shape rather than runtime behavior, prefer rank deps --explain. If the question is deterministic regression coverage, prefer Unit Testing.