Bug Report Intake (part 4)
Part 4 of the Bug Report Intake page: the table of decisions you can see when a report does not route, the analytics events intake records, what is deliberately out of scope for the first version, and a map of every file and route behind the feature for readers who have the repository open.
What it is
This is part 4 of the Bug Report Intake page. It is the reference half: the table of decisions you can see when a report does not route, the analytics events intake records, what is deliberately out of scope for the first version, and a map of every file and route behind the feature for readers who have the repository open.
Where to find it
Nothing in this part is a place you visit — it is the reference behind the panel described on the earlier parts. Keep part 1 open for the Bug Intake sidebar row, part 2 for the Sources tab, and part 3 for what a spawned session receives.
How it behaves
Failure modes — what you'll see
| Decision | When it fires | Tag / row state | Session spawn? |
|---|---|---|---|
routed |
Email: match + safe + under cap + not in cooldown | Routed to <Project Name> |
yes |
unrouted |
Email: subject parsed but no project has that slug | Unrouted bug report |
no |
cooldown |
Email: same slug fired in last 30s | Bug intake — cooldown for <slug> (30s) |
no |
cap |
Email: project hit bugIntakeDailyCap for the day |
Bug intake — daily cap reached |
no |
invalid |
Email: body sanitized to empty | Bug intake — empty body |
no |
failed |
Spawn (DB insert or CLI launch) threw | Bug intake failed: <reason> |
attempted; soft-deleted on launch failure |
consolidated (email) |
Email: the copy of an in-app report whose in-app twin already claimed it | Audit — Merged into open issue | no — the in-app report's session has it |
screen_held |
In-app, teammate, GitHub or Sentry: the safety screen flagged the report or could not check it | Review tab + its own inbox card — Held by safety screen, the reason, Spawn anyway / Dismiss | no — only you can start it |
unrouted (pending review) |
Sentry: source's first poll exceeded 5 unresolved issues | Review tab — Spawn now / Dismiss | no |
manually_dismissed |
Sentry: user clicked Dismiss in Review | Audit only | no |
routed (Sentry) |
Sentry: autoSpawn on, new issue, dedupe row claimed |
Audit; session linked | yes |
| (no row) | Email: subject didn't match the regex; or Sentry: issue already had a bug_intake_processed row |
none | no |
Analytics
Successful routes emit a bug_intake_routed feature event with { decision, reportType } metadata for usage tracking. Duplicate detection adds two more: bug_intake_dedup_hint_emitted (Layer 1 prepended a hint) and bug_intake_self_dedup (Layer 2 archived a confirmed duplicate) — source + ids only, never report text. No PII (sender, body, Sentry payload) is recorded in the analytics table. The full audit trail lives in bug_intake_processed with project FK, slug, decision, reason, source kind (email | sentry | github), external_id, external_url, severity, the dedup signature, and the spawned session_id for forensics.
What's NOT in v1 (Sentry vertical, this PR)
- GitHub manual-spawn from Review + first-connect-deferral audit rows: GitHub auto-spawn polling now ships (see "GitHub intake" above), but the Review-tab Spawn now button stays Sentry-only (a report the safety screen held is the exception: its Spawn anyway works for every source — see the Bug intake safety screen page), and GitHub's first-connect cap silently defers the overflow (no Review row) instead of recording deferrals the way Sentry does. Label-based report-type inference, issue-comment reply-back, multi-repo single sources, and per-source rate-limit backoff are also out of scope for the GitHub v1.
- Per-project Sentry filtering (level/environment/release) — every unresolved issue is a candidate
- Per-source prompt override — you can append your own instructions globally and per project (see "Appended investigation instructions" above), but not per individual Sentry source; the built-in investigation framing/preamble itself is fixed across email + Sentry
- Domain-wildcard email rules (e.g.
*@acme.com) — email intake rules match an exact (sender, slug) pair only; a global approved-senders allow-list + per-(sender, slug) rules (see "Email intake rules" above) already exist, but wildcards do not - Web form intake — email + Sentry only
- Auto-reply to unrouted senders — they appear in the inbox tagged unrouted
- Multi-org Sentry under one credential — one
(project, source)row per project today
For agents
Where this lives in code
- Sidebar virtual project:
BUG_INTAKE_PROJECT_ID = '__bug_intake__'in src/shared/virtual-project-ids.ts - View / form: src/renderer/src/features/bug-intake/BugIntakeView.tsx + src/renderer/src/features/bug-intake/BugIntakeSourceForm.tsx
- Shared types + Zod schemas: src/main/services/intake/intake-types.ts —
sentryConfigSchema,githubConfigSchema, every IPC schema, and the renderer-facing DTOs (ProjectIntakeSourceDto,IntakeAuditRow) - Sentry HTTP client (shared with RepoGuard plugin): src/main/services/intake/intake-sentry-client.ts —
fetchUnresolvedSentryIssues,testSentryConnection - Sentry service (poll + dedupe + spawn + manual spawn): src/main/services/intake/intake-sentry-service.ts —
pollSentrySource,manuallySpawnSentryIntake - Event flattener: src/main/services/intake/intake-sentry-flatten.ts —
flattenSentryEventForPrompt,buildSentrySessionName - Poll scheduler: src/main/services/intake/intake-poll-scheduler.ts —
IntakePollSchedulerclass,initIntakePollScheduler,refreshIntakePollScheduler, 5-min cadence + jitter, per-source mutex, 24h escalation - Durable broken-source alert: src/main/services/intake/intake-source-alert.ts (
maybeRaiseIntakeSourceAlert/clearIntakeSourceAlert/reconcileIntakeSourceAlerts,INTAKE_SOURCE_ALERT_THRESHOLD_MS) + shared dedupKey src/shared/alert-features/intake-source-alert.ts +listActiveByDedupKeyPrefixin queries-inbox-alerts.ts; wired into the scheduler tick (raise on fail / clear on success / reconcile on start). Contract: intake-source-alert-contract.md - Shared spawn entrypoint (email + Sentry): src/main/services/intake/intake-spawn.ts —
spawnIntakeSession(acceptsimages?: StdinImageInput[], forwarded toprocessManager.launch) - In-app feedback screenshots → investigator: writer embeds Vision images inline + budget-gated via
selectScreenshotsForFirestore/MAX_DOC_SCREENSHOT_BASE64_BYTESin firestore-bug-report-client.ts; the operator-side listener mapsdoc.screenshots→StdinImageInput[]and threads them throughspawnIntakeSession(fixing session only, never the email liaison) in firestore-bug-report-listener.ts.writer-embeds-inline-budget-gated-vision-only→spawn-threads-images-to-the-cliin intake-dedup-contract.md - Operator-poll stuck alarm: bug-report-operator-alert.ts (
maybeRaiseOperatorPollAlert/clearOperatorPollAlert/operatorPollFailingMs,OPERATOR_POLL_ALERT_THRESHOLD_MS, fixed keybug-report-operator-poll-broken); wired into firestore-bug-report-listener.tspollOnce/startPolling/stopPolling/init(raise on a failed poll, clear on success / operator-off). Invariantoperator-poll-broken-alertin operator-console-relay-contract.md; tests bug-report-operator-alert.test.ts - Email router (legacy): src/main/services/bug-report-router.ts —
parseBugIntakeSubject,sanitizeAndTruncateBody,onIncomingEmailPostPrescreen - Gmail intake (poller + reply-back): src/main/services/email/gmail-bug-intake-poller.ts and src/main/services/email/gmail-bug-intake-completion.ts; the
email_inbound_tracking.sourcechannel column comes from the dated ledger migration 20260610063703-add-source-column-to-email-inbound-tracking.ts - Retention sweep: src/main/services/intake/intake-retention-sweeper.ts — 90-day periodic task, pausable via service registry
- IPC handlers (10 channels, all Zod-validated): src/main/ipc/intake-handlers.ts
- IPC channels:
INTAKE_SOURCE_LIST / CREATE / UPDATE / DELETE,INTAKE_TEST_CONNECTION,INTAKE_POLL_NOW,INTAKE_AUDIT_LIST,INTAKE_PENDING_LIST,INTAKE_MANUAL_SPAWN,INTAKE_DISMISSplus push channelsINTAKE_SOURCE_UPDATED,INTAKE_SOURCE_ERROR_ESCALATIONin src/shared/ipc-channels/index.ts (defined in thediagnostics.tsdomain map) - Schema: migrations
v112(bug_intake_processed) andv222(project_intake_sources+ extra columnssource,external_id,external_url,severity,prior_event_idonbug_intake_processed) in src/main/db/database.ts; queries in src/main/db/queries-bug-intake.ts and src/main/db/queries-intake-sources.ts - Email-only:
PROJECT_UPDATE_BUG_INTAKE_SLUGIPC + matching CLI route, settings UI panel in src/renderer/src/features/settings/BugIntakeSettings.tsx mounted inside the project edit dialog - Email intake rules (sender + slug → repo): table via ledger migration 20260530151130-email-intake-rules.ts; queries queries-email-intake-rules.ts (
findProjectByEmailRulefor routing,listEnabledRuleSendersfor admission, CRUD); rule lookup ahead of the slug lookup in bug-report-router.ts; additive admission via theruleSendersparam offilterInboundMessagesin email-inbound-service.ts; IPC email-intake-rule-handlers.ts (EMAIL_INTAKE_RULE_LIST / CREATE / UPDATE / DELETE+ pushEMAIL_INTAKE_RULES_UPDATED); UI EmailIntakeRulesSection.tsx in the Sources tab; contract .claude/memory/contracts/email-intake-rules-contract.md (14 test-locked invariants) - Built-in investigation steps (shared across all four surfaces):
BUG_INTAKE_INVESTIGATION_STEPSin src/main/services/bug/bug-intake-prompt.ts — the "What to do" block (first analyze and report back a plain-English, markdown-formatted explanation of the item + recommended next steps; then a type-aware stop — a bug report carries the owner's standing go-ahead and continues, a feature request / question / feedback waits for it; drive the work with/dev-pipelinewhen installed — and when it is NOT, keep the plan checkpoint (investigate, report findings and a plan, wait before touching code), because the pipeline's PLAN gate is the only thing that judges an intake plan and the skill is default-OFF; report in plain English with short summaries; an error that is not ours closes with a one-line report rather than a question), spread into emailbuildBugIntakePrompt, SentryflattenSentryEventForPrompt, GitHubflattenGithubIssueForPrompt, and in-appbuildAgentPrompt. Each surface's intro framing stays per-source; only these steps are shared (so they can't drift). Locked by the shared-constant test + per-surface/dev-pipelineassertions inbug-intake-prompt.test.ts/intake-sentry-flatten.test.ts/intake-github-flatten.test.ts/firestore-bug-report-listener.test.ts - The intake defect's PLAN gate is answered without a person: the dev pipeline's GATE_1 prose park is decided by the autofix rubric the help desk uses — helpdesk-plan-autofix.ts
beginIntakePlanAutofix(readreadIntakePlanRow; eligibility viaisProseGatePlanRow), probed throughpipelineAutoAdvance.planGateAwaitingand approved throughapprovePlanGatein pipeline-auto-advance-runner.ts, wired at the turn-complete seam in auto-approve-or-auto-run.ts. One owner per gate: GATE_1 belongs to this arm for ANY eligible defect row — a help-desk ticket's included, because a ticket investigation runs the same pipeline prompt and the ticket arm only ever answersplan_approval— andplan_approvalstays the ticket arm's, so a session parked at one gate can never be judged by both. Eligibility requires the recorded kind to bebug, and excludes GitHub, whose every claim is written withreport_type: 'bug'whatever the issue says, so on that public tracker the recorded kind is a constant. The arm also DECLINES when the blanket per-gate Plan toggle is on, sincepipelineGateReconcilerwould advance the gate ~4 minutes later and make a hold message false. A held plan still lands atneeds_youwith the deciding rule named. Governed by its ownhelpdeskPlanAutopilotswitch, which can never reach a reporter. Locked bytests/unit/services/helpdesk/intake-plan-autofix.test.tsandtests/unit/lint/helpdesk-plan-switch-isolation.test.ts - Appended instructions (global + per-project): composer
buildIntakeAppendInstructionsBlockin src/main/services/bug-intake-prompt.ts, spliced above the fence in all three builders (emailbuildBugIntakePrompt, SentryflattenSentryEventForPrompt, in-appbuildAgentPromptin firestore-bug-report-listener.ts). Global value =bugIntakeAppendPromptAppSettings field (textarea in BugIntakeView.tsx); per-project value =projects.bug_intake_append_promptcolumn (added by a dated ledger migration undersrc/main/db/migrations/), written viaPROJECT_UPDATE_BUG_INTAKE_PROMPT→handleProjectUpdateBugIntakeAppendPromptin project-bug-intake-service.ts, textarea in BugIntakeSettings.tsx. Shared length capBUG_INTAKE_APPEND_PROMPT_MAXin src/shared/bug-intake-constants.ts. Per-project audit eventproject_bug_intake_prompt_updated(promptProvided boolean only, never the text) - Lint test: tests/unit/lint/intake-source-validation.test.ts — pins the 10-channel Zod contract + the renderer / server regex parity
- Analytics registration: src/shared/feature-registry/intake-and-ai-manager.ts —
bug_intake_routed,bug_intake_dedup_hint_emitted,bug_intake_self_dedup
Duplicate detection (similarity dedup)
- Signatures + 24h window: src/main/services/intake/intake-dedup.ts —
computeBugSignature,computeSentrySignature,normalizeComponent,INTAKE_DEDUP_WINDOW_HOURS = 24,SIGNATURE_MAX_LEN = 256(pure functions, no DB) - Layer-1 hint lookup:
findRecentSimilarIntakein src/main/db/queries-bug-intake.ts — same source + signature + project, within 24h,decision='routed'+ live non-archived session only - Layer-1 wiring (kept parallel): email computes/looks-up/emits in bug-report-router.ts (gate read in email-inbound-service.ts); Sentry mirrors it in intake-sentry-service.ts (gate read in intake-poll-scheduler.ts)
- Hint threading into the spawn prompt: src/main/services/bug-intake-prompt.ts
- Layer-2 decision service: src/main/services/intake/intake-mark-duplicate.ts —
markIntakeAsDuplicate, all guards + the note-then-archive ordering - Agent HTTP route:
POST /agent/intake/duplicate-ofviaregisterIntakeAgentRoutesin src/main/services/cli/cli-server-intake-routes.ts (registered unconditionally; behavior gated upstream by the toggle) - Toggle:
intakeDedupEnabled(defaulttrue) — header switchdata-setting-id="intake-dedup-enabled"in BugIntakeView.tsx - Schema: migration
v257addsbug_intake_processed.signature+idx_bug_intake_processed_signature_created(signature, created_at)in src/main/db/database.ts - Contract (agents with repo access): .claude/memory/contracts/intake-dedup-contract.md — 19 test-locked invariants + safe-change checklist
Related
The rest of the page is on part 1 (overview, sidebar panel and email), part 2 (Gmail, Sentry and GitHub sources) and part 3 (what a session receives, duplicate collapsing and safety). Related features: inbox alerts for the durable cards intake raises, Agent email for the mail channel, and Repoguard, which shares the same Sentry reading code for its own scanner.
Last verified 2026-10-04