Debug IDs
A debug ID ties a deployed bundle to the exact source map produced by the same build. The
build embeds a 128-bit ID (in UUID format) into both the bundle and its .map file; the
client reports the IDs of the bundles involved in an exception; and the backend picks the map
uploaded with that exact ID. Matching is content-addressed, with no dependence on filenames,
URLs, versions or deploy timing.
Not usable end-to-end yet. The backend already accepts debug IDs: an uploaded map or
bundle carrying one is stored under a by-debug-id/ key alongside its filename, and frames
that arrive with a debug ID resolve against it. What is missing is the client end — the
TracePath browser SDK, which is what reports a frame's debug ID at capture time, is
not published yet. Until it ships, maps resolve
by filename, which is reliable as
long as your bundle names are content-hashed.
The format
TracePath uses the format standardized by the
ECMA-426 source map specification's Debug ID proposal (opens in a new tab):
a debugId field in the source map JSON and a //# debugId=<uuid> comment in the generated
file. Nothing about it is TracePath-specific, so artifacts stay interoperable with other
ECMA-426 tooling, and TracePath accepts artifacts produced by other debug-ID-aware tools.
On upload, the backend scans each file for both: the debugId field in a .map, and the
//# debugId= or //@ debugId= comment in a bundle. The legacy debug_id field written by
Sentry-era tooling is accepted too. An ID has to be a well-formed lowercase UUID to be
indexed.
Why filenames are not enough
Without debug IDs, a map is matched to a frame by filename. That works well with
content-hashed bundle names — index-DZ8aDdLF.js — because every build produces a distinct
name and the maps never collide. It breaks down with stable names like app.js served from
two concurrent deploys: the most recent upload wins, and half your traces resolve against the
wrong build. Debug IDs remove the ambiguity entirely.
How the runtime half will work
When the browser SDK ships, the build step will, for each emitted JS chunk:
- Derive a deterministic debug ID from the chunk's content, so rebuilding identical input yields an identical ID.
- Inject a small self-executing snippet at the top of the chunk that registers the ID in a global registry, keyed by the stack of a synthetic error — which is how the SDK later discovers the runtime URL each bundle was loaded from. Source maps are adjusted for the insertion, so resolved positions are unaffected.
- Append the
//# debugId=<uuid>comment above the//# sourceMappingURL=comment. - Write the ID into the source map JSON.
At capture time the SDK reads that registry, maps each bundle filename in the stack trace to its debug ID, and sends the mapping with the exception. At ingest, the backend resolves each frame against the map uploaded with that debug ID, falling back to filename matching when no debug-ID artifact exists. Because artifacts are stored under both keys, frames without IDs keep resolving from the same upload.
Uploading
Nothing changes about the upload itself: debug IDs are detected server-side in the file content, with no extra fields or flags. See Source Maps for the endpoint and its limits.
OpenTelemetry
OpenTelemetry has no debug ID convention: there is no semantic-conventions attribute for source map linkage, and the OTel JS SDKs collect nothing like it. TracePath therefore carries debug IDs in its own report payload rather than in OTLP. Because the build artifacts follow the ECMA-426 format rather than anything TracePath-specific, the same uploads will work unchanged if an OTel convention appears later.
Next steps
- Source Maps: the upload endpoint, live today
- Symbolication: the full resolution pipeline