Disbug
A bug-reporting tool for developer teams
- Year
- 2021 – 2024
- Role
- Built core capture pipeline + integrations layer
- Status
- Live — v2 rebuilt after my time
What it is
Disbug was a bug-reporting tool for developer teams. A Chrome extension captured screenshots, video recordings, console logs, network requests, and rrweb session replays from a browser session, then attached the report to an issue tracker — with a 2-way sync so status changes flowed back. A few hundred teams used it between 2022 and 2024.
My role
I was the first engineering hire (joined Sep 2021), working alongside the founder. Over my Trainee → Full Stack Developer years (2021–2023), I owned a few specific subsystems on Disbug: the in-extension capture pipeline (network / console / video timing), the third-party issue-tracker integration layer in the backend, and the embeddable feedback widget. Other parts of Disbug — the original XHR capture, the React rewrite, several later integrations — were built by the founder or other engineers.
What I built
The network-capture pipeline
Disbug needed to attach every network request the user’s page made to the bug report. The original code captured XHR. I added fetch() capture on top, which turned out to be much fussier:
- The
fetchAPI gives youRequestandResponseobjects with bodies you can only read once. If you read them to log them, the page’s own code breaks (“body already used”). Solution: clone every Request/Response before reading. - Reading a response body is async and the response can resolve at any time; if you log it before the body is ready, you get an empty record. Solution: use
Promise.allto gate the log on both the response and its body. - Logging every response body would balloon payload size and hit content types where the body is binary garbage. Solution: a content-type allow-list (JSON,
text/*, form-encoded — that’s it).
I also added a separate path for non-fetch / non-XHR resources via PerformanceObserver (CSS, fonts, images), with deduplication so the same request didn’t appear in both the fetch log and the resource log.
Time-windowed evidence
A bug report includes screenshots, a recording, and the logs from “around” the user’s action. The “around” was vague — without bounds, we’d attach 30 minutes of unrelated console spam. I added 5-minute windowing: drop any network / console / event log older than 5 minutes before the screenshot or the recording start. Smaller payloads, less noise.
Navigation-aware recording
If a user refreshed or navigated mid-recording, the recording broke. I hooked chrome.webNavigation.onBeforeNavigate to insert a pause marker before the navigation and a resume marker after the new page loaded — so the recording stayed continuous across page reloads.
The third-party issue-tracker integration layer (backend)
This was the meat of my backend work. I built and maintained the integrations layer in the Django backend — 7 trackers in total (Jira Cloud + Jira Server, ClickUp, Monday, Trello, GitLab, GitHub). For each one:
- Connection adapter — OAuth flow, credential storage, token refresh.
- Destination fetching — pull the user’s projects / boards / lists from the tracker so they could pick where bugs go.
- Submission — post the bug to the tracker (attachments, status, priority, custom fields).
- 2-way sync via webhooks — when someone closed / updated / deleted the issue in Jira (or wherever), we received a webhook and mirrored the change back onto the Disbug issue. I designed the
WebhookListenermodel and the receiver endpoints; later trackers (Linear, Asana, Notion, Azure DevOps) were built by other engineers on top of this base.
The embeddable feedback widget
A separate sub-product: a JS snippet sites embedded to let their users report bugs (vs. Disbug’s main extension which captured internal team bug reports). I built it largely on my own: a Vue app compiled to a single-file Web Component (so it could be dropped into any site without conflicts), html2canvas for screenshot capture, custom marker.js2 marker subclasses for highlight + blur-to-redact annotation, console / window.onerror interception, browser-metadata collection, and authenticated submission to the Disbug backend.
A session-replay SDK for an analytics vendor
Around the same time I built a session-replay SDK for UserExperior, shipped publicly as the user-experior-web npm package. I wrote essentially all of it: a TypeScript browser SDK with rrweb-based DOM recording (with privacy masking), automatic XHR / fetch / console / error capture, a unique-CSS-selector generator for clicked elements, multi-tab session / idle lifecycle (auto-restart on logout, browser-close, 30-min idle), and batched JSON upload — published with UMD/CJS/ESM builds.
Status
Disbug is still live, but not the version I built. The v1 I worked on ran from 2021 to around 2024. It has since been rebuilt as a v2 with AI features, which is deployed now — I had no part in that rebuild. Everything above is v1.
What I learned
Disbug was where I learned what it means to ship browser-runtime code that has to not break the host page. Every monkey-patch (fetch, XHR, console, errors) is a way to break someone’s app if you get it wrong. Most of the work was defensive. I still write browser instrumentation the same way.