-
-
TRAIL: every bug leaves a trail. Records what happened in the browser, so you don't have to explain it from memory.
-
Flagging the moment: the user adds the one piece of context the browser can't capture on its own — what they expected to happen.
-
Session replay and evidence timeline, synced. Scrub the replay and the timeline follows; click an event and the replay jumps there
-
The technical evidence, captured automatically: the overlapping request rejected with a 429, right alongside everything else
-
Sensitive inputs are masked at the source. Passwords and emails never reach disk unredacted, not redacted after the fact
-
One click turns the trail into a maintainer-ready GitHub issue: reproduction steps, environment, console errors, failed requests, and more!
-
How it works: capture in the browser → review the trail → export straight into GitHub's issue flow.
TRAIL: Every bug leaves a trail
One sentence: Nobody should have to describe a bug from memory when the browser was right there for all of it. TRAIL is a browser extension that records the trail a bug leaves behind and turns it into a GitHub issue ready to file: exact clicks, console errors, failed requests, a playable replay.
What started this
Every maintainer knows the reply. Someone files a bug and the first thing you type is "can you provide a minimal reproduction?" Usually that's where the thread goes quiet.
The reporter isn't lazy. They genuinely want to help, but they don't remember the click order, never looked at the console, and have no idea what "environment" means. So the issue sits open for weeks while a fixable bug does nothing.
I kept watching this loop repeat in open-source repos, and it felt like a tooling problem, not a people problem. The existing options all ask something of the app. Sentry needs an SDK. Session replay tools need integration. Chrome's built-in Recorder is a developer automation tool, not something a user files a bug with. Nobody built the tool for the weak link in the chain: the reporter standing in front of the browser.
This isn't a feeling. Next.js, one of the most-used frameworks on the web, runs a GitHub Action whose whole job is closing bug reports that never got a reproduction: labeled "please add a complete reproduction", the issue auto-closes after two days of silence; "please simplify reproduction" buys the reporter two weeks. That's not tooling for helping reporters. It's infrastructure for giving up on them.
And the complaint is old enough to have a history. In 2016, more than 1,700 open-source maintainers signed an open letter to GitHub listing issues filed without reproduction steps as one of three things broken enough to demand fixing. GitHub's answer was issue templates. A template asks a human to remember something they never wrote down in the first place.
The numbers back it up. A 2026 study of Mojira, Minecraft's bug tracker (ImproBR, arXiv:2604.26142), pulled 24,998 bug reports and manually evaluated the 139 that ended up in the failure buckets: waiting on reporter info, un-reproducible, or closed incomplete. Only 7.9% of those contained all three essential elements. Only 28.8% had reproduction steps a developer could actually follow, and 70.5% never stated what the reporter expected to happen. Those are exactly the reports TRAIL stops from happening.
So the tool had to live in the reporter's browser, and nowhere else. That decision shaped everything that followed.
What I built
A browser extension. Open it, hit Record, reproduce the bug on any site exactly as you normally would, stop. The extension captures six kinds of evidence: clicks with readable labels ("Clicked Sign in", not coordinates), what was typed and into which field, the environment, console errors, failed network requests with their status codes, and a full session replay via rrweb. A small draggable overlay counts clicks, console errors, and failed requests live, so you watch capture happening instead of trusting a silent black box.
Stop, and a review screen shows the replay next to a generated timeline: Navigated to /checkout → Typed in Email field → Clicked Submit → Console error → POST /api/submit failed, 500. Export produces a deterministic Markdown report and a prefilled GitHub new-issue URL, mapped onto the target repo's actual issue template when it has one. You submit in GitHub's own UI. TRAIL never touches your account.
Nothing uploads until you choose to share. And sharing produces a link that renders the whole review in any browser, extension or not.
How it works under the hood
The capture half. A content script lives in an isolated world, so patching console.error or window.fetch there patches copies of functions the page never calls. The recorder is split in two: a MAIN-world script that genuinely wraps the page's own functions and emits everything as postMessage events, and an isolated-world relay that batches those to the service worker about once a second. The service worker is deliberately stateless; it writes batches straight to IndexedDB and keeps only counts in session storage. When Chrome kills it mid-recording, which it will, everything already captured survives.
The safety half. Passwords, emails, and card-shaped fields are excluded before recording starts, using rrweb's input masking and block-class rules. Those values never exist in the recording at all. Not blurred, not hidden after the fact. Never there. There's still a backstop toggle on the review screen for anything that looks sensitive in hindsight, but it's a second net, not the thing keeping you safe.
The report half. The pipeline is deterministic. Title suggestion, reproduction steps, and the timeline all come from the captured events themselves, with no model involved, and they render locally. AI is optional and additive: an OpenRouter pass drafts the title, summary, and steps, a Groq pass suggests labels, results are cached by session hash and merged with the deterministic output. Configure neither key and the report is still complete; you just get the plain digest.
Two build-time flukes worth knowing. rrweb's bundle contains the Unicode non-character U+FFFE, which Chrome's content-script loader rejects as not UTF-8, so a custom WXT plugin escapes non-ASCII bytes to \uXXXX sequences that are runtime-identical. And GitHub's issues/new page is a GET that nginx 414s somewhere around eight kilobytes, so the URL builder fits to a 7600-character budget, cuts sections by priority when something has to go, and always keeps the full report on the clipboard.
What went wrong, and what I'm taking from it
The hardest part was honest pre-capture redaction. A lot of tools sell "privacy" and deliver blur-after-record. Making "never recorded" literally true meant getting the masking right at the rrweb source and verifying password, email, and card inputs on test pages. That is the difference between architecture you can claim and architecture you can defend.
The other hard part was scope. Two weeks, solo. The rubric rewards a working product, so I decided early that the capture-to-report-to-share path had to be bulletproof and on camera, and everything else could wait. It was the right call. The product spec I wrote before the first commit still lists click-to-select redaction, duplicate detection, and API submission as stretch goals; the shipped path does everything the core spec promised.
I also learned something about writing software for other people's reputations. A bug report carries the reporter's name on it. That means the output has to read better than the reporter could have typed themselves, with evidence structured so a maintainer can act on it, not a log dump. People who file a good report once tend to keep doing it. That's the behaviour change I actually want to cause.
What's next
AI duplicate detection against the repo's open issues, direct GitHub API submission so filing is one click, and Linear/Jira support. The deterministic core stays deterministic. The AI stays optional. That's the line I don't want to cross.
Log in or sign up for Devpost to join the conversation.