Omniscio documentation
Browse all documentation
  1. Getting Started13
  2. Sessions & Agents115
  3. Inbox & Notifications59
  4. Projects & Tasks95
  5. Automation & Scheduling75
  6. Knowledge & Memory26
  7. AI Features60
  8. Integrations100
  9. Plugins & Marketplace33
  10. Cloud & Teams56
  11. Settings & Customization58
  12. Account & Billing28
  13. Troubleshooting84
  14. CLI & API Reference22
  15. Legal & Policies4
  16. Uncategorised22

Bug Report Intake (part 3)

Part 3 of the Bug Report Intake page: what an auto-spawned investigation actually receives — the fenced report, your appended instructions, email attachments and in-app screenshots — how a reply threads back to its session, how repeat reports of one issue collapse into a single investigation, and the safety guarantees behind all of it.

What it is

This is part 3 of the Bug Report Intake page. It covers the other half of intake — what actually lands in an auto-spawned investigation (the report fence, your own appended instructions, email attachments and in-app screenshots), how a follow-up reply threads back to the session it belongs to, how repeat reports of the same issue collapse into one investigation, and the safety guarantees that hold across every channel.

Where to find it

There is nothing new to open — the settings this part describes live in places you already have. The global appended-instructions box sits in the Bug Intake panel above its tabs, the per-project one is in that project's Edit Project dialog, the duplicate-detection switch is in the Bug Intake panel header beside Pause all polling, and screenshots are attached from the in-app Send feedback dialog.

How it behaves

What goes into a spawned session (email, Sentry, and GitHub)

When intake spawns a session, two things land in it:

  1. An operator chat row — what the operator sees in the conversation log. For an email-routed session it's From: / Subject: / Attachments: plus the fenced body. For a Sentry-routed session it's the flattened event payload (Title, Level, Culprit, First seen, Last seen, Count, Permalink).
  2. An initial prompt to the agent — the same envelope + fenced body, prefixed by a short trusted-instruction preamble: the fenced content is untrusted external input, then a shared "What to do" block (BUG_INTAKE_INVESTIGATION_STEPS, the one copy all four surfaces splice) telling the agent to take the report at face value — for a bug report, the reported error is real and the job is to find and fix its true cause; for a feature request, work out what the reporter actually needs; for a question or feedback item, find the accurate answer, or the change it points to; then analyze the item and report back a plain-English, markdown-formatted explanation of what's being reported or requested plus its recommended next steps, and then — type-aware, and this is the load-bearing part — a bug report carries your standing go-ahead, so it carries the fix through the same turn and the only thing that can bring you back in is the dev pipeline's plan gate (which the autofix rubric answers, holding anything guarded), while a feature request, question or feedback STOPS there and waits for your go-ahead, because a request for new behaviour is the owner's call; the work is then driven by the /dev-pipeline skill when it's installed — otherwise investigate the same way but keep the plan checkpoint (report findings and a plan, and wait), because the skill is opt-in / default-off and its PLAN gate is the only thing that produces a judged plan — reporting in plain English with short summaries. An error that turns out not to be ours closes with a one-line report, never a question.

    Until 2026-10-03 one clause — "Wait for my go-ahead before you proceed" — applied to all four kinds at once. Every [SENTRY] session off that morning's restart obeyed it literally and ended its turn asking whether to fix the crash it had just diagnosed; the type-aware split above is what replaced it.

Both rows go through stripIntakeMarkers() first — if the tester's body itself contains a line consisting only of BEGIN-USER-REPORT or END-USER-REPORT, that line is removed so an attacker can't inject a false closing fence. Inline mentions of those tokens inside prose remain visible (harmless data).

Session rows are stamped source = 'bug_intake' (email), source = 'sentry_intake' (Sentry), or source = 'github_intake' (GitHub) so they're distinguishable from manually-started sessions, and silent = 0 so they're fully visible in the sidebar and inbox.

Appended investigation instructions (global + per-project)

You can append your own free-form instructions to the investigation prompt every bug report receives — without touching code. Two independent text boxes stack:

  • Global: one box in the Bug Intake sidebar view (above the Sources / Review / Audit tabs). Its text is appended to every auto-triaged report across all projects and all three sources (email, Sentry, in-app feedback).
  • Per-project — one box per project, in Edit Project → Appended investigation instructions. Its text is appended only to reports routed to that project.

When a report for project X is triaged, the agent prompt is assembled as: the fixed investigation framing → the numbered "What to do" steps → your global text, then project X's text (labeled Additional operator instructions:) → the untrusted BEGIN-USER-REPORT / END-USER-REPORT fence holding the report. Both layers are optional; leave them blank and the prompt is byte-for-byte identical to before the feature existed.

Why it's placed where it is. Your appended text is operator-authored (you typed it into Omniscio's own UI), so it is trusted — it sits above the report fence, in the same region as the built-in instructions. The reporter's words stay inside the untrusted fence exactly as before, so the prompt-injection protection is unchanged. As defense-in-depth, a stray BEGIN-USER-REPORT / END-USER-REPORT marker line you happen to type is stripped from your text so it can't forge a second fence.

Each box is capped at 10,000 characters (BUG_INTAKE_APPEND_PROMPT_MAX). The global value lives in the bugIntakeAppendPrompt AppSettings field; the per-project value in the projects.bug_intake_append_prompt column (sibling of bug_intake_slug, added by a dated ledger migration under src/main/db/migrations/). The per-project box is independent of the email-slug toggle — it applies to a project's Sentry and in-app reports even when email intake is off for that project.

Attachments (email only)

If the tester's email carries attachments, Omniscio processes them through the same 4-route delivery pipeline used by the catchall inbound-email sessions (processEmailAttachments). Each attachment is routed by content type:

Route Types Delivery into the session
Image image/png, image/jpeg, image/gif, image/webp Anthropic Vision image content block on the first stdin message (base64); the agent can "see" the screenshot directly
PDF application/pdf Native Vision document content block AND saved to <workdir>/.claude/amc-attachments/<sessionId>/<filename> so the agent can also Read / edit the file
Office doc .docx, .xlsx, .pptx Text extracted via office-text-extract.ts, saved to workdir as <basename>.md, and the path is injected into the prompt
Text doc .md, .txt, .csv, .json, .html, .xml, .tsv, .log, .markdown Saved verbatim to the session workdir; path injected into the prompt via buildPromptWithAttachments

Skipped attachments (oversize, unsupported format, office-extract failure, encrypted PDF) are NOT dropped silently — they render as a Note: N attachment(s) could not be included block outside the BEGIN/END fence in both the operator chat row and the agent prompt, so the diagnostic is visible but structurally separated from sender-controlled fenced content. Per-file caps mirror the in-app attachment rules: 30 MB per image, 32 MB per document. No count cap.

Sentry intake has no attachment pipeline — the Sentry event is the entire payload.

In-app feedback screenshots (reach the investigator)

The in-app feedback dialog (the Send feedback button) lets a reporter attach up to 5 screenshots. Those already travel to the Resend email and the report database (Firestore); they now also reach the auto-spawned investigator so the agent can see the bug, not just read about it. (Before, an in-app report spawned an agent that was structurally blind to the attached screenshot — e.g. a "Screenshot for ticket …" report with nothing for the agent to look at.)

The in-app path is dual-written (Resend email + a Firestore mirror doc); the Firestore leg is what spawns the investigation. Because a Firestore document is capped at 1 MB, screenshots ride inline (base64) through a budget gate:

  • Only the four Anthropic-Vision image formats (png, jpeg, gif, webp) are forwarded — each becomes an image content block on the agent's first message, exactly like the email Image route above.
  • The total embedded base64 is capped (~700 KB, headroom under the 1 MB doc limit). Screenshots that don't fit, or aren't a Vision format, are dropped from this leg only and the agent prompt notes how many — the email copy still carries the full-resolution originals.

Unlike the email pipeline, the in-app path forwards images only (no PDF / Office / text-doc routes) — it's screenshots-for-investigation, not a general attachment channel. The screenshots ride inline rather than through any shared/public storage bucket, since screen captures can be sensitive.

The operator pickup is monitored — a durable alarm when it stalls (2026-07-06)

An in-app report only becomes a session because the operator install (the one flagged amcBugReportOperator: true, normally the operator's) polls the amc_bug_reports relay every 15s and spawns per new doc. If that poll can't reach the relay (a signed-out operator, a cloud/relay outage), the tick was previously only a rate-limited WARN — invisible — so a sustained outage would silently strand every reporter's doc in the cloud, uncollected.

A durable, self-clearing inbox card now closes that gap. Once the poll has been failing continuously past 5 minutes (measured from the last success, or this run's poll-start if it has never succeeded this run), one card is raised — "Bug Intake: in-app reports aren't being picked up" — on desktop, mobile, and the badge (a DB row, so it survives a restart). It is raised once (guarded by findActiveByDedupKey on the fixed key bug-report-operator-poll-broken, never re-raised each tick) and auto-clears on the next successful poll or when the operator flag is turned off. Gated by the AMC_DISABLE_OPERATOR_POLL_ALERT env kill switch, and bulletproof (any error → no-op, never breaks a poll tick). It is the single-poll sibling of the per-source Sentry/GitHub "source broken" card documented above. Producer: src/main/services/bug/bug-report-operator-alert.ts; invariant operator-poll-broken-alert in operator-console-relay-contract.md.

Replies thread back to the same email-routed session

When a routed email spawns a session, Omniscio inserts an email_inbound_tracking row mapping the AgentMail agentmail_message_id → spawned session_id (plus thread_id when threaded). The insert lives inside the same SQL transaction as the session-row insert, so a concurrent inbound poll racing ahead trips the PRIMARY KEY on agentmail_message_id and rolls the spawn back rather than creating an orphan.

A subsequent reply on the same thread then:

  • Has subject Re: [BUG: amc] ..., which fails the strict ^\[ anchor in the bug-intake parser — so it is not spawn-routed again
  • Falls through to the normal email-inbound flow, which finds the thread in email_inbound_tracking and appends the reply as a regular operator message in the existing session

Sentry has no analog — each Sentry issue maps to one intake row by external_id, and the dedupe layer guarantees one-and-only-one session per issue.

Duplicate detection — one investigation per underlying issue (24h)

A bug can land more than once: a tester emails the same complaint twice, or Sentry fires the same crash from two devices within minutes. Left alone, each arrival spawns its own investigator — two agents chewing the same issue, burning tokens and cluttering the inbox. Intake dedup collapses the same underlying issue, reported again within 24 hours, into a single investigation. Three lines of defense run in order, each catching what the one before it cannot:

1. Exact-id dedup (always on, never gated)

If the same external id arrives twice — same AgentMail agentmail_message_id (email), same Sentry issue.id (Sentry) — it is collapsed at the bug_intake_processed PRIMARY KEY before anything else runs (the INSERT OR IGNORE / tryClaimIntakeDedupe() described under Safety guarantees below). This is the front line and is never affected by the toggle. The two layers below exist only to catch the harder different-id, same-content case that exact-id matching can't see.

2. Layer 1 — the hint (intake-side, never archives)

Before spawning, intake computes a cheap structural signature of the incoming report — normalized sender + subject for email, culprit + title for Sentry (never the body, so nothing sensitive is stored or indexed). If a routed intake from the same source and same project carried the same signature within the last 24 hours and its session is still live, the new spawn's prompt is prepended with a one-line hint: "this looks like session X — compare the two reports before investigating."

Layer 1 only hints. It never archives, never blocks the spawn, never decides anything on its own. A false-positive signature collision costs exactly one extra sentence in a prompt — so the match is deliberately cheap and the cost of being wrong is near zero.

3. Layer 2 — the decision (agent-side, archives)

The spawned investigator reads the actual bodies of both reports (its own and the hinted one). Only if it confirms a true duplicate does it act — and only then does anything get archived:

  • The current (duplicate) session is archived automatically.
  • The original session gets a "Same issue reported again …" note in its conversation log — with a timestamp, the source, and a link to the duplicate — so the breadcrumb is visible to you.

Net effect: you see one live investigation, plus a note on the original telling you it came in again. Guards protect this archive-on-behalf-of path: the calling session must itself be a fresh (under 1h) intake-spawned session, it can't archive itself, the original must still exist, and two agents that hint at each other can't archive each other in a tie. The note is always posted before the archive, so if the archive ever fails the duplicate stays alive with the trail intact rather than vanishing silently.

The "Dedup pre-check" toggle

The Bug Intake view header carries a Dedup pre-check toggle (next to Pause all polling), bound to the intakeDedupEnabled setting (on by default). When off, no signature is computed and no hint is ever added to a spawn — Layers 1 and 2 go inert. Exact-id dedup (line 1 above) keeps running regardless; the toggle governs only the two similarity layers.

Safety guarantees

Both intake paths share these:

  • Manual spawn is one click. A human-initiated spawn of a paid investigation — the Review tab's Spawn now and the inbox detail pane's Spawn (the new-session icon button — a click or the N key) — starts the billable session immediately, with no confirmation dialog. A duplicate still can't burn extra tokens: the inbox pane's in-flight guard collapses a fast double-press to one spawn, and the backend is idempotent by external id (bug_intake_processed, INSERT OR IGNORE), so a repeat can never start a SECOND billable session. (Auto-spawn from a polled match is governed by the triage gate + caps.)
  • Untrusted-data framing. The agent prompt wraps the external payload in BEGIN-USER-REPORT / END-USER-REPORT markers and explicitly tells the agent the content is untrusted external input
  • Fence-marker stripping. stripIntakeMarkers() removes whole-line occurrences of those marker tokens from the source body before fencing
  • Idempotency by external ID. Each AgentMail agentmail_message_id (email) or Sentry issue.id (Sentry) is stamped into bug_intake_processed exactly once via INSERT OR IGNORE; restarts, parallel polls, and replays cannot spawn a session twice
  • Orphan cleanup on launch failure. If the session row is created but the CLI process fails to launch, the session is soft-deleted (is_deleted = 1) so no ghost row remains in the sidebar
  • 90-day retention sweep. intake-retention-sweeper.ts (registered with the pausable service registry, fires 10 seconds after launch and every 6 hours after) purges bug_intake_processed rows older than 90 days. Long enough to investigate a regression that surfaced weeks after the spawning event, short enough that the Audit tab stays scannable. Audit history is preserved across source deletion (no CASCADE from project_intake_sources → bug_intake_processed), so deleting a source intentionally keeps its decision history visible until retention expires it.

Email-only:

  • Prescreen first. The email router runs ONLY after the inbound prescreen returns safe: true — prompt-injection emails are blocked upstream
  • Strict subject regex. [a-z0-9-]{1,64} rejects unicode, traversal characters, and oversize values
  • 30-second cooldown per slug. Back-to-back emails referencing the same slug get the second tagged cooldown rather than spawning a parallel session
  • Daily cap per project. Default 50 routed emails per project per day; configurable via bugIntakeDailyCap

Sentry-only:

  • First-connect cap of 5. A brand-new source's first poll spawns at most 5 sessions; the rest are deferred to the Review tab (recorded as unrouted / pending review) so a misconfigured org doesn't carpet-bomb your inbox
  • Per-source mutex. The scheduler holds an in-memory mutex per source so overlapping ticks short-circuit cleanly (manual "Poll now" while a scheduled tick is mid-flight)
  • 24h escalation push. Continuous-failure beyond a day fires a single intake:source-error-escalation push the view surfaces as a red badge
  • Durable broken-source inbox card. Past ~1h of continuous failure a persistent, self-clearing inbox card is raised per source (desktop + mobile + badge, survives restart, auto-clears on recovery / disable / delete) — see "Durable 'source broken' inbox card" above
  • IPC + service-layer Zod re-parse. Every handler that touches config_json (CREATE, UPDATE, TEST_CONNECTION) re-validates the parsed JSON through sentryConfigSchema before the DB write or outbound HTTP. The renderer form mirrors the same [a-z0-9_-]{1,64} regex client-side, but client validation can be bypassed via a direct IPC call — never trust the renderer. Enforced by tests/unit/lint/intake-source-validation.test.ts

Related

Part 1 is the overview and the email channel; part 2 covers Gmail, Sentry and GitHub source setup; part 4 covers the routing decision table, analytics, out-of-scope notes and the code map. For the alert card raised when in-app reports stop being collected, see inbox alerts, and for the mail channel the reply-back path rides on, see Agent email.

Last verified 2026-10-04