Home

BugTape docs

Capture and Error Semantics

How the SDK captures, buffers, deduplicates and submits.

How the BugTape SDK captures, buffers, deduplicates, and submits bug reports.

Automatic capture

Once BugTape.init() is called, six recorders start automatically:

RecorderEvents capturedCan disable?
DOMrrweb visual replay (mutations, mouse, scroll, input)disableDomRecording: true
NetworkFetch, XHR, resource timing (URL, status, duration)disableNetworkRecording: true
Consoleconsole.log/warn/error/info/debugdisableConsoleRecording: true
Errorswindow.onerror — uncaught exceptions with stack tracesAlways on
Rejectionsunhandledrejection — unhandled promise rejectionsAlways on
Rage clicksRapid repeated clicks on same elementAlways on

Sensitive tables / client grids: mark with data-bugtape-ignore / .bugtape-ignore / .rr-block before any capture key. Prove with the privacy proof checklist and privacy fixture. Default PII regex is not enough for named clients in visible text.

All recorders are wrapped in error boundaries. If a recorder fails, the SDK logs a warning and continues — it never crashes the host application.

Browser crash and tab close

The SDK does not attempt to capture browser crashes or beforeunload events. If the browser tab crashes or is closed:

  • Buffered events are lost (they are in-memory only).
  • Any pending report from a previous session that was stored in localStorage (due to network failure) will be resubmitted on the next BugTape.init().

For crash monitoring, use a complementary service or beacon API.

Unhandled error capture

window.onerror

Captures uncaught exceptions globally. Event data:

{ type: "error", data: { message, filename, lineno, colno, stack } }

The SDK does not call preventDefault() — errors still propagate to the browser console and any other error handlers.

unhandledrejection

Captures unhandled promise rejections. Event data:

{ type: "error:unhandledrejection", data: { message, stack } }

Auto-reporting

Errors are buffered, not auto-submitted. The SDK does not automatically submit a report when an error occurs. Reports are only submitted when:

  1. A user clicks the bug button and submits through the widget.
  2. Your code calls the API directly (manual report).
  3. You implement custom logic in the onSubmit callback.

If you want auto-reporting on errors, implement it yourself:

window.addEventListener('error', async (event) => {
  await fetch('https://app.bugtape.ai/v1/ingest', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'X-BugTape-Key': 'bt_live_xxx',
    },
    body: JSON.stringify({
      title: event.error?.message || 'Uncaught error',
      severity: 'high',
      url: window.location.href,
      userAgent: navigator.userAgent,
      viewport: `${innerWidth}x${innerHeight}`,
      events: bt.getEvents(),
    }),
  });
});

Recommendation: If you implement auto-reporting, add throttling (see below).

Manual issue reporting

Users file reports through the SDK widget (floating bug button) or your own UI. The widget flow:

  1. User clicks bug button → widget opens.
  2. User optionally captures a screenshot (with annotation tools).
  3. User optionally records an audio note.
  4. SDK generates an AI summary (if openaiKey configured).
  5. User fills in title, description, severity.
  6. User clicks submit → report sent to API with all buffered events.

For programmatic reporting without the widget, POST directly to /v1/ingest.

Deduplication

Server-side fingerprinting

Every report is fingerprinted on the server:

SHA256(errorType : normalizedMessage : normalizedUrl).slice(0, 16)
  • Same fingerprint + open bug: report count incremented, no new bug created.
  • Same fingerprint + resolved bug: bug reopened as regression.
  • New fingerprint: new bug created.

The SDK does not deduplicate on the client. If you implement auto-reporting, add your own throttle:

const reported = new Set<string>();

function shouldReport(errorMessage: string): boolean {
  const key = errorMessage.slice(0, 100);
  if (reported.has(key)) return false;
  reported.add(key);
  setTimeout(() => reported.delete(key), 60_000); // Allow re-report after 1 min
  return true;
}

Recommended throttle: Max 5 auto-reports per minute per session. The server rate limit is 100 requests/minute per IP, but lower client-side throttling reduces noise.

Event ordering

Events are stored with millisecond Unix timestamps (event.timestamp). The SDK does not guarantee strict ordering — events from different recorders may interleave. The server stores events in insertion order.

When displaying events, sort by timestamp. For DOM replay, rrweb events contain their own internal ordering.

Session correlation

The SDK does not currently assign a session ID. All events in a single report are implicitly from one session (the buffer contents at submission time).

To correlate multiple reports from the same user session:

  1. Generate a session ID in your app (e.g., crypto.randomUUID()).
  2. Include it in the report description or as a custom event:
bt.buffer.push({
  type: 'session',
  timestamp: Date.now(),
  data: { sessionId: 'your-session-id', userId: 'user-123' }
});
  1. Search for the session ID in the BugTape console.

Attachments and replay data

Screenshots

The SDK captures screenshots using modern-screenshot (DOM-to-image). Screenshots are:

  • Rendered at device pixel ratio (up to 2x).
  • Annotatable with a drawing tool before submission.
  • Included as base64 in the report payload.

Audio notes

Users can record voice notes via the MediaRecorder API:

  • Format: WebM with Opus codec.
  • Stored as base64 data URL in the report payload.

Session replay

DOM events recorded by rrweb are included in the events array. The BugTape console can replay these events visually. The replay player is read-only — the rrweb events are opaque blobs stored as-is.

Offline and flaky network behavior

Retry logic

  1. SDK submits the report via fetch().
  2. If the request fails, the SDK retries once after a 1-second delay.
  3. If the retry also fails, the report is serialized and stored in localStorage under a BugTape-specific key.

Pending report resubmission

On the next BugTape.init() call (e.g., page reload), the SDK checks localStorage for pending reports and resubmits them. Successfully submitted pending reports are cleared from storage.

Buffered events during offline

Events continue to be captured in the ring buffer while offline. If the user submits a report while offline, the events captured up to that point are included. The report will be stored locally and resubmitted when connectivity returns.

Network failure events

Network failures are captured as network:fetch:error events and included in the buffer, providing context for debugging connectivity issues.

Best practices for auto-reporting

  1. Throttle client-side. Max 5 auto-reports per minute. Use a Set or Map to track recently reported error messages.
  1. Prioritize severity. Auto-report critical and high severity errors (uncaught exceptions, unhandled rejections). Log medium and low severity events to the buffer only.
  1. Include buffered events. Always call bt.getEvents() and include the result. This gives BugTape the full context (what happened before the error).
  1. Set reporterEmail. If you know the user's identity, include reporterEmail so reports can be attributed.
  1. Handle 402 gracefully. If the API returns 402 (plan limit), stop auto-reporting for the rest of the session to avoid noise.
  1. Handle 429 gracefully. If rate-limited, back off exponentially. The retryAfter field tells you how long to wait.
  1. Don't suppress widget reports. Even with auto-reporting, keep the widget available. User-submitted reports include context (title, description, screenshot) that auto-reports lack.