Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Mistakes

Five things that go wrong, roughly in order of how much damage they do.

Over-declaring

observes_crossings: "all" means nothing can answer the program before your wrapper. If your sandbox refuses calls over a cap, or a deadline guard answers early, or a cache returns without dispatching, those calls make no span and "all" is a lie.

It’s the worst one because everything else rests on it, and you can’t spot it afterwards from the data. Four calls, two spans, and a declaration saying two was all of them looks completely normal.

Fix: write a program that hits every refusal path you have, and count spans against calls. If they don’t match, you’re "some". Declaring.

No provider

The OpenTelemetry API does nothing when no provider is registered. No spans, no error, no warning, exit code zero. Registering one in a test or a demo script doesn’t count.

Fix: grep your own src/ for NodeSDK or TracerProvider and make sure you find something outside a test. Or use your logger, which needs no provider at all.

Silent failures

If callTool returns { ok: false } instead of throwing, the default reads that as success. Every failed call gets recorded as working, and the trace looks healthy while your users don’t.

Fix: the end option on instrument. See Wrappers.

Late start

Runs you refuse for a bad key, a failed lint or being at capacity never produce a span at all. The failure modes you most want to see are the ones that vanish, and they vanish in a way that looks like nobody called you.

Fix: start the run span first, then execution.fail(cause, { errorType: "validation" }).

Leaked internals

The default treats every argument after the first as the program’s input. A bridge shaped callTool(name, params, { signal, deadline }) therefore records your abort signal and deadline as things the program passed, and attesting crossing.input publishes that as fact.

Fix: input: (_name, params) => params.

Smaller

Leaving kind unset is a choice. It defaults to client, which says you forwarded the call somewhere remote. Pass kind: "local" for tools your own process serves.

No context manager means neighbouring instrumentation floats. mocon puts the run span in the active context so other instrumentation nests under it, but that only works if your app registered a context manager. NodeSDK does. A hand-assembled provider doesn’t. mocon’s own spans are fine either way, which is exactly why it’s easy to miss: your trace looks perfect and everything else drifts off.