Status & errors

How run and span status work, and how errors are captured with a type, HTTP status code, stack trace, and severity level.

Every run and span carries a status, and errors are how a span or run becomes error. Most of the time it's automatic: a thrown exception inside the wrap is captured for you, including the error type, message, HTTP/provider status code, and stack trace. You can also record errors and set severity explicitly.

Status

LevelStatus valuesHow it's set
Runok · error · runningrunning until the run ends, then ok, or error on a thrown callback or setErrorSummary
Spanok · errorok on success, error on a thrown callback or setError

A run stays running until wrapAgent returns (or endRun is called), and rolls up an error_count from its spans.

Severity level

Alongside status, every span and run carries a level, a severity gradient:

LevelUse for
debugVerbose/diagnostic spans
defaultNormal spans (the implicit default)
warningA recovered or retried step, notable but not a failure
errorA failure

level is derived automatically (error on a failure, otherwise default), so you never have to set it. Set it explicitly only to flag a warning/debug without changing the ok/error status:

await trodo.withSpan('fetch', async (span) => {
  span.setTool('fetch');
  const res = await fetchWithRetry(url); // succeeded, but only after a retry
  span.setLevel('warning');              // status stays 'ok'
  return res;
}, { kind: 'tool' });
with trodo.span('fetch', kind='tool') as span:
    res = fetch_with_retry(url)  # succeeded, but only after a retry
    span.set_level('warning')    # status stays 'ok'

Errors are captured automatically

A thrown exception inside wrapAgent, a span helper, or withSpan is recorded and re-raised. The failing span gets status='error', level='error', and, parsed from the error object, error_type, error_message, status_code and stack_trace. The run's error_count increments, and if the throw bubbles to the run, the run is marked error too. You don't need a try/catch just to capture it.

await trodo.wrapAgent('agent', async (run) => {
  // If the OpenAI/Anthropic call throws a rate-limit error, the span records
  // error_type='RateLimitError', status_code='429', the message, and the stack.
  return await agent.run(query);
});
with trodo.wrap_agent('agent') as run:
    # If the provider call raises a rate-limit error, the span records
    # error_type='RateLimitError', status_code='429', the message, and traceback.
    return agent.run(query)

The status code is read from the thrown error's status / status_code / response.status_code / code, covering OpenAI, Anthropic, httpx/requests, fetch/axios, and stdlib errors. Auto-instrumented provider spans (OpenAI, Anthropic, LangChain, …) surface the same fields from their OpenTelemetry exception events.

Record an error manually

When you catch an error yourself, to recover or to add detail, set it on the span. This finalises the span as error without re-throwing.

await trodo.withSpan('charge', async (span) => {
  span.setTool('charge');
  try {
    return await billing.charge(id);
  } catch (err) {
    span.setError({
      type: 'ChargeFailed',
      message: err.message,
      statusCode: err.status,   // optional: HTTP/provider code
    });
    return fallbackReceipt();    // recover; span is still 'error'
  }
}, { kind: 'tool' });
with trodo.span('charge', kind='tool') as span:
    try:
        return billing.charge(id)
    except Exception as e:
        span.set_error(
            str(e),
            type='ChargeFailed',
            status_code=getattr(e, 'status', None),  # optional
        )
        return fallback_receipt()  # recover; span is still 'error'

For a run-level summary, set it on the run or pass it to endRun:

await trodo.wrapAgent('agent', async (run) => {
  if (!ok) run.setErrorSummary('No candidates returned', { type: 'NoCandidates' });
});

// long-running runs:
await trodo.endRun(runId, { status: 'error', errorSummary: 'timed out' });
with trodo.wrap_agent('agent') as run:
    if not ok:
        run.set_error_summary('No candidates returned', type='NoCandidates')

# long-running runs:
trodo.end_run(run_id, status='error', error_summary='timed out')

Two levels of error

LevelTriggerSets
Span errorA helper or withSpan callback throws, or setErrorstatus='error', level='error', error_type, error_message, status_code, stack_trace; increments the run's error_count
Run errorThe wrapAgent callback throws, or setErrorSummarystatus='error', level='error', error_summary, error_type on the run

A throw inside a nested span that bubbles all the way up sets both.

Where errors show up

Every failed run and span lands on the Failures tab (Assess → Issues, where it opens), grouped by agent, tool and error, and filed into a category from its type, status code and message: timeout, rate limited, auth, not found, refused, bad request, server error, connection, code error or other. A failed parent only repeats its child's error, so a failure is counted once, at the deepest span that failed. The same categories are a filter on alerts.

Sending status_code and a real error_type is what puts a failure in the right category.

Errors that don't throw

Some providers return HTTP 200 with an error in the body. You have two choices:

  • Record it explicitly with setError (Node) / set_error (Python): the span becomes an error and counts toward error_count, without you re-throwing.
  • Keep status='ok' and tag it with setAttribute('llm_error', …), so it stays filterable without inflating the error count. Optionally setLevel('warning') to flag it.

Next

On this page