In my demo run, the agent’s withdraw_from_vault tool span has no error. The transaction it sent reverted. Both spans are right, because they describe different operations.
What are the three status codes?
| Code | Means | Who sets it |
|---|---|---|
Unset |
Finished without an error. The default. | Nobody, it’s what you get |
Error |
The operation failed | Instrumentation, when it fails |
Ok |
Explicitly marked successful | An application developer or operator |
The semantic conventions say instrumentation must leave status Unset when an operation ends without errors (recording errors). So most healthy spans are Unset, not Ok. That surprises people who look for a green “Ok” in their backend.
Error status comes with error.type
When an operation fails, instrumentation sets status Error and the error.type attribute. error.type is stable and names the class of error, such as timeout. It should have low cardinality, so put the details somewhere else.
import { SpanStatusCode, type Span } from '@opentelemetry/api';
function endConfirm(span: Span, receipt: { status: 'success' | 'reverted' }) { span.setAttribute('blockchain.tx.status', receipt.status); if (receipt.status === 'reverted') { span.setAttribute('error.type', 'reverted'); span.setStatus({ code: SpanStatusCode.ERROR }); } span.end();}confirm 8453 UNSET { 'blockchain.tx.status': 'success' }confirm 8453 ERROR { 'blockchain.tx.status': 'reverted', 'error.type': 'reverted' }Whose error is it?
Status belongs to the operation the span describes, not to the whole trace. Here’s the run:
sendis Unset. Sending worked: the node accepted the transaction and returned its hash.confirmis Error. The receipt says the transaction reverted.execute_toolis Unset. viem returned the receipt without throwing, and the tool passed{ status: "reverted" }back to the model. Nothing failed inside the tool.
You could argue the tool span should be Error too. I don’t think so: the tool did its job and told the agent the truth. The failure is real, and it’s on the span that describes it. Filter on error.type in your backend and you find it. This post shows what you’d see without that span.