A MERN-stack bug tracker that turns vague bug reports into structured, reproducible ones — with an embeddable crash-reporting SDK, real-time collaboration and AI-assisted debugging.
No API keys and no config. The full stack — MongoDB, the Express API and the React client behind nginx — comes up with two commands:
git clone https://github.com/krishnendu-9/bugsense.git
cd bugsense
docker compose up --build
# in a second terminal, load 3 demo users and 4 sample bugs
docker compose exec server npm run seedThen open http://localhost:5173 and sign in with:
| Role | Password | |
|---|---|---|
| Admin | [email protected] |
password123 |
| Developer | [email protected] |
password123 |
| Reporter | [email protected] |
password123 |
The whole application is explorable with zero configuration. Without
ANTHROPIC_API_KEY, root-cause analysis falls back to keyword heuristics and
post-mortems to a data-filled template — both clearly labelled as such in the UI —
and patch generation reports that it is unavailable rather than inventing code.
Add a key to see Claude drive all three.
| Dashboard | Kanban Board |
|---|---|
![]() |
![]() |
| Bug Detail | AI Diagnostics |
|---|---|
![]() |
![]() |
| SDK Sandbox | Audit Trail |
|---|---|
![]() |
![]() |
Screenshots use the seeded demo data plus simulated SDK traffic, with no AI key configured — which is why analyses are labelled "Heuristic".
Bug reports are the worst part of software development — not because bugs exist, but because reports are incomplete. Developers waste hours asking "what browser?", "what error?", "can you reproduce it?". BugSense removes most of that back-and-forth: it auto-captures browser context, guides reporters through structured reproduction steps, records what a user did before a crash, and uses AI to suggest a likely cause and fix.
- One-Click CSV & JSON Data Export — Export every incident matching the current filters to CSV (formula-injection safe) or JSON
- In-App Webhook Settings & Live Test Ping — Each user configures their own Discord/Slack webhooks with alert preferences (new critical incidents, regressions) and a 1-click test ping
- Live System Vitals & Health Metrics (
/metrics, staff only) — Real-time Node.js process heap, MongoDB connectivity status, uptime counters, and telemetry deduplication efficiency analytics - Audit Trail (
/audit, staff only) — Paginated activity log of bug creation, updates and deletion, telemetry incidents and regressions, and GitHub exports - Embeddable Client SDK (
bugsense.js) — Zero-dependency agent any web app can install to auto-capture crashes and inject an in-app bug report pill; never records typed input, strips query strings from URLs, and can be torn down withBugSense.destroy() - Flight Recorder Breadcrumbs — Automatically records the user's last 15 actions (DOM clicks, page navigation, fetch API calls, and console logs) leading up to an incident
- Error Fingerprinting & Deduplication — Deterministic SHA-256 stack trace hashing groups recurring SDK errors with atomic, race-safe occurrence counters (e.g.
42x occurrences) and flags regressions; manual reports are never merged, but similar existing reports are pointed out - AI Pull Request & Patch Generator — Claude proposes unified Git diffs with syntax highlighting and copyable
git applycommands (requires an API key) - Interactive SDK Sandbox (
/sdk-demo) — Built-in simulated customer storefront allowing developers to inject real-world browser exceptions, failing API calls, and async rejections to demonstrate live telemetry ingestion - AI Incident Post-Mortem Generator — Formal engineering post-mortem synthesis in GitHub-flavored Markdown covering Executive Summary, Breadcrumb Timeline, RCA, and Action Items with 1-click
.mddownload - Real-Time Kanban Workflow Board (
/board) — Linear-style collaborative issue board featuring quick stage shifts (Open,In Progress,Resolved,Closed) synchronized via Socket.io - GitHub Issues 1-Click Export — Convert any bug report into an official GitHub Issue via GitHub REST API with stack traces and AI diagnostics attached
- Discord & Slack Webhooks — Alerts for new and regressed incidents, sent only to genuine Discord/Slack webhook URLs
- Structured Bug Reporting — Rich text descriptions, numbered reproduction steps, auto-captured browser/OS metadata
- Screenshot Uploads & Fabric.js Markup — Drag-and-drop screenshot upload with canvas markup (pencil, lines, rectangles, ellipses)
- Real-Time Collaboration — Authenticated Socket.io live updates for status changes, assignments, new incidents, and comments
- AI-Powered Diagnostics — Claude diagnoses root causes and suggests actionable fix steps; offline heuristic results are labelled as heuristics
- Profile & Settings — Manage display names, avatar uploads, and bcrypt password changes that sign out every other session
- Security — Server- and client-side HTML sanitization, role-based access control on every mutating route, authenticated WebSockets, SSRF-safe webhooks, optional SDK ingest key, multi-tier rate limiting, and Helmet headers
- Developer Dashboard — Recharts metrics, priority distributions, and recent incident streams
- Responsive Dark UI — Tailwind design tokens, skeleton loading states, keyboard-accessible controls, and a mobile navigation drawer
| Layer | Technology |
|---|---|
| Frontend Framework | React 18 (Vite) |
| Styling | Tailwind CSS v3 |
| Routing | React Router v7 |
| HTTP Client | Axios |
| Forms | React Hook Form |
| Rich Text | React Quill (react-quill-new) + DOMPurify |
| Canvas Annotations | Fabric.js |
| Icons | Lucide React |
| Charts | Recharts |
| Notifications | React Hot Toast |
| Date Formatting | date-fns |
| Backend | Node.js + Express.js |
| Database | MongoDB + Mongoose |
| Authentication | JWT + bcryptjs |
| File Uploads | Multer |
| Validation & Sanitization | express-validator, sanitize-html |
| Real-time | Socket.io (server + client) |
| Security | Helmet, CORS, express-rate-limit |
| AI | Anthropic SDK (Claude Sonnet) |
| Logging | Morgan |
| Linting | ESLint 9 (flat config, both packages) |
| Containers | Docker + Docker Compose + nginx |
| Testing | node:test + Supertest + mongodb-memory-server (API), Vitest + React Testing Library (client) |
| CI | GitHub Actions (lint, tests, build, image builds) |
Two ways in: a human filing a structured report, or an SDK in someone else's app reporting a crash automatically. Both are fingerprinted, but only SDK crashes are deduplicated — a person's report is always kept, with a "looks similar" hint. Every write fans back out over authenticated WebSockets.
flowchart TB
subgraph clients["Clients"]
UI["React SPA<br/>report · triage · Kanban"]
SDK["bugsense.js<br/>embedded in a 3rd-party site"]
end
subgraph api["Express API"]
AUTH["JWT auth<br/>reporter · developer · admin"]
BUGS["Bug routes<br/>CRUD · search · filters"]
TEL["Telemetry ingest<br/>public · rate limited<br/>optional ingest key"]
FP{{"Fingerprint<br/>SHA-256 of normalised<br/>stack trace + project"}}
INC["increment occurrences<br/>reopen if resolved<br/>= regression"]
AI["AI service<br/>Claude, labelled fallbacks<br/>hourly budget for SDK"]
end
subgraph out["Side effects"]
WS(["Socket.io broadcast"])
AUDIT[("Audit log")]
HOOK["Discord / Slack"]
GH["GitHub Issues"]
end
DB[("MongoDB")]
UI -->|"Bearer token"| AUTH
AUTH --> BUGS
SDK -->|"crash + breadcrumbs"| TEL
BUGS -->|"always a new bug"| DB
BUGS -.->|"similar report?"| FP
TEL --> FP
FP -->|"new SDK crash"| DB
FP -->|"seen before (SDK)"| INC
INC --> DB
TEL --> AI
DB --> WS
DB --> AUDIT
FP --> HOOK
BUGS --> AI
AI --> DB
BUGS --> GH
WS -.->|"live updates"| UI
The part worth reading the code for is the fingerprint step. Incoming stack
traces are normalised — UUIDs, memory addresses, timestamps and line numbers
stripped — then hashed with the project name. Identical SDK crashes collapse into
one incident with an occurrence counter instead of a thousand duplicate rows, and
a crash that reappears after being marked resolved is automatically flagged as a
regression. See fingerprint.util.js and
telemetry.controller.js.
The choices below shaped the code most. Each one records what was done, why, and what it costs.
Only machine reports are deduplicated. Merging two SDK crashes loses nothing, because they're the same stack trace. Merging two human reports throws away someone's description and steps, so manual reports are always kept and the reporter is shown the similar incident instead.
Deduplication is race-safe by construction. A crash loop can send many
identical reports at once. Counting uses a single atomic $inc, and a unique
partial index on {fingerprint, source: 'sdk'} means two simultaneous "first"
reports can't both create an incident — the loser gets a duplicate-key error and
is retried as a repeat. A regression is flipped with one conditional update, so
it's recorded exactly once. The API tests fire eight reports concurrently to
prove it.
Rich text is sanitised twice. Descriptions are cleaned with sanitize-html on
write and with DOMPurify before rendering. The server copy protects every
consumer of the API; the client copy also covers records stored before
sanitising existed. This matters more than usual because the JWT lives in
localStorage (a project requirement): any script injection could read it. An
httpOnly cookie would be the stronger choice in a real deployment.
Sessions can be revoked. Each user has a tokenVersion embedded in their JWT.
Changing a password increments it, which invalidates every other session
immediately without keeping a server-side token list.
The public endpoint is treated as hostile. /api/telemetry/report must accept
unauthenticated traffic from other origins, so it has its own CORS policy, a
64 KB body limit, per-field length caps, enum coercion, its own rate limit and an
optional shared ingest key. Each new crash triggers an AI analysis, so those are
capped per hour — otherwise anyone could run up the API bill.
AI output always says where it came from. Every result carries source
(claude, heuristic, template or unavailable) and the UI labels it. Without
an API key, patch generation returns nothing rather than an invented diff: a
plausible-looking fake patch is worse than none.
One payload shape everywhere. REST responses and every Socket.io event send the same fully populated bug, so a listener can replace its copy without losing names or history. Sockets require the same JWT as the REST API.
Permissions live on the server; the UI mirrors them. Every mutating route checks
the role or ownership (authorize(), canModifyBug()); the client hides
controls the user can't use, but never relies on that for security.
Testable by design. The Express app is built by createApp() in app.js,
separate from the process that listens and connects to the database, so the API
tests run the real middleware stack against an in-memory MongoDB.
- Uploads are stored on the server's local disk; a multi-instance deployment would need object storage (S3, Cloudinary).
- The AI budget and rate limits are per process, not shared across instances (a Redis store would fix this).
- Single workspace: every signed-in user can see every bug.
- No email verification or password reset flow.
bugsense/
├── .github/workflows/ # CI: lint, tests, build, docker image builds
├── docker-compose.yml # Full stack: MongoDB + API + nginx client
├── client/ # React frontend (Vite)
│ ├── eslint.config.js
│ ├── nginx.conf # Proxies /api, /uploads, /sdk, /socket.io
│ ├── Dockerfile
│ └── src/
│ ├── api/ # Axios instance with auth interceptor
│ ├── components/
│ │ ├── ai/ # ErrorInsights, GitPatchModal, PostMortemModal
│ │ ├── bugs/ # BugCard, BugForm, BugFilters, StepsReproducer,
│ │ │ # AnnotationCanvas, BreadcrumbTimeline
│ │ ├── common/ # Navbar, Sidebar, Loader, Badge, ProtectedRoute,
│ │ │ # ErrorBoundary, RoleRoute
│ │ └── dashboard/ # StatsCard, BugChart, RecentBugs
│ ├── context/ # AuthContext, SocketContext
│ ├── hooks/ # useAuth, useBugs, useAI, useSocket, useDebouncedValue
│ ├── pages/ # auth/, bugs/ (list, detail, report, kanban),
│ │ # dashboard/, ai/, audit/, metrics/,
│ │ # playground/, profile/
│ └── utils/ # constants, helpers
└── server/ # Express backend
├── eslint.config.js
├── Dockerfile
├── app.js # Express app (imported by server.js and the tests)
├── server.js # Entry point: DB connection, HTTP + Socket.io server
├── config/ # MongoDB, Socket.io
├── controllers/ # auth, bug, comment, ai, user, telemetry, audit
├── middleware/ # auth, upload, rateLimit, error
├── models/ # User, Bug, Comment, AuditLog
├── public/sdk/ # bugsense.js — the embeddable client SDK
├── routes/ # auth, bugs, comments, ai, users, telemetry, audit
├── scripts/ # seed.js
├── services/ # ai, token, audit, webhook, bugEvents
├── tests/ # node:test + Supertest API and unit tests
├── utils/ # fingerprint, sanitize, permissions, webhookUrl,
│ # uploads, aiBudget
└── uploads/ # Multer file storage
- Node.js 20+
- MongoDB (local or Atlas)
- Anthropic API key (optional — AI features degrade gracefully without it)
git clone https://github.com/krishnendu-9/bugsense.git
cd bugsenseRun the full stack (MongoDB, Express API, and Nginx React Client) with a single command:
cp .env.example .env # optional: add ANTHROPIC_API_KEY and a real JWT_SECRET
docker compose up --build- App (and API under
/api): http://localhost:5173
The client image is built with VITE_API_URL=/api, so all browser traffic goes
through nginx, which proxies /api, /uploads, /sdk and /socket.io
to the server container. The server itself is not published on a host port, and
MongoDB is bound to 127.0.0.1 only. The server runs with NODE_ENV=production,
so set a real JWT_SECRET in .env before exposing it anywhere.
cd server
npm install
cp .env.example .envEdit server/.env:
PORT=5000
MONGO_URI=mongodb://localhost:27017/bugsense
JWT_SECRET=your_super_secret_jwt_key_here
ANTHROPIC_API_KEY=sk-ant-...
CLIENT_URL=http://localhost:5173Start the backend:
npm run devcd client
npm installCreate client/.env:
VITE_API_URL=http://localhost:5000/apiStart the frontend:
npm run devcd server
npm run seedCreates three demo accounts and four sample bugs. Seeding deletes all data, so it
refuses to run on a database that already has users; use npm run seed:reset to
wipe and reseed deliberately.
Both packages are linted with ESLint (flat config). The server has an API test suite (Node's built-in test runner + Supertest) that runs against an in-memory MongoDB, covering auth, permissions, XSS sanitization, SSRF protection, telemetry deduplication and socket authentication. The client has component and utility tests (Vitest + React Testing Library) for role-gated navigation, form validation, HTML sanitization and honest AI labelling. CI runs all of it on every push.
cd server && npm run lint && npm test
cd client && npm run lint && npm testSet MONGO_URI_TEST to run the tests against an existing MongoDB instead
(use a dedicated database — the suite drops it).
Navigate to http://localhost:5173
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | /register |
Register new user | — |
| POST | /login |
Login, returns JWT | — |
| GET | /me |
Get current user | Bearer |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | / |
List bugs (filter: status, priority, assignedTo, reporter, project, search, page, limit) |
Bearer |
| POST | / |
Create bug | Bearer |
| GET | /stats |
Dashboard statistics | Bearer |
| GET | /:id |
Single bug with comments | Bearer |
| PUT | /:id |
Update bug (whitelisted fields only) | Bearer |
| DELETE | /:id |
Delete bug | Admin only |
| POST | /:id/screenshot |
Upload screenshot (multipart screenshot) |
Bug reporter / Staff |
| POST | /:id/annotation |
Upload annotated screenshot (multipart annotation) |
Bug reporter / Staff |
| POST | /:id/github |
Export bug as a GitHub Issue | Staff |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | / |
Add comment | Bearer |
| GET | /bug/:bugId |
Get comments for a bug | Bearer |
| DELETE | /:id |
Delete comment | Author/Admin |
| Method | Endpoint | Body | Returns | Auth |
|---|---|---|---|---|
| POST | /analyze |
{ errorLog, bugContext?, bugId? } |
{ possibleCause, suggestedFix, source } |
Bearer |
| POST | /generate-patch |
{ bugId?, errorLog?, bugDescription?, steps? } |
{ diff, explanation, source, generatedAt } |
Bearer |
| POST | /post-mortem |
{ bugId } |
{ markdown, source, generatedAt } |
Bearer |
source says what produced the result: claude, or without an API key heuristic (analysis), template (post-mortem) or unavailable (patch — the diff is empty). Passing bugId saves the result on that bug, which requires permission to edit it.
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | /assignable |
Developers and admins a bug can be assigned to | Staff |
| GET | /profile |
Get current user profile | Bearer |
| PUT | /profile |
Update name / change password (returns a fresh token; other sessions are revoked) | Bearer |
| POST | /avatar |
Upload profile avatar | Bearer |
| PUT | /webhooks |
Save Discord/Slack webhook settings | Bearer |
| POST | /webhooks/test |
Send a test alert to a webhook | Bearer |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| POST | /report |
SDK crash/feedback ingestion with fingerprint deduplication | Public, rate limited; X-BugSense-Key when TELEMETRY_INGEST_KEY is set |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | / |
Paginated activity log (filter: entityType, action) |
Staff |
| Method | Endpoint | Description | Auth |
|---|---|---|---|
| GET | / |
Liveness probe | — |
| GET | /metrics |
Uptime, heap, Mongo status, dedup efficiency | Staff |
Staff means the developer or admin role. Sockets require the same JWT, passed as auth.token in the Socket.io handshake.
bugsense.js is a zero-dependency agent any web app can drop in. It records the
last 15 user actions, auto-reports uncaught errors and unhandled rejections, and
injects a floating "Report Bug" widget.
<script
src="https://your-bugsense-host/sdk/bugsense.js"
data-api-url="https://your-bugsense-host"
data-project="My Client App"
data-key="your-ingest-key"
></script>| Attribute | Default | Description |
|---|---|---|
data-api-url |
http://localhost:5000 |
Base URL of the BugSense API |
data-project |
Production Web App |
Project name recorded on every incident |
data-widget |
true |
Set to "false" to suppress the floating report pill |
data-key |
— | Ingest key, required when the server sets TELEMETRY_INGEST_KEY |
Manual capture:
window.BugSense.captureError(err, 'Checkout failed');
window.BugSense.captureMessage('Coupon banner clicked', 'low');
window.BugSense.addBreadcrumb('click', 'User opened the cart drawer');
window.BugSense.destroy(); // restore patched globals and remove the widgetPrivacy: the SDK never records form field values or text from inputs,
textareas, selects, contenteditable regions, or anything inside an element
marked data-bugsense-mask. Query strings and fragments are stripped from every
reported URL.
The SDK caps itself at 20 reports per page session and suppresses repeats of the
same error within 10 seconds, so a crash loop in the host page cannot flood the
ingestion endpoint. Try it live at /sdk-demo.
| Variable | Required | Description |
|---|---|---|
PORT |
no | Server port (default: 5000) |
MONGO_URI |
no | MongoDB connection string (defaults to mongodb://localhost:27017/bugsense) |
JWT_SECRET |
in production | Secret key for JWT signing. The server refuses to start without it when NODE_ENV=production |
ANTHROPIC_API_KEY |
no | Anthropic API key. Without it, AI features fall back to heuristics |
CLIENT_URL |
no | Frontend URL, used for CORS and links in webhook alerts |
NODE_ENV |
no | Set to production to enforce the JWT check and production rate limits |
DISCORD_WEBHOOK_URL |
no | Global Discord webhook for incident alerts |
SLACK_WEBHOOK_URL |
no | Global Slack webhook for incident alerts |
GITHUB_TOKEN |
no | Default token for GitHub Issue export (can also be supplied per request) |
TELEMETRY_INGEST_KEY |
no | Shared secret the SDK must send (data-key). Unset = open ingestion, fine for local demos |
TELEMETRY_ALLOWED_ORIGINS |
no | Comma-separated origins allowed to post telemetry (default: any) |
TELEMETRY_AI_HOURLY_BUDGET |
no | Max background AI analyses started by public telemetry per hour (default: 20) |
| Variable | Description |
|---|---|
VITE_API_URL |
Backend API base URL — absolute (https://api.example.com/api) or relative (/api). Sockets and uploads use the same origin |
Built on a consistent dark theme with glass morphism cards:
- Primary:
#6366F1(Indigo) - Secondary:
#8B5CF6(Purple) - Background:
#090D16(Near-black navy) - Surface:
#111827(Dark slate) - Cards:
bg-white/5 backdrop-blur border border-white/10
MIT






