DEV Community

Cover image for I stopped asking OpenClaw what broke and started reading the trace
Lars Winstand
Lars Winstand

Posted on • Originally published at standardcompute.com

I stopped asking OpenClaw what broke and started reading the trace

I used to do the same thing everyone does the first time an agent goes off the rails:

  1. Watch OpenClaw do something weird
  2. Ask it what happened
  3. Read a very confident explanation
  4. Realize 20 minutes later that the explanation was basically fan fiction

That loop gets old fast.

If you're debugging OpenClaw, or really any agent that touches APIs, browsers, queues, files, Discord bots, n8n workflows, or custom automations, the model's self-explanation is not evidence. It's a reconstruction.

The fix is boring, which is why it works:

  • emit OpenTelemetry traces
  • send them to Jaeger on localhost:4317 or localhost:4318
  • open Jaeger on http://localhost:16686
  • filter for tool_call spans with ERROR
  • also filter for spans longer than 30s

One Reddit user in r/openclaw said those two filters caught about 80% of their weird-agent failures.

That matches my experience.

The problem with asking the agent

The agent is usually telling you a plausible story based on whatever is still in context.

It is not replaying a ground-truth execution log unless you explicitly captured one and fed it back.

That matters because agent runs get distorted by all the usual stuff:

  • retries
  • summarization
  • context compression
  • tool wrappers
  • partial failures
  • timeouts
  • memory truncation

So when GPT-5, Claude Opus 4.6, Grok 4.20, or any other model says:

"The browser step probably failed because authentication expired"

maybe that's useful as a guess.

But it's still a guess.

If you're running agents at any real volume, especially with long-lived automations, you need something outside the model's own memory of the run.

That's what traces are for.

Why OpenTelemetry is a better debugger

OpenTelemetry gives you a timeline of what actually happened.

A trace is made of spans. Each span has timing, status, metadata, and parent/child relationships.

That means if OpenClaw:

  • plans a task
  • calls a search tool
  • retries an HTTP request
  • opens a Playwright browser step
  • hangs for 42 seconds
  • then fails downstream

...you can see that sequence directly.

Not a summary. Not a story. The sequence.

The difference is huge.

Agent explanation:

"I encountered an issue during browsing."

Trace evidence:

tool_call.playwright started at 14:03:11, retried twice, returned ERROR, and blocked downstream spans for 38.7s.

That's real debugging.

Jaeger is the fastest way to start

Jaeger is great because it's simple and local.

Run one container:

docker run --rm --name jaeger \
  -p 16686:16686 \
  -p 4317:4317 \
  -p 4318:4318 \
  -p 5778:5778 \
  -p 9411:9411 \
  cr.jaegertracing.io/jaegertracing/jaeger:2.19.0
Enter fullscreen mode Exit fullscreen mode

Then open:

http://localhost:16686
Enter fullscreen mode Exit fullscreen mode

Jaeger accepts OTLP on:

  • 4317 for gRPC
  • 4318 for HTTP

So if your OpenClaw service is already instrumented with OpenTelemetry, point it there.

Common env vars:

export OTEL_EXPORTER_OTLP_ENDPOINT=http://localhost:4317
export OTEL_EXPORTER_OTLP_TRACES_ENDPOINT=http://localhost:4318/v1/traces
Enter fullscreen mode Exit fullscreen mode

Which one you use depends on your exporter and protocol.

The 2 filters that catch most failures

The most useful thing I found in that Reddit thread wasn't a big architecture diagram. It was a dead-simple heuristic:

  • filter tool_call spans with ERROR
  • filter spans with duration > 30s

That's it.

1) tool_call spans with ERROR

This catches the obvious failures that agents love to narrate around:

  • bad API keys
  • malformed payloads
  • browser crashes
  • rate limits
  • file permission issues
  • webhook 500s
  • upstream timeouts

The model may smooth over those details.

The span won't.

2) spans longer than 30 seconds

This one catches the sneaky failures.

A run can look "confused" when it's actually just blocked.

Typical examples:

  • Playwright hangs on a page load
  • a retrieval call retries three times
  • a wrapper waits on upstream I/O
  • a queue-backed tool stalls before returning

Long spans are often the first sign that a run is going bad before you even get a hard error.

The other 20%: context overflow

A lot of weird behavior isn't a crash.

It's context rot.

Nothing failed at the tool layer, but the agent accumulated too much history across turns and started operating on a compressed version of reality.

That's when self-explanations get even less reliable.

One practical signal: watch input token counts climb across turns right before behavior gets sloppy.

This is also why pricing model changes behavior.

When you're not obsessing over per-token billing, you naturally let agents:

  • run longer
  • call more tools
  • branch more often
  • retry more aggressively

That's good for throughput.

But it also creates longer traces and weirder failure modes.

If you're building on a flat-rate API like Standard Compute, this matters even more. Unlimited compute is great for agents and automations, but once agents can run freely, you need observability that doesn't depend on the model remembering what it did.

Ask the agent, read logs, or trace everything?

Here's the practical version.

Approach What you actually get
Ask the agent what happened Near-zero setup. Useful for hypotheses. Not trustworthy for root-cause analysis.
Structured logs only Better than nothing. Fine for simple services. Gets painful once runs branch or retry.
OpenTelemetry + Jaeger Moderate setup. Best option for multi-step agent debugging with real timelines and error visibility.

I'm not anti-logs.

For a tiny local project, terminal logs are fine.

But once your OpenClaw runs involve multiple tools, retries, browser steps, or long sessions, logs become archaeology.

Traces give you the map.

A sane setup for OpenClaw

Option 1: local debugging

This is the easiest place to start:

  1. Run Jaeger locally
  2. Enable OpenTelemetry in your OpenClaw service
  3. Export OTLP to localhost:4317 or localhost:4318
  4. Open Jaeger on 16686
  5. Filter for tool_call + ERROR
  6. Then check spans over 30s

That alone answers most "why did it do that?" questions.

Option 2: put an OpenTelemetry Collector in the middle

If you're moving beyond local debugging, add an OpenTelemetry Collector.

That's the cleaner architecture.

Your agents emit OTLP once. The Collector forwards traces wherever you want later.

Minimal example:

receivers:
  otlp:
    protocols:
      grpc:
        endpoint: 0.0.0.0:4317
      http:
        endpoint: 0.0.0.0:4318

exporters:
  debug:
    verbosity: detailed

service:
  pipelines:
    traces:
      receivers: [otlp]
      exporters: [debug]
    metrics:
      receivers: [otlp]
      exporters: [debug]
    logs:
      receivers: [otlp]
      exporters: [debug]
Enter fullscreen mode Exit fullscreen mode

That setup is a lot easier to live with when your agents are spread across:

  • OpenClaw
  • n8n
  • Make
  • Zapier
  • Node workers
  • Python workers
  • queue consumers
  • custom internal tools

A quick Node example

If you're wiring up an OpenAI-compatible service or agent backend in Node, the shape usually looks something like this:

npm install @opentelemetry/sdk-node @opentelemetry/exporter-trace-otlp-grpc
Enter fullscreen mode Exit fullscreen mode
const { NodeSDK } = require('@opentelemetry/sdk-node');
const { OTLPTraceExporter } = require('@opentelemetry/exporter-trace-otlp-grpc');

const sdk = new NodeSDK({
  traceExporter: new OTLPTraceExporter({
    url: 'http://localhost:4317'
  })
});

sdk.start();
Enter fullscreen mode Exit fullscreen mode

Then instrument your tool calls so spans are named clearly:

  • planner.step
  • tool_call.search
  • tool_call.playwright
  • tool_call.http
  • memory.retrieve

Good span names make Jaeger way more useful.

What I actually do now

I still ask the agent what happened sometimes.

But I treat the answer like a tired teammate explaining an incident from memory.

Potentially useful.

Definitely not authoritative.

My actual workflow is:

1. Find the trace
2. Look for ERROR spans
3. Look for long spans
4. Check retries and parent/child relationships
5. Only then read the agent's explanation
Enter fullscreen mode Exit fullscreen mode

That order matters.

Because once you've seen a bad OpenClaw run laid out in Jaeger, it's hard to go back.

You see the broken tool_call, the retry loop, the 41-second stall, and the downstream cascade. At that point, asking the agent to explain itself feels like asking the suspect to write the incident report.

Practical takeaway

If you're debugging OpenClaw, stop treating the model's explanation as the source of truth.

Use it for hypotheses if you want.

Do root-cause analysis with traces.

Start local:

Jaeger UI: http://localhost:16686
OTLP gRPC: localhost:4317
OTLP HTTP: localhost:4318
Enter fullscreen mode Exit fullscreen mode

Then start with two filters:

  • tool_call spans with ERROR
  • spans longer than 30s

That simple setup will save you a lot of wasted time.

And if you're running agents at scale, especially on flat-rate infrastructure where you actually want them to run freely, this stuff stops being optional. Predictable compute is great. Predictable debugging matters too.

That's the real upgrade: less interrogating the model, more inspecting what actually happened.

If you're building high-volume agents and automations and you're tired of per-token pricing changing how aggressively you let them run, Standard Compute is worth a look. It's an OpenAI-compatible API with flat monthly pricing, which makes a lot more sense for always-on agent workflows than babysitting token spend all day.

Top comments (0)