# How your agent hears about bugs

BugTape hands a coding agent the evidence for a real failure: replay, network, console, stack and affected users. **Verified Close** adds a credential-separated assertion to the captured occurrence. Trusted runner promotion is the measured path that can set `verified_fixed`.

This page answers two integrator questions: **how does my agent find out that something broke?** and **how does a fixer hand the same evidence to a separate verifier credential without self-closing?**

There are three wake-up paths. Pick by how your agent runs.

| Your agent is… | Use | How it learns | Latency | Needs |
|---|---|---|---|---|
| **Interactive** — Claude Code, Cursor, Codex, an MCP client with a live session | **MCP `listen` / `resources/subscribe`** | Blocks on the project inbox or a bug's status until something changes, with durable Tuesday-queue `afterCursor` catch-up | ~1 s | Pro plan, agent PAT |
| **Headless automation** — a worker, a queue, a Cursor Automation, a cron box you own | **Signed webhook** (Alerts → Agent destination) | BugTape POSTs each event to your HTTPS endpoint, HMAC-signed, retried 4× with a dead-letter log | seconds | Any plan with integrations |
| **Periodic** — a nightly job, a script, a spreadsheet | **Poll `GET /v1/bugs/recommended?since=`** | Ask for issues recommended since your last run; `ack` what you took | your cadence | Agent PAT |

All three carry the same identifiers — `bugId`, `occurrenceId`, `platform`, `userId` — so whichever path wakes the agent, the next call is the same: `get_repro_context`.

## 1. Interactive agents: MCP `listen`

Connect the BugTape MCP server (Settings → Agents mints a token and the config snippet):

```json
{ "mcpServers": { "bugtape": { "url": "https://app.bugtape.ai/mcp", "headers": { "Authorization": "Bearer bt_pat_…" } } } }
```

Then either subscribe to a resource or call the `listen` tool:

- `resources/subscribe` on `bugtape://projects/{projectId}/inbox` — a `notifications/resources/updated` arrives when a **new fingerprint** lands in the project (bug-level, not every occurrence). Read the inbox afterwards; the notification is a cue, not the payload.
- `resources/subscribe` on `bugtape://bugs/{bugId}/status` (or the bare bug URI) — fires on a real status transition.
- `listen({ resourceUri: "bugtape://projects/{projectId}/inbox", afterCursor })` — blocks until the next new-bug event. Optional `afterCursor` replays missed durable Tuesday-queue events (file-backed cursor store, up to 500) before waiting. A dropped SSE stream is not a replay log — durable catch-up stays on `afterCursor`. Read the authorized issue snapshot after reconnecting to recover current state.
- `listen({ bugId, toStatus: "verified_fixed" })` — wait for one bug to reach a state.

Agent tokens have explicit actions and expiry. New tokens default to 30 days and
`subscribe` plus `get_repro_context`. Choose actions in **Settings → Agents**.
`subscribe` follows issues and permits `ack_bug`; `suggest_fix` also permits
acknowledgements. An ignored acknowledgement can create a 14-day fingerprint mute.
`create_bug` permits reporting. The older read/report/ack actions use workspace
access; project work grants do not narrow them.

Admin/manager-created tokens can also receive `claim_issue`, `request_evidence`,
`read_verification` and `submit_verification`. These actions require an explicit
project work grant. Grant expiry cannot exceed token expiry. Expired/revoked tokens
or grants cannot exercise that authority. Human-only actions such as ordinary
`update_bug`, `bulk_update_bugs`, `delete_bug`, `merge_bugs`, `add_comment` and
`watch_bug` remain unavailable to agent PATs.

### Send one occurrence to a local LLM

The staged [local diagnosis bridge](/docs/local-diagnosis-bridge.md) adds a
human-started **Send to my LLM** action, and a **Send to get fixed** action when
a GitHub code-host connection is nominated. It pairs an outbound-only local
worker to one project. The server builds a bounded redacted packet for the exact
selected occurrence; the worker calls the nominated local agent and may return a
diagnosis or a pull-request identity. BugTape never receives the local model URL
or GitHub token. The nominated agent opens the pull request. BugTape records the
PR identity and does not merge. Settings can auto-send new high/critical issues
to that nominated agent.

The bridge key has only `repair_run` and one project grant. Its access cannot
be widened after pairing. Use the fake adapter only to test the connection;
its result is marked as simulated.

In **Settings → Agents → Edit access**, an admin or manager can change an active
token’s actions, expiry and project work grants. Managers cannot edit an admin’s
token. The secret stays the same. Extending the token keeps existing grant deadlines;
shortening it also shortens grants. Removing a grant blocks new work and renewal of
its current claim. Expired or revoked tokens require a replacement. If another
person changes access while the editor is open, reload and review before saving.

Read the current snapshot before waiting for a new event. Otherwise an agent can
sit idle while a report is already waiting. Start with `list_recommended_critical`
for new actionable issues; it excludes diagnostics, silenced and acknowledged
rows. Re-read authorized issue state after reconnecting; `afterCursor` replays a
bounded durable Tuesday-queue window (default up to 500 events) and is not a full
history API.

For diagnosis: list issues, get_repro_context with the occurrence ID, then
investigate. `ack_bug` records attention; it is not an exclusive lease or proof
of a fix. A human can manage ordinary issue status. Never call human-only
`update_bug` from an agent PAT. For the credential-separated assertion loop
(claim → `propose_fix` → separate-PAT `verify_fix`), see [Verified Close](#verified-close).

### Hand a measured repair to a trusted runner

Use the [public runner verification guide](./runner-verification.md) for the exact
claim, enrollment, artifact and signing contract. Its [standalone Node signer](./examples/sign-verification.mjs)
uses no BugTape package or private source. The runner executes the test and creates
its own measured observations; signing agent-supplied assertions does not establish
that a test ran.

A human admin enrolls the runner and device. A human manager/admin registers the
repaired build. The original agent PAT owns its claim and verification challenge;
a separate runner can sign the complete attempt offline, then the same PAT submits
it. Check admitted storage before uploading real evidence. Hosted binary storage
remains unavailable. Missing enrollment or evidence must stay an explicit blocker.

A status label or quiet traffic is not proof of a fix. The repro packet's
`verifiedFixed` boolean reports a recorded server promotion; it does not recheck
current receipt applicability. Read the receipt before acting. Completing a claim
closes the work lease. Whole-issue promotion requires separate current coverage.

### Read the captured evidence

Start with `get_repro_context({bugId, occurrenceId})`. The packet includes the
human report description, a diagnostic timeline, and `evidence.eventCoverage`.
The timeline omits DOM mutations and scans at most the requested event page.
An empty timeline does not prove that no recording exists. Follow
`eventCoverage.continuation` with `get_bug_events` and its `offset` until
`nextOffset` is null. `capture.availability` distinguishes captured, partial,
missing or expired, and never-captured event data.

`evidence.files` reports **available**, **empty**, **restricted**, or
**unavailable**. Restricted access is not an empty file list. File access needs
`get_repro_context` plus a current project work grant. Reading ordinary report
text alone does not grant file access.

Use `get_occurrence_files({bugId, occurrenceId, limit, offset})` for file pages.
For a ready approved PNG, call the returned
`get_screenshot({bugId, occurrenceId, artifactId})` action. This returns one MCP
image after checking the occurrence, access, expiry, size and SHA-256. Packets do
not download images automatically. Each read uses current authority; revocation
or grant removal blocks later reads. File discovery is bounded to100 files per
page; a screenshot search stops after1,000 files with an explicit limit error.

Reproduction operations stop after30 seconds; each HTTP request/body has a10-second
limit. Large event responses have a16MiB decoded JSON cap. File metadata has a64KiB
cap and screenshots have a5MiB cap. Use smaller event pages after a limit error.
An older occurrence can have files even when its environment metadata is unknown;
grouped issue environment is labelled separately.

The web recording is DOM replay from when the SDK starts, not a screen video.
Hosted PNG storage remains unavailable until the storage gate is complete. A
locally admitted capture-to-agent image path is qualified. Historical server-only
reports cannot gain DOM data that was never captured.

For a token with project work grants:

1. Call `claim_issue` with projectId, issueId, a new commandId UUID and a60–900
   second TTL. Keep the returned claim ID, generation and fencingToken as strings.
2. Reproduce and fix the captured failure. Renew the lease with `renew_claim`.
   Each new operation gets a new commandId; an identical retry reuses its UUID.
   On a stale fence, stop using the old claim and acquire current authority.
3. Run the fixed application/build/install through an enrolled trusted runner.
   `prepare_verification` returns a short-lived challenge for that runner to sign.
   `submit_verification` records exact build/device/runtime, test observations and
   stored artifact IDs/hashes. Unsigned assertions remain pending.
4. `get_verification_receipt` reads the result. `request_evidence` currently returns
   authorized artifact metadata; it does not imply hosted content is enabled.
   Hosted evidence storage and physical-iPhone qualification remain gated.
5. `complete_claim` attaches an applicable receipt to completed work. It does not
   mark the issue verified. `promote_verification` requires current trusted receipts
   covering every observed application/runtime and the proposed fixed builds.
   Missing or stale coverage returns pending reasons. A new failure can reopen it.
6. Release unfinished work with `release_claim`. After work or a snapshot check,
   use `listen` for new activity. Runner/device enrollment is human-admin-only;
   an agent cannot enroll its own trust or label a simulator as a physical phone.


## 2. Headless automation: the signed webhook

In the console: **Alerts → Destinations → Agent** (or any generic **Webhook**). Give it your HTTPS URL and a shared secret. BugTape POSTs JSON for every event your **Rules** allow (severity floor, excludes and quiet hours are applied server-side before send).

Events: `new_bug`, `regression`, `status_change`, `comment`, `assigned`, `recommended_bug`, plus `test` from the Send test button.

Body (ingest events carry the occurrence facts; other events carry what they know):

```json
{
  "event": "new_bug",
  "source": "bugtape",
  "title": "TypeError: Cannot read properties of undefined (reading 'total')",
  "severity": "high",
  "url": "https://shop.example/checkout",
  "bugId": "3c2e…",
  "projectId": "27e1…",
  "occurrenceId": "9f1a…",
  "fingerprint": "a1b2c3d4e5f60718",
  "platform": "ios",
  "release": "1.4.0",
  "environment": "production",
  "userId": "u_123",
  "sessionId": "s_…",
  "userEmail": "sara@acme.com",
  "consoleUrl": "https://app.bugtape.ai/console/issues/3c2e…",
  "evidence": {
    "mcp": "get_repro_context({ bugId: \"3c2e…\", occurrenceId: \"9f1a…\" })",
    "events": "https://app.bugtape.ai/v1/bugs/3c2e…/events?occurrenceId=9f1a…",
    "occurrences": "https://app.bugtape.ai/v1/bugs/3c2e…/occurrences",
    "users": "https://app.bugtape.ai/v1/bugs/3c2e…/users"
  },
  "timestamp": "2026-09-03T01:02:03.000Z"
}
```

Headers:

```
Content-Type: application/json
X-BugTape-Event: new_bug
X-BugTape-Signature: sha256=<hex HMAC-SHA256 of the raw body with your secret>
User-Agent: BugTape-Webhook/1.0   (BugTape-AgentWebhook/1.0 for Agent destinations)
```

Verify the signature before acting:

```js
const expected = 'sha256=' + crypto.createHmac('sha256', SECRET).update(rawBody).digest('hex');
if (!crypto.timingSafeEqual(Buffer.from(expected), Buffer.from(req.get('X-BugTape-Signature') || ''))) return res.status(401).end();
```

Delivery semantics: one attempt per job, **4 retries** with backoff via the job queue, then the delivery lands in the dead-letter log. Every attempt is visible under **Alerts → Delivery log** with a manual **Retry**. Respond `2xx` within 10 s; do your work after acknowledging. Agent destinations refuse private/internal URLs.

Recipe (Cursor Automations, GitHub Actions `repository_dispatch`, or your own worker): on `new_bug` or `recommended_bug`, call `GET /v1/bugs/:id/events?occurrenceId=` (or the MCP `get_repro_context`) with a PAT, reproduce, open a draft PR, then `POST /v1/bugs/:id/ack {"action":"working"}`.

## 3. Periodic: poll the recommended queue

```bash
curl -s "https://app.bugtape.ai/v1/bugs/recommended?since=2026-09-02T00:00:00Z&limit=50" \
  -H "Authorization: Bearer bt_pat_…"
# → { "bugs": [ { "id", "title", "severity", "recommended_severity", "recommended_score", "platform", "url", "project_id", … } ], "total": N }

curl -s -X POST "https://app.bugtape.ai/v1/bugs/<id>/ack" \
  -H "Authorization: Bearer bt_pat_…" -H "Content-Type: application/json" \
  -d '{"action":"working"}'
```

`since` compares against `recommended_at`, so store the newest `recommended_at` you saw and pass it next run. Acked rows drop out of the queue; `ignored` mutes the fingerprint for 14 days.

## Which one, if you are unsure

- You (a human) are in a Claude Code / Cursor session and want the agent to react while you work → **MCP listen**.
- Nobody is watching a terminal and the agent should still open a PR at 3 am → **webhook**.
- You want a daily digest of what an agent should look at, with no infrastructure → **poll**.

Webhooks and MCP are not exclusive: the common shape is *webhook pages the agent, MCP pulls the evidence*.

## Verified Close

Verified Close is an evidence-bound assertion loop. Capture and `get_repro_context` stay the substrate. API and MCP expose each verb, and the server prevents the fixer PAT from submitting its own assertion.

### Pitch

A status label is not test proof. Verified Close records whether a **separate verifier PAT explicitly reproduced the failure**, bound by `evidence_hash`. It does not prove a separate agent identity, require a test artifact, or set the issue to `verified_fixed`. Use trusted runner promotion for a measured close.

### Separate credentials

Mint **two** agent PATs in **Settings → Agents** (or your org admin flow). Give the fixer `claim_issue` (and related work grants). Give the verifier `submit_verification` (and read scopes as needed). The server enforces different PATs. It does not establish that different people or agent processes control them. Do not paste real tokens into docs, prompts, or tickets — use placeholders like `bt_pat_…`.

There is still no seventh claim model: claims, receipts, and Verified Close share the existing agent-work surface. `watch_bug` stays human. There is no `agent_resolved` tool.

### Loop

1. **Wake + evidence** — `list_recommended_critical` / webhook / poll, then `get_repro_context({ bugId, occurrenceId })`.
2. **Claim** — fixer PAT: `claim_issue` with a fresh `commandId`, keep `claimId` / `generation` / `fencingToken` as strings.
3. **Bind capture** — `store_failure_pack` / `ensure_repro_contract` births or updates the ReproContract. `evidence_hash` binds **occurrence ↔ failure pack ↔ contract ↔ verify**. For MCP-related failures, treat `census_stamp` on the failure pack as a first-class field (record it when you have one; do not invent a separate Census product lookup here).
4. **Propose** — fixer PAT: `propose_fix` with `prUrl` and/or `commitUrl`. Transitions toward `fix_proposed`. **`propose_fix` never closes the issue and never sets `verified_fixed`.**
5. **Assert reproduction** — a **different PAT** calls `verify_fix` with the contract’s `evidence_hash`, method `replay_assert`, and an explicit `observation.reproduced` boolean. Same PAT as the fixer → machine code **`E_SELF_VERIFY`**. The receipt records the supplied assertion and does not set `verified_fixed`.
6. **Use measured proof when required** — `prepare_verification` / `submit_verification` / `promote_verification` use an admin-enrolled runner, registered build/device scope, signed observations and artifact hashes. Promotion stays pending when current coverage is incomplete.

### Non-negotiables

| Rule | Meaning |
|---|---|
| Fixer PAT ≠ verifier PAT | Same PAT on `verify_fix` → `E_SELF_VERIFY`; separate agent identity is not established |
| `propose_fix` never closes | Records a PR/commit only; no `verified_fixed` |
| `evidence_hash` binds the loop | Capture ↔ ReproContract ↔ verify must match or you get `EVIDENCE_HASH_MISMATCH` |
| Observation is explicit | `verify_fix` requires `observation.reproduced`; omission is rejected |
| Measured close is separate | Only trusted runner receipts can support `promote_verification` |
| `census_stamp` on MCP failures | First-class optional field on `agent_failure.pack`; name it in packs/docs when present |
| No invented claim model | Use the existing claim / receipt / Verified Close tools only |

Full tool rows: [mcp-tools.md — Claims and trusted verification](./mcp-tools.md#claims-and-trusted-verification).

## What the agent gets once it is awake

- `get_repro_context(bugId, occurrenceId?)` — report text, a page of diagnostic events, recording coverage, scoped file discovery and release/environment evidence. AI output remains separate. Follow event continuation and explicit screenshot actions for more evidence.
- `get_bug_users(bugId)` / `get_user_timeline(userKey)` — who hit it, how many times, what else they hit.
- `list_bug_groups({ platform: "ios" })` — triage one surface at a time.
- `suggest_fix(bugId)`, `get_similar_bugs`, `get_bug_history`, `add_comment`, `bulk_update_bugs` — the rest of the loop.

Full tool list: [mcp-tools.md](/docs/mcp-tools.md). Platform semantics: [platforms.md](/docs/platforms.md). Ingest contract: [ingest-schema.md](/docs/ingest-schema.md).
