---
title: Bug Report Intake (part 4)
---

# Bug Report Intake (part 4)

## What it is

This is part 4 of the [Bug Report Intake](bug-report-intake.md) 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](bug-report-intake.md) open for the **Bug Intake** sidebar row, [part 2](bug-report-intake-part-2.md) for the **Sources** tab, and [part 3](bug-report-intake-part-3.md) 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](../../src/shared/virtual-project-ids.ts)
- **View / form:** [src/renderer/src/features/bug-intake/BugIntakeView.tsx](../../src/renderer/src/features/bug-intake/BugIntakeView.tsx) + [src/renderer/src/features/bug-intake/BugIntakeSourceForm.tsx](../../src/renderer/src/features/bug-intake/BugIntakeSourceForm.tsx)
- **Shared types + Zod schemas:** [src/main/services/intake/intake-types.ts](../../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](../../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](../../src/main/services/intake/intake-sentry-service.ts) — `pollSentrySource`, `manuallySpawnSentryIntake`
- **Event flattener:** [src/main/services/intake/intake-sentry-flatten.ts](../../src/main/services/intake/intake-sentry-flatten.ts) — `flattenSentryEventForPrompt`, `buildSentrySessionName`
- **Poll scheduler:** [src/main/services/intake/intake-poll-scheduler.ts](../../src/main/services/intake/intake-poll-scheduler.ts) — `IntakePollScheduler` class, `initIntakePollScheduler`, `refreshIntakePollScheduler`, 5-min cadence + jitter, per-source mutex, 24h escalation
- **Durable broken-source alert:** [src/main/services/intake/intake-source-alert.ts](../../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](../../src/shared/alert-features/intake-source-alert.ts) + `listActiveByDedupKeyPrefix` in [queries-inbox-alerts.ts](../../src/main/db/queries-inbox-alerts.ts); wired into the scheduler tick (raise on fail / clear on success / reconcile on start). Contract: [intake-source-alert-contract.md](../../.claude/memory/contracts/intake-source-alert-contract.md)
- **Shared spawn entrypoint (email + Sentry):** [src/main/services/intake/intake-spawn.ts](../../src/main/services/intake/intake-spawn.ts) — `spawnIntakeSession` (accepts `images?: StdinImageInput[]`, forwarded to `processManager.launch`)
- **In-app feedback screenshots → investigator:** writer embeds Vision images inline + budget-gated via `selectScreenshotsForFirestore` / `MAX_DOC_SCREENSHOT_BASE64_BYTES` in [firestore-bug-report-client.ts](../../src/main/services/firestore-bug-report-client.ts); the operator-side listener maps `doc.screenshots` → `StdinImageInput[]` and threads them through `spawnIntakeSession` (fixing session only, never the email liaison) in [firestore-bug-report-listener.ts](../../src/main/services/firestore-bug-report-listener.ts). `writer-embeds-inline-budget-gated-vision-only` → `spawn-threads-images-to-the-cli` in [intake-dedup-contract.md](../../.claude/memory/contracts/intake-dedup-contract.md)
- **Operator-poll stuck alarm:** [bug-report-operator-alert.ts](../../src/main/services/bug/bug-report-operator-alert.ts) (`maybeRaiseOperatorPollAlert` / `clearOperatorPollAlert` / `operatorPollFailingMs`, `OPERATOR_POLL_ALERT_THRESHOLD_MS`, fixed key `bug-report-operator-poll-broken`); wired into [firestore-bug-report-listener.ts](../../src/main/services/firestore-bug-report-listener.ts) `pollOnce` / `startPolling` / `stopPolling` / `init` (raise on a failed poll, clear on success / operator-off). Invariant `operator-poll-broken-alert` in [operator-console-relay-contract.md](../../.claude/memory/contracts/operator-console-relay-contract.md); tests [bug-report-operator-alert.test.ts](../../tests/unit/services/bug-report-operator-alert.test.ts)
- **Email router (legacy):** [src/main/services/bug-report-router.ts](../../src/main/services/bug/bug-report-router.ts) — `parseBugIntakeSubject`, `sanitizeAndTruncateBody`, `onIncomingEmailPostPrescreen`
- **Gmail intake (poller + reply-back):** [src/main/services/email/gmail-bug-intake-poller.ts](../../src/main/services/email/gmail-bug-intake-poller.ts) and [src/main/services/email/gmail-bug-intake-completion.ts](../../src/main/services/email/gmail-bug-intake-completion.ts); the `email_inbound_tracking.source` channel column comes from the dated ledger migration [20260610063703-add-source-column-to-email-inbound-tracking.ts](../../src/main/db/migrations/20260610063703-add-source-column-to-email-inbound-tracking.ts)
- **Retention sweep:** [src/main/services/intake/intake-retention-sweeper.ts](../../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](../../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_DISMISS` plus push channels `INTAKE_SOURCE_UPDATED`, `INTAKE_SOURCE_ERROR_ESCALATION` in [src/shared/ipc-channels/index.ts](../../src/shared/ipc-channels/index.ts) (defined in the `diagnostics.ts` domain map)
- **Schema:** migrations `v112` (`bug_intake_processed`) and `v222` (`project_intake_sources` + extra columns `source`, `external_id`, `external_url`, `severity`, `prior_event_id` on `bug_intake_processed`) in [src/main/db/database.ts](../../src/main/db/database.ts); queries in [src/main/db/queries-bug-intake.ts](../../src/main/db/queries-bug-intake.ts) and [src/main/db/queries-intake-sources.ts](../../src/main/db/queries-intake-sources.ts)
- **Email-only:** `PROJECT_UPDATE_BUG_INTAKE_SLUG` IPC + matching CLI route, settings UI panel in [src/renderer/src/features/settings/BugIntakeSettings.tsx](../../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](../../src/main/db/migrations/20260530151130-email-intake-rules.ts); queries [queries-email-intake-rules.ts](../../src/main/db/queries-email-intake-rules.ts) (`findProjectByEmailRule` for routing, `listEnabledRuleSenders` for admission, CRUD); rule lookup ahead of the slug lookup in [bug-report-router.ts](../../src/main/services/bug/bug-report-router.ts); additive admission via the `ruleSenders` param of `filterInboundMessages` in [email-inbound-service.ts](../../src/main/services/email/email-inbound-service.ts); IPC [email-intake-rule-handlers.ts](../../src/main/ipc/email-intake-rule-handlers.ts) (`EMAIL_INTAKE_RULE_LIST / CREATE / UPDATE / DELETE` + push `EMAIL_INTAKE_RULES_UPDATED`); UI [EmailIntakeRulesSection.tsx](../../src/renderer/src/features/bug-intake/EmailIntakeRulesSection.tsx) in the Sources tab; contract [.claude/memory/contracts/email-intake-rules-contract.md](../../.claude/memory/contracts/email-intake-rules-contract.md) (14 test-locked invariants)
- **Built-in investigation steps (shared across all four surfaces):** `BUG_INTAKE_INVESTIGATION_STEPS` in [src/main/services/bug-intake-prompt.ts](../../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, waiting for the owner's go-ahead before any work; then drive the work with `/dev-pipeline` when installed, with a default-off fallback; report in plain English with short summaries), spread into email `buildBugIntakePrompt`, Sentry `flattenSentryEventForPrompt`, GitHub `flattenGithubIssueForPrompt`, and in-app `buildAgentPrompt`. 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-pipeline` assertions in `bug-intake-prompt.test.ts` / `intake-sentry-flatten.test.ts` / `intake-github-flatten.test.ts` / `firestore-bug-report-listener.test.ts`
- **Appended instructions (global + per-project):** composer `buildIntakeAppendInstructionsBlock` in [src/main/services/bug-intake-prompt.ts](../../src/main/services/bug/bug-intake-prompt.ts), spliced above the fence in all three builders (email `buildBugIntakePrompt`, Sentry `flattenSentryEventForPrompt`, in-app `buildAgentPrompt` in [firestore-bug-report-listener.ts](../../src/main/services/firestore-bug-report-listener.ts)). Global value = `bugIntakeAppendPrompt` AppSettings field (textarea in [BugIntakeView.tsx](../../src/renderer/src/features/bug-intake/BugIntakeView.tsx)); per-project value = `projects.bug_intake_append_prompt` column (added by a dated ledger migration under `src/main/db/migrations/`), written via `PROJECT_UPDATE_BUG_INTAKE_PROMPT` → `handleProjectUpdateBugIntakeAppendPrompt` in [project-bug-intake-service.ts](../../src/main/services/project-bug-intake-service.ts), textarea in [BugIntakeSettings.tsx](../../src/renderer/src/features/settings/BugIntakeSettings.tsx). Shared length cap `BUG_INTAKE_APPEND_PROMPT_MAX` in [src/shared/bug-intake-constants.ts](../../src/shared/bug-intake-constants.ts). Per-project audit event `project_bug_intake_prompt_updated` (promptProvided boolean only, never the text)
- **Lint test:** [tests/unit/lint/intake-source-validation.test.ts](../../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](../../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](../../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:** `findRecentSimilarIntake` in [src/main/db/queries-bug-intake.ts](../../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](../../src/main/services/bug/bug-report-router.ts) (gate read in [email-inbound-service.ts](../../src/main/services/email/email-inbound-service.ts)); Sentry mirrors it in [intake-sentry-service.ts](../../src/main/services/intake/intake-sentry-service.ts) (gate read in [intake-poll-scheduler.ts](../../src/main/services/intake/intake-poll-scheduler.ts))
- **Hint threading into the spawn prompt:** [src/main/services/bug-intake-prompt.ts](../../src/main/services/bug/bug-intake-prompt.ts)
- **Layer-2 decision service:** [src/main/services/intake/intake-mark-duplicate.ts](../../src/main/services/intake/intake-mark-duplicate.ts) — `markIntakeAsDuplicate`, all guards + the note-then-archive ordering
- **Agent HTTP route:** `POST /agent/intake/duplicate-of` via `registerIntakeAgentRoutes` in [src/main/services/cli/cli-server-intake-routes.ts](../../src/main/services/cli/cli-server-intake-routes.ts) (registered unconditionally; behavior gated upstream by the toggle)
- **Toggle:** `intakeDedupEnabled` (default `true`) — header switch `data-setting-id="intake-dedup-enabled"` in [BugIntakeView.tsx](../../src/renderer/src/features/bug-intake/BugIntakeView.tsx)
- **Schema:** migration `v257` adds `bug_intake_processed.signature` + `idx_bug_intake_processed_signature_created(signature, created_at)` in [src/main/db/database.ts](../../src/main/db/database.ts)
- **Contract (agents with repo access):** [.claude/memory/contracts/intake-dedup-contract.md](../../.claude/memory/contracts/intake-dedup-contract.md) — 19 test-locked invariants + safe-change checklist

## Related

The rest of the page is on [part 1](bug-report-intake.md) (overview, sidebar panel and email), [part 2](bug-report-intake-part-2.md) (Gmail, Sentry and GitHub sources) and [part 3](bug-report-intake-part-3.md) (what a session receives, duplicate collapsing and safety). Related features: [inbox alerts](inbox-alerts.md) for the durable cards intake raises, [Agent email](agent-email.md) for the mail channel, and [Repoguard](repoguard.md), which shares the same Sentry reading code for its own scanner.
