Bug reports
Users report a bug from the chat. The report carries what they wrote, the browser context
(session replay, last trace id, conversation and message ids, build, viewport, recent console
errors). There is no screenshot: the HyperDX session replay linked by replay_url shows what
the user saw. Routes live in routers.bug_report, logic in src.services.bug_reports.
File a report
POST /bug-reports, authenticated, multipart/form-data:
| Field | Type | Rules |
|---|---|---|
description |
text | 1 to 4000 characters, not blank |
context |
JSON text | object below, required ({} is valid) |
Any other part is ignored. A screenshot file part, sent by frontends older than
2026-09-24, is dropped unread and the report is stored as if it were absent.
context, every key optional, unknown keys dropped:
{
"session_id": "rum session id",
"replay_url": "HyperDX replay deep link: /sessions?sid=...&sfrom=...&sto=...&ts=...",
"trace_id": "last trace id the page saw",
"conversation_id": "...",
"message_id": "...",
"app_version": "v0.1.1",
"app_commit": "8ea24e5",
"environment": "dev",
"user_agent": "...",
"viewport": {"width": 1440, "height": 900},
"path": "/chat/...",
"console_errors": ["at most 50 entries, each clipped to 2000 chars"],
"privacy_mode": "off | mask | on_demand | clear"
}
Answers:
201{"id", "created_at"}.401without a valid credential.404whencontext.conversation_idis missing or not the caller's. Nothing is stored.422blank or too long description, missing context, invalid context JSON or field.429{"detail": {"code": "bug_report_rate_limited", ...}}withRetry-After: 3600afterBUG_REPORT_MAX_PER_HOUR(default 5) reports in the rolling hour. Counted on thebug_reportscollection, no Redis;<=0disables it. The whole limit is behindFEATURE_BUG_REPORT_RATE_LIMIT(default on):falseor0means no limit at all.
curl -X POST "$BASE_URL/bug-reports" \
-H "Authorization: Bearer $ACCESS_TOKEN" \
-F 'description=The answer stopped halfway' \
-F 'context={"conversation_id":"...","path":"/chat/..."}'
Read a report back
GET /bug-reports/{id} returns the whole stored report to its author: description, context,
conversation snapshot and request_trace_id. Anyone else and an unknown id answer 404.
Storage
- Collection
bug_reports:user_id,description,context(every field above,nullwhen not sent),conversation(server side snapshot ornull),request_trace_id(trace of the POST,nullwith telemetry off),timestamp. Indexbug_reports_by_user_timebacks the rate limit. - Description and every context string are redacted with
src/utils/redaction.pybefore they are stored: bearer tokens, JWTs,eve_keys, credential query parameters and email addresses become placeholders. replay_urlgoes through the same redaction and is otherwise stored as sent: itssid,sfrom,stoandtsparameters carry no secret and are left untouched.- Reports filed before 2026-09-24 may still hold a
screenshotsubdocument and objects underbug-reports/in the artifacts bucket. Nothing reads them any more.
Log event
Each stored report emits bug_report.created at WARNING on logger eve.bug_report, inside
the request span, so it carries the request's trace and span id when telemetry is on. With
telemetry off it is a plain WARNING line. Attributes (absent values left out):
event.name, eve.bug_report.id, rum.sessionId, eve.replay_url,
gen_ai.conversation.id, eve.message_id, user.id, deployment.environment.name,
eve.bug_report.messages, eve.bug_report.truncated. eve.replay_url is the deep link as
stored.
The description is never in the event. The request span also gets eve.bug_report.id and
user.id.