Documentation

AI-augmented production triage

Reference guides for release workflows, command-line usage, cross-file protections, and the desktop app.

Inside the Docs

Practical guides for real release work.

How-to guides Start with release sequencing and command-line usage, then move into feature-specific references.
Advanced protection Browse cross-file controls like Replace Globals and Protect Members when a build spans multiple scripts.

AI-augmented production triage

  • jso-symbolicate · /v1/ai/explain-error
  • Sentry, Bugsnag, Rollbar, Datadog, Honeybadger, Raygun, Airbrake, AppSignal
  • jso-symbolicate ships today · explain-error supports preview and BYO-key live AI

Production crashes in protected JavaScript travel through three stages before someone can fix them: (1) the runtime catches the error and the reporter SDK ships it to Sentry / Bugsnag / Rollbar / etc, (2) the function names in the stack are mangled and need to be demangled, (3) the demangled stack still doesn't tell you which JSO transform caused the issue. jso-symbolicate and /v1/ai/explain-error cover stages 2 and 3. This page shows the full composition.

The end-to-end pipeline

  1. Crash happens in production. The protected bundle throws. The error reporter SDK (Sentry / Bugsnag / etc) catches it and ships the stack trace to the customer’s aggregator.
  2. jso-symbolicate demangles the stack. Either inline via the reporter’s beforeSend hook (one of eight first-class integrations) or offline via the CLI when triaging from a saved report. The mangled function names become the original names from the source.
  3. /v1/ai/explain-error diagnoses the JSO transform. The demangled error string goes to the explain-error endpoint, which returns the most likely JSO transform that caused the issue, a concrete fix, and a docs link.
  4. You apply the fix and ship. Add the function to VariableExclusion, toggle FlatTransform: false on the affected file, or whatever the diagnosis recommends.

Inline composition: reporter beforeSend → AI diagnosis

The cleanest path is to have the reporter’s beforeSend hook both demangle the stack and annotate the event with the AI’s diagnosis. When the event lands in Sentry / Bugsnag / Datadog, the triage engineer reads not just “calculateLicenseHash is not a function” but also “Name Mangling renamed this; add calculateLicenseHash to VariableExclusion.”

Sketch (Sentry browser SDK):

import * as Sentry from "@sentry/browser";
import { buildLookup } from "jso-symbolicate";
import { createSentryEventProcessor } from "jso-symbolicate/sentry";

const lookup = buildLookup(
    window.__JSO_REPORT__.Report.GlobalIdentifierMap,
    window.__JSO_REPORT__.Report.MemberIdentifierMap
);

const demangle = createSentryEventProcessor(lookup);

Sentry.init({
    dsn: "...",
    release: window.__JSO_BUILD_ID__,
    beforeSend: async (event) => {
        // 1. Demangle the stack in-place.
        const demangled = demangle(event);

        // 2. Ask /v1/ai/explain-error what JSO transform likely caused this.
        //    Server-side proxy recommended in production — don't expose your
        //    JSO API key in the browser.
        try {
            const resp = await fetch("/api/jso/explain-error", {
                method: "POST",
                headers: { "Content-Type": "application/json" },
                body: JSON.stringify({
                    error: demangled.exception.values[0].value
                            + " " + (demangled.exception.values[0].type || "")
                }),
            });
            const json = await resp.json();
            if (json.ok && json.explanation) {
                demangled.tags = demangled.tags || {};
                demangled.tags.jso_diagnosis_transform = json.explanation.transform;
                demangled.tags.jso_diagnosis_confidence = json.explanation.confidence;
                demangled.extra = demangled.extra || {};
                demangled.extra.jso_diagnosis = json.explanation;
            }
        } catch (_) { /* swallow — never block crash reporting */ }

        return demangled;
    },
});

The Sentry event that lands in the UI now carries two extra pieces of information:

  • The jso_diagnosis_transform tag — filterable / searchable. Find “all Name Mangling-caused crashes this week.”
  • The full jso_diagnosis extra — cause, transform, confidence, explanation, fix, docs URL. The triage engineer sees a recommended fix without leaving Sentry.

Offline composition: pull a saved report, run both CLIs

When the crash is in a saved log file or a Sentry export rather than a live event stream:

# 1. Demangle.
npx jso-symbolicate --map dist-protected/jso-report.json --stack crash.txt > crash-demangled.txt

# 2. Diagnose. Pull the top error line, send to the explain-error endpoint.
ERROR=$(head -1 crash-demangled.txt)
curl -sS -X POST https://javascriptobfuscator.com/v1/ai/explain-error.ashx \
    -H "Content-Type: application/json" \
    -d "{
      \"APIKey\":   \"$JSO_API_KEY\",
      \"APIPwd\":   \"$JSO_API_PASSWORD\",
      \"error\":    $(jq -Rs . <<< \"$ERROR\")
    }" | jq '.explanation'

This is the path support uses when a customer pastes a stack trace in a ticket. Two commands, no service to keep running.

Why the AI endpoint, not just static rules in the client

Two reasons the JSO-side endpoint earns the network round-trip:

  1. The rule table stays in sync. The patterns the explain-error endpoint matches are JSO-engineering-team-curated and get updated when transforms change. A client-side static table written today goes stale the first time JSO ships a new transform.
  2. One endpoint, two modes. The same endpoint returns deterministic rule-based guidance when no provider key is configured and provider-backed reasoning when the account has a BYO OpenAI or Claude key, or a site-managed provider. Your reporter integration stays the same while the account chooses the AI setup that fits its data and billing policy.

If your environment cannot make outbound HTTP from the reporter beforeSend hook, the offline composition path covers the gap — nothing about the architecture requires inline calls.

Quota and cost

Each explain-error call counts 1 AI action against the JSO AI subscription tier (see pricing). When the action quota is hit, the endpoint returns ok: false with error: "quota_exhausted" — your beforeSend hook’s try/catch handles it gracefully and the event still ships with the demangled stack, just without the AI diagnosis tag. The point: the AI augmentation is additive, not gating.

For a heavy-traffic site, route AI diagnosis only to a sampled subset of events (e.g. 10% of unhandled exceptions, 100% of high-severity Sentry events) to keep the AI quota usage proportional to debugging value rather than total crash volume.

The eight reporters with the explain-error pattern wired in

Reporterjso-symbolicate integrationexplain-error hook
Sentry createSentryEventProcessor in beforeSend Inline (above)
Bugsnag createBugsnagOnError in onError Inline in the same callback
Rollbar createRollbarTransform as transform Inline in the same transform
Datadog Browser RUM / LogscreateDatadogBeforeSend in beforeSend Inline in the same callback
Honeybadger createHoneybadgerBeforeNotify Inline in the same callback
Raygun createRaygunBeforeSend via rg4js("onBeforeSend", ...) Inline in the same callback
Airbrake createAirbrakeFilter via addFilter Inline in the same callback
AppSignal createAppSignalDecorator via addDecorator Inline in the same callback

Every reporter exposes a single beforeSend-style hook; the composition pattern is the same in all eight. Pick your reporter, drop in the matching jso-symbolicate sub-export, and add the fetch-to-/v1/ai/explain-error call inside the same callback.

What this gets you in practice

Concrete before/after for the same crash:

Before (reporter raw):

TypeError: _0xa3f.b is not a function
    at _0x1c2 (https://app.example.com/protected.js:1:8742)
    at _0x4f9 (https://app.example.com/protected.js:1:13205)

After (with the composition wired):

TypeError: calculateLicenseHash is not a function
    at boot (https://app.example.com/protected.js:1:8742)
    at runStartup (https://app.example.com/protected.js:1:13205)

jso_diagnosis_transform = "Name Mangling"
jso_diagnosis_confidence = "high"

jso_diagnosis (extra):
  cause:        "name-mangling"
  explanation:  "A renamed identifier was called as a function by its original name.
                 Typically external code (a framework, an HTML inline handler,
                 a window-scoped consumer) references the symbol by its source-level name."
  fix:          "Add the function name to VariableExclusion in your jso.config.
                 Run jso-protector --dry-run --json to confirm the function got included."
  docsUrl:      "/docs/variableexclusionlist.aspx"

That’s the value the composition delivers: from “_0xa3f.b” to “here’s the JSO option to flip” with zero engineer-driven investigation.

Frequently asked questions

Why is a production stack trace from a protected bundle hard to act on?

Because two separate things have been lost, and only one of them is usually addressed. The function names in the trace have been mangled, so the trace does not name anything you recognise. Even after those names are restored, the trace still does not tell you which transform in the protection step caused the behaviour. The tooling described on this page covers both stages: symbolication restores the names, and the explain-error endpoint proposes the transform most likely responsible.

Which error reporters are supported?

Eight are first-class integrations: Sentry, Bugsnag, Rollbar, Datadog, Honeybadger, Raygun, Airbrake and AppSignal. Each can demangle inline through the reporter hook that runs before an event is sent, so events arrive already readable. If you are triaging from a saved report rather than live traffic, the command line tool does the same job offline against an exported trace.

Should the explain-error call be made from the browser?

No. Put a small endpoint on your own server and have the browser call that instead. Calling the diagnosis endpoint directly from page script means your API credential is in the page, which is exactly the disclosure the rest of this documentation warns about. The sketch on this page proxies through a server route for that reason.

What does the diagnosis actually return?

The likely transform responsible, a concrete fix, and a link to the relevant documentation, along with a confidence value. In practice the fix is usually one of a small set: add a name to the variable exclusion list so it survives renaming, turn off a specific transform for the affected file, or adjust an option that interacts badly with a framework expectation. The value is that the triaging engineer reads a diagnosis instead of only reading a symbol name.

Is any of this available without an AI provider key?

Symbolication is, and it ships today with no AI involvement at all: it is a lookup from the identifier maps in your build report back to original names. The explain-error diagnosis is the part that uses a model, and it supports a preview mode as well as live use with your own provider key. So the first and most valuable stage of the pipeline works regardless of whether you enable any AI feature.

What do we need to keep from a build in order to symbolicate later?

The identifier maps from that build's report, tied to the build identifier you ship. Store the report privately with the release, and tag your error reporter events with the same build identifier so an incoming trace can be matched to the map that decodes it. Without that pairing a trace from an old release cannot be resolved, which is the failure people discover during an incident rather than before one.

Try this in the online obfuscator

Paste your own code and see this option applied, or compare plans for larger projects and the desktop app.

Try It Free See Pricing