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 that mirrors Langfuse:

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.

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