Skip to main content

Debugging

Most Rank debugging falls into three layers:

  1. compile-time diagnostics from rank check
  2. graph and dependency inspection from rank deps
  3. runtime evaluation tracing from rank serve --trace-eval or rank <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> {} or HTTP::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 @sensitive stay redacted as <sensitive>
  • this applies to the same diagnostic renderers used by runtime annotation failures such as @constraint and @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:

  • schemaVersion
  • timestamp
  • executionId
  • requestId
  • route
  • kind
  • phase
  • status
  • sourcePath
  • modulePath
  • bindingName
  • provider
  • durationMs
  • message
  • diagnostic
  • detail

Typical event kinds in the current slice:

  • binding.evaluate_started
  • binding.evaluate_succeeded
  • binding.evaluate_failed
  • function.entered
  • function.exited
  • function.failed
  • provider.invoke_started
  • provider.invoke_succeeded
  • provider.invoke_failed
  • commit.mutation_started
  • commit.mutation_succeeded
  • commit.mutation_rejected
  • commit.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.