Skip to content

Add trace ID extraction and appending to error messages - #1664

Open
djeebus wants to merge 3 commits into
mainfrom
claude/trace-id-error-messages-cbzqwl
Open

Add trace ID extraction and appending to error messages#1664
djeebus wants to merge 3 commits into
mainfrom
claude/trace-id-error-messages-cbzqwl

Conversation

@djeebus

@djeebus djeebus commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Summary

This change adds trace ID extraction from HTTP response headers and appends them to error messages across the SDK and CLI. When API or envd requests fail, the error message now includes the trace ID (e.g., (trace ID: abc123)) so users can report it to E2B support for correlation with server-side traces.

Usage examples

No API changes — the trace ID shows up in existing error messages when the failed response carries a trace header.

JS SDK

import { Sandbox } from 'e2b'

try {
  await Sandbox.connect('already-dead-sandbox-id')
} catch (err) {
  console.error(err.message)
  // 404: sandbox not found (trace ID: 105445aa7843bc8bf206b12000100000)
}

Python SDK (sync and async)

from e2b import Sandbox

try:
    Sandbox.connect("already-dead-sandbox-id")
except Exception as e:
    print(e)
    # 404: sandbox not found (trace ID: 105445aa7843bc8bf206b12000100000)

CLI

$ e2b sandbox logs already-dead-sandbox-id
Error while getting sandbox logs: [404] not found: sandbox not found (trace ID: 105445aa7843bc8bf206b12000100000)

Key Changes

  • New trace ID utilities: Added e2b/trace_id.py (Python) and src/traceId.ts (JavaScript) modules that:

    • Extract trace IDs from response headers in priority order: X-Trace-ID (direct), X-Cloud-Trace-Context (GCP edge), X-Amzn-Trace-Id (AWS edge)
    • Normalize AWS trace IDs from the format Root=1-<8 hex>-<24 hex> to the 32-hex form the server logs as edge_trace_id, so the reported ID joins directly against traces/logs in Grafana
    • Append trace IDs to exception/error messages
  • SDK integration:

    • Python SDK: Updated e2b/api/__init__.py (handle_api_exception) and e2b/envd/api.py (handle_envd_api_exception / ahandle_envd_api_exception) to extract and append trace IDs to exceptions
    • JavaScript SDK: Updated src/api/index.ts (handleApiError) and src/envd/api.ts (handleEnvdApiError) to extract and append trace IDs to errors
    • CLI: Updated packages/cli/src/utils/errors.ts with trace ID extraction and appending logic
    • The envd RPC (connect) error paths are intentionally not covered: Python's connectrpc.ConnectError exposes no response metadata, and the JS side is kept symmetric
  • Comprehensive test coverage: Added test suites for both Python and JavaScript implementations covering:

    • Header extraction with various formats and edge cases
    • Case-insensitive header matching
    • Fallback behavior for missing headers
    • Priority ordering of trace ID sources
    • Integration with existing error handling

Implementation Details

  • Trace ID extraction is case-insensitive for header names
  • Empty or whitespace-only trace IDs are ignored
  • The implementation gracefully handles missing headers and responses without header support
  • Error messages are appended with (trace ID: ...) format for consistency across all SDKs
  • All three trace ID header formats (direct, GCP, AWS) are supported with proper normalization

Note on the server side

No code in e2b-dev/infra or e2b-dev/belt currently sets a trace-ID response header (the API handlers stash traceID in the gin context but never write it to the response; the edge only parses the GCP/AWS request headers into edge_trace_id logs). If production responses don't carry one of the three headers above, error messages stay unchanged until the API emits one — e.g. a one-line c.Header("X-Trace-ID", traceID) in infra where c.Set("traceID", ...) already happens. Worth verifying which header a real failed api.e2b.dev response returns.

https://claude.ai/code/session_0147b6bTi2gq4Yvm7VrLrxLD

When a failed API or envd response carries a trace header (X-Trace-ID,
or the GCP X-Cloud-Trace-Context / AWS X-Amzn-Trace-Id edge headers),
the JS SDK, Python SDK (sync and async), and CLI now append
'(trace ID: ...)' to the error message so users can report the ID and
it can be correlated with server-side traces.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0147b6bTi2gq4Yvm7VrLrxLD
@djeebus
djeebus requested a review from mishushakov as a code owner August 12, 2026 00:38
@cla-bot cla-bot Bot added the cla-signed label Aug 12, 2026
@changeset-bot

changeset-bot Bot commented Aug 12, 2026

Copy link
Copy Markdown

🦋 Changeset detected

Latest commit: 0adc234

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 3 packages
Name Type
e2b Patch
@e2b/python-sdk Patch
@e2b/cli Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

@cursor

cursor Bot commented Aug 12, 2026

Copy link
Copy Markdown

PR Summary

Low Risk
Backward-compatible error-message enrichment on existing HTTP failure paths; no auth or data-handling logic changes.

Overview
Failed API and envd HTTP responses can now surface a trace ID in SDK and CLI error text as (trace ID: …), so support can tie user reports to server-side traces. No new public APIs beyond exporting extractTraceId from the JS package.

Header parsing is centralized in traceId.ts / trace_id.py: prefer X-Trace-ID, then GCP X-Cloud-Trace-Context (segment before /), then AWS X-Amzn-Trace-Id (normalize Root=1-… to 32-hex when possible).

Wiring: handleApiError / handle_api_exception, envd HTTP error handlers, and CLI handleE2BRequestError read response headers and pass the ID into exception constructors. Sandbox-related error classes accept an optional traceId and append it via shared formatting; messages stay unchanged when no header is present. Connect/RPC error paths are unchanged.

Reviewed by Cursor Bugbot for commit 0adc234. Bugbot is set up for automated code reviews on this repo. Configure here.

@github-actions

github-actions Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Package Artifacts

Built from d76c350. Download artifacts from this workflow run.

JS SDK (e2b@2.38.4-claude-trace-id-error-messages-cbzqwl.0):

npm install ./e2b-2.38.4-claude-trace-id-error-messages-cbzqwl.0.tgz

CLI (@e2b/cli@2.16.2-claude-trace-id-error-messages-cbzqwl.0):

npm install ./e2b-cli-2.16.2-claude-trace-id-error-messages-cbzqwl.0.tgz

Python SDK (e2b==2.38.0+claude.trace.id.error.messages.cbzqwl):

pip install ./e2b-2.38.0+claude.trace.id.error.messages.cbzqwl-py3-none-any.whl

@djeebus
djeebus enabled auto-merge (squash) August 12, 2026 00:39
Comment thread packages/cli/src/utils/errors.ts Outdated
Comment on lines 60 to 79
if (key?.toLowerCase() !== 'root' || !value) {
continue
}

const match = value.match(/^1-([0-9a-f]{8})-([0-9a-f]{24})$/i)
return match ? match[1] + match[2] : value
}
}

return undefined
}

function throwE2BRequestError(
error: E2BResponseError,
errMsg?: string,
traceId?: string
): never {
let message: string
const code = error.code ?? 0
switch (code) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 CLAUDE.md requires 'Create or update tests covering affected codepaths,' but this PR leaves packages/cli/tests/utils/errors.test.ts untouched even though errors.ts gains a new extractTraceId() function and trace-ID appending in throwE2BRequestError/handleE2BRequestError. No case exercises X-Trace-ID, GCP X-Cloud-Trace-Context, or AWS X-Amzn-Trace-Id extraction/normalization for the CLI, unlike the JS SDK (new traceId.test.ts + updated handleApiError.test.ts/handleEnvdApiError.test.ts) and Python SDK (test_trace_id.py), which both got full coverage for the identical logic.

Extended reasoning...

This PR introduces a new, non-trivial codepath in packages/cli/src/utils/errors.ts: extractTraceId() parses three different header formats (a direct X-Trace-ID, the GCP X-Cloud-Trace-Context edge header which requires splitting on /, and the AWS X-Amzn-Trace-Id edge header which requires parsing a Root=1-<8 hex>-<24 hex> field and normalizing it into a 32-hex trace ID), and it wires the extracted value into throwE2BRequestError/handleE2BRequestError so it gets appended to the thrown error message as (trace ID: ...).

None of this is covered by tests in the CLI package. packages/cli/tests/utils/errors.test.ts already exists and already tests handleE2BRequestError for various status codes, but the PR leaves it completely untouched — a grep for trace/Trace across packages/cli/tests returns nothing. There is no test that passes a response/headers-bearing object into handleE2BRequestError, so none of the three extraction branches, the AWS regex normalization, the priority ordering (direct > GCP > AWS), or the final message-appending behavior is exercised at all for the CLI.

This is a direct violation of the repository's own CLAUDE.md instructions, which explicitly state: 'Create or update tests covering affected codepaths and run them using pnpm run test.' The PR's own diff demonstrates the intended standard: the JS SDK got a full new traceId.test.ts (extraction, normalization, priority ordering, edge cases) plus updated handleApiError.test.ts and handleEnvdApiError.test.ts with trace-ID-specific cases, and the Python SDK got a full new test_trace_id.py covering the same logic end to end. The CLI implementation — which, per the related duplication finding, re-implements this parsing logic independently rather than sharing it with the SDK's traceId.ts — is the only one of the three places this feature landed that received zero test coverage.

Concrete proof of the gap: take the AWS-header path, since it's the most failure-prone (regex parsing + normalization). In extractTraceId, if X-Amzn-Trace-Id is Root=1-5759e988-bd862e3fe1be46a994272793;Sampled=1, the code should return 5759e988bd862e3fe1be46a994272793 (the two hex groups joined), and this exact case is verified in both packages/js-sdk/tests/traceId.test.ts and packages/python-sdk/tests/test_trace_id.py. But if the CLI's regex or splitting logic were subtly wrong — e.g. an off-by-one in the hex-group lengths, or the ; split failing to find Root= first — nothing in pnpm run test for the CLI package would catch it, since handleE2BRequestError is only ever invoked in the test suite without a response argument. The same blind spot applies to the GCP-header branch and to the case-insensitivity of headers.get('x-trace-id').

Fix: add test cases to packages/cli/tests/utils/errors.test.ts mirroring the ones already written for the JS SDK — pass a response: { headers: new Headers({...}) } into handleE2BRequestError and assert the thrown E2BRequestError's message ends with (trace ID: ...) for each of: direct X-Trace-ID, GCP X-Cloud-Trace-Context, AWS X-Amzn-Trace-Id (including the raw-fallback case for a malformed Root= value), priority ordering when multiple headers are present, and the no-headers case where the message is left unchanged.

This does not block merging on its own — missing tests don't cause a runtime failure, and the trace-ID feature works correctly as shipped. But it's an explicit, unambiguous violation of a stated project convention, and the parity gap with the JS/Python SDKs (both of which got thorough coverage for the identical logic) makes it worth flagging as a nit for the author to close before merge.

Comment thread packages/cli/src/utils/errors.ts Outdated
Comment on lines +29 to +58
}

/**
* Extract a trace ID from HTTP response headers of a failed request, so the
* error message can carry an ID that E2B support can correlate with
* server-side traces. Checks `X-Trace-ID`, then the GCP
* (`X-Cloud-Trace-Context`) and AWS (`X-Amzn-Trace-Id`) edge trace headers.
*/
function extractTraceId(response?: ResponseHeadersLike): string | undefined {
const headers = response?.headers
if (!headers || typeof headers.get !== 'function') {
return undefined
}

const direct = headers.get('x-trace-id')?.trim()
if (direct) {
return direct
}

// GCP edge: "TRACE_ID/SPAN_ID;o=OPTIONS"
const gcp = headers.get('x-cloud-trace-context')?.split('/')[0]?.trim()
if (gcp) {
return gcp
}

// AWS edge: "Root=1-<8 hex>-<24 hex>;..." — the two hex parts joined are
// the 32-hex trace ID the server logs
const aws = headers.get('x-amzn-trace-id')
if (aws) {
for (const field of aws.split(';')) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 packages/cli/src/utils/errors.ts:37-70 duplicates the exact trace-ID header parsing logic (X-Trace-ID / X-Cloud-Trace-Context / X-Amzn-Trace-Id, including the AWS regex and GCP split) already implemented in packages/js-sdk/src/traceId.ts's extractTraceId. Since the CLI already depends on and imports from 'e2b', consider exporting extractTraceId from js-sdk's public index and importing it here instead of copy-pasting, to avoid the two copies silently drifting on future changes.

Extended reasoning...

What the bug is: packages/cli/src/utils/errors.ts (lines 29-58/37-70) reimplements a byte-for-byte copy of the trace-ID extraction logic that this same PR adds to packages/js-sdk/src/traceId.ts. Both versions check headers in the same priority order — X-Trace-ID (direct, trimmed), X-Cloud-Trace-Context (GCP edge, split('/')[0].trim()), and X-Amzn-Trace-Id (AWS edge, splitting on ;, matching Root=1-<8 hex>-<24 hex> with the identical regex /^1-([0-9a-f]{8})-([0-9a-f]{24})$/i, and joining the two hex groups) — with only the wrapper signature differing (CLI takes a {headers}-shaped object, js-sdk's version takes the Headers object directly).\n\nWhy it happens / code path: This PR introduces extractTraceId/appendTraceId in packages/js-sdk/src/traceId.ts and wires them into packages/js-sdk/src/api/index.ts and packages/js-sdk/src/envd/api.ts. Rather than reusing that helper for the CLI's error handling in packages/cli/src/utils/errors.ts, the PR hand-writes an equivalent extractTraceId function locally. The CLI package already depends on and imports from e2b (see packages/cli/src/api.ts: import * as e2b from 'e2b', and package.json lists e2b: workspace:^), so nothing structurally prevents sharing the implementation — it's purely that extractTraceId is not currently re-exported from js-sdk's public index.ts, which would need to be added for the CLI to import it.\n\nWhy existing code doesn't prevent it: There's no lint rule or test that would catch two independently-written implementations of the same parsing logic living in separate packages; both pass their own test suites (traceId.test.ts for js-sdk, presumably covered indirectly for the CLI) without any cross-check that they stay in sync.\n\nImpact: This doesn't cause incorrect behavior today — both copies are currently identical and produce the same trace IDs. The risk is purely maintenance: any future change to trace-ID parsing (e.g. supporting a new header format, fixing an edge case in the AWS regex, adjusting GCP parsing) has to be applied in two places, and it would be easy to update one copy and forget the other, causing the CLI and SDK to silently disagree on trace IDs for the same failure.\n\nProof (concrete example): Suppose in a follow-up PR someone fixes a bug in the AWS regex — say widening it to accept a 16-hex span ID variant, changing the pattern in packages/js-sdk/src/traceId.ts only (since that's the 'canonical' looking implementation reused by the JS SDK's API/envd handlers). packages/cli/src/utils/errors.ts's local copy still has the old regex. Now: a request fails with an AWS trace header in the new format; the JS SDK correctly extracts and reports the trace ID; the CLI (which wraps the same underlying e2b client and hits the same API) falls through the regex match and falls back to the raw Root=... string (or drops the ID depending on which branch), producing an inconsistent/wrong trace ID for the exact same server-side failure. A user following CLI-reported error output to file a support ticket would give E2B support a malformed or missing trace ID, right where this feature is supposed to help.\n\nHow to fix: Export extractTraceId (and optionally appendTraceId) from js-sdk's public src/index.ts, then in packages/cli/src/utils/errors.ts import it and adapt the call site to pass res.response?.headers directly (a trivial signature bridge, since the CLI's ResponseHeadersLike.headers is already the same {get(name): string|null} shape as js-sdk's HeadersLike), deleting the ~45-line duplicated block entirely.

@mishushakov mishushakov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

reviewed, please remove duplicate method in JS SDK and you can modify the default Error classes to accept a trace field in the constructor that will append it to the message, which is cleaner than relying on helpers side-effects

Comment thread packages/cli/src/utils/errors.ts Outdated

function throwE2BRequestError(error: E2BResponseError, errMsg?: string): never {
type ResponseHeadersLike = {
headers?: { get(name: string): string | null }

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you can use built-in Headers type from undici?

Comment thread packages/js-sdk/src/api/index.ts Outdated
errorClass,
stackTrace
return appendTraceId(
apiErrorFromCode(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

maybe just change apiErrorFromCode instead

Comment thread packages/js-sdk/src/traceId.ts Outdated
* 3. `X-Amzn-Trace-Id` (AWS edge) — `Root=1-<8 hex>-<24 hex>;...`, the two
* hex parts joined are the 32-hex trace ID the server logs.
*/
export function extractTraceId(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is same code as in packages/cli/src/utils/errors.ts

Comment thread packages/js-sdk/src/traceId.ts Outdated
* Append the trace ID of the failed request to the error message, so users
* can include it when reporting the failure to E2B.
*/
export function appendTraceId<T extends Error | undefined>(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you could also modify E2B error classes to have trace id in message instead of this

Comment thread packages/python-sdk/e2b/api/__init__.py Outdated
if message is None and e.status_code not in (401, 429):
return default_exception_class(f"{e.status_code}: {e.content}").with_traceback(
stack_trace
return append_trace_id(

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

feel free to update default_exception_class instead

Address review feedback:
- Error/exception classes now take an optional trace ID in the
  constructor and append it to the message, instead of a post-hoc
  append helper; apiErrorFromCode / api_exception_from_code and the
  envd error maps thread it through.
- The CLI reuses extractTraceId exported from the JS SDK instead of
  keeping its own copy, and types headers with the built-in Headers.
- Cover AWS raw-value fallback and header priority in the CLI tests.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0147b6bTi2gq4Yvm7VrLrxLD

djeebus commented Aug 12, 2026

Copy link
Copy Markdown
Contributor Author

Addressed the review in 0adc234:

  • Duplicate method removedextractTraceId now lives only in the JS SDK (src/traceId.ts), is exported from the package index, and the CLI imports it from e2b.
  • Trace ID via constructors — the error/exception classes in both SDKs now take an optional trace ID in the constructor and append it to the message (SandboxError(message, stackTrace, traceId) / SandboxException(message, trace_id)); the appendTraceId/append_trace_id side-effect helpers are gone. apiErrorFromCode / api_exception_from_code and the envd error maps thread the trace ID through, per the inline suggestions.
  • Built-in Headers type — the CLI (and the SDK helper) now type headers with the standard Headers instead of a custom shape.

Also added CLI test cases for the AWS raw-value fallback and header priority.


Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants