---
title: "Operations and limits"
url: "https://bicho.fyi/docs/operations"
description: "Run your Bicho installation on Cloudflare. Learn about storage, quotas, access controls, and background enrichment, and troubleshoot missing error reports."
---

You operate Bicho’s Worker, D1 database, private R2 bucket, and credentials in your Cloudflare account. Cloudflare account and platform limits apply in addition to the application’s quotas and payload limits. Bicho does not set a hosting price or guarantee that a workload fits a free allowance.

## Local and hosted resources

Follow the [setup guide](/docs/) for provisioning, migrations, deployment, and project registration. The core’s `wrangler.jsonc` binds D1 as `DB` and R2 as `PAYLOADS`, enables `nodejs_compat`, and declares an hourly Cron Trigger at `0 * * * *` (UTC). Preserve those binding names when choosing your resource names.

Local development uses local storage, not the hosted database or bucket. Apply migrations and register projects in the environment you intend to test. Generating offline project SQL or editing a project example registers nothing until that SQL is applied. Local success does not establish hosted credentials, resource bindings, scheduled delivery, or project registration.

Use a Worker secret for hosted `BICHO_ADMIN_TOKEN`. An absent or empty value makes admin routes fail closed with 503, not 401. Local development supplies its own token separately. Never reset a populated D1 database to reapply the initial migration: editing an already applied migration does not upgrade it.

## Quotas, cost, and storage growth

Projects default to 1000 new events per UTC hour. At registration, `hourly_limit` accepts an integer from 1 through 1000000. D1 enforces the limit atomically, including concurrent requests. Usage follows receipt time, not a client-supplied occurrence timestamp.

Already stored occurrence UIDs do not consume quota. Distinct occurrences grouped into one issue do consume it. A whole-request quota rejection returns 429 with `Retry-After` until the next UTC hour; exhaustion after an accepted batch prefix returns HTTP 200 with protocol-specific rejection details instead. See [OTLP responses and retries](/docs/otlp/#validation-partial-success-and-retries) and [Sentry responses](/docs/sentry/#transport-and-limits).

Quotas cap accepted events, not incoming requests or total Worker cost. Invalid requests, duplicates, administrative queries, map uploads, background processing, and storage operations still use platform resources. Check [Workers limits](https://developers.cloudflare.com/workers/platform/limits/), [D1 limits](https://developers.cloudflare.com/d1/platform/limits/), and [R2 limits](https://developers.cloudflare.com/r2/platform/limits/) against your account and workload.

D1 stores project configuration, issue/event metadata, searchable tags, source-map registrations, processing state, and issue-status history. R2 stores gzip-compressed captured evidence, uploaded maps, and separate derived enrichment objects. Compression is not a retention policy.

Events are append-only. Resolving, ignoring, muting, or reopening changes an issue’s status, not its captured events or payloads. New occurrences do not automatically reopen resolved, ignored, or muted issues. There are no event/issue DELETE endpoints, retention deletion, or operator cleanup/restore jobs in the core.

R2 is written before the D1 index. An ambiguous index failure can leave an unreferenced object; Bicho retains it rather than risk deleting committed evidence. Known duplicate uploads can be removed, but that is not general garbage collection. Budget for captured events, maps, derived objects, and retained unreferenced uploads. Configure backups, recovery procedures, and capacity monitoring for both D1 and R2 yourself. Avoid bucket lifecycle rules that delete payloads still referenced by D1.

## Abuse protection and CORS

Numeric project IDs and browser ingest keys are public, not bot protection. Configure [Cloudflare WAF and rate limiting rules](https://developers.cloudflare.com/waf/rate-limiting-rules/) on your production custom domain for `/v1/logs`, `/api/`, and `/mcp` as appropriate. Available controls depend on your Cloudflare plan; the repository does not create these rules.

Avoid interactive challenges on these paths. SDKs, exporters, API clients, and MCP clients cannot complete them. Tune limits for legitimate error bursts and users sharing an IP. Disable unused `workers.dev` and preview URLs so traffic cannot bypass domain-level rules. The default manifest enables `workers.dev` and disables preview URLs.

Ingest supports browser preflight and any origin, but still validates the ingest key and any required secret. API CORS uses `CORS_ORIGINS`; MCP also rejects unexpected or opaque origins and permits same-origin requests. CORS is not authentication and cannot stop a non-browser client from using exposed credentials. Keep dashboard admin credentials on the server. See [privacy and credentials](/docs/privacy/).

## Background enrichment and capacity

Ingestion indexes events immediately. Successful ingestion and source-map uploads also request background enrichment through `ctx.waitUntil()`. This is best-effort work, not a durable queue. The hourly Cron Trigger recovers interrupted work and continues eligible batches.

A D1 lease permits one processor at a time, with a one-minute cooldown and recovery after five minutes. A request during cooldown does not arm a timer for the next minute; the next eligible request or cron invocation must continue the work. Each run processes at most 25 events and 100 source-map lookups. Large events save a frame cursor for a later run. With no traffic, a backlog advances by at most one batch per hour, and some events need multiple runs.

Missing maps wait for an upload in their project rather than being rescanned each hour. Uploads invalidate pending continuation across that project, including unrelated builds, so continual uploads can delay progress. Upload maps before their events when possible. Retryable storage failures back off for one hour and stop after three attempts. Permanent invalid evidence and exhausted failures remain visible; there is no public retry/reset endpoint.

Enrichment writes separate derived evidence. It never changes original event bytes, grouping fingerprints, or issue status. Bicho does not inject debug IDs, fetch remote maps, or discover `sourceMappingURL`. See [source maps](/docs/sourcemaps/) for supported references, positions, map limits, and incomplete-result reasons.

## Monitoring

Inspect authenticated `GET /api/:project_id/processing` or MCP `get_processing_health`. Project status counts and global processor health have different scopes. Global `processed` and `failed` describe attempts in the last run, not lifetime totals. An idle invocation leaves health unchanged.

A `pending` event may be waiting for a map or resuming budget-limited work. `not_applicable` is normal when there is no usable source-map reference. Zero pending/failed events does not prove the Cron Trigger has fired. Check the deployed trigger and scheduled invocation logs separately, and inspect event enrichment reasons, context-missing indicators, and truncation flags.

Monitor ingestion 429/5xx rates, partial-success rejection counts, Worker error logs, D1 query/storage usage, R2 growth, and enrichment backlog. A 200-only success metric misses OTLP and Sentry partial rejections. Responses include a server-generated `X-Request-ID`; internal failure logs include request IDs and error names rather than credentials, query strings, or captured bodies. Preserve the request ID when diagnosing a failure without logging sensitive request headers or payloads yourself.

## Troubleshoot missing events

For Sentry SDKs, use the generated Bicho DSN and remove any old tunnel or custom transport destination. Secret-enabled backend projects also need `sentry.headers`. Disable unsupported integrations, flush before shutdown, and check `accepted`, `rejected`, and `discarded` in the response: an SDK may ignore partial rejection. Follow [Sentry delivery checks](/docs/sentry/#check-delivery), then search the stored evidence as described below.

For OTLP exporters:

1.  Confirm the exporter targets this instance’s `/v1/logs` with HTTP JSON or protobuf. A redirect, HTML challenge, gRPC connection, trace exporter, or wrong environment is not a successful logs delivery.
2.  Verify the project was registered in this environment, and use the generated ingest headers. Add the secret only for a backend project that requires it. Admin bearer tokens do not substitute for ingest credentials.
3.  Confirm your logs instrumentation actually emitted an exception record or numeric ERROR/FATAL log. Exporter environment variables do not install capture. Flush before shutdown and inspect the full OTLP response for partial rejection.
4.  Check request/batch limits, 429/5xx responses, and `Retry-After`. Preserve occurrence UIDs during transient retries. A fixed test UID represents one occurrence regardless of how often you resend it.
5.  Search the correct project with `status=all` for issues. Issue lists default to unresolved, and new events do not reopen hidden issues. Remove environment, release, tag, and time filters before concluding evidence is absent. Statistics default to the last 24 hours; they are not an unbounded event count.
6.  Read the stored event through the [API](/docs/api/) or [MCP](/docs/mcp/). OTLP correlation IDs and occurrence UIDs are not the server’s event lookup ID. Sentry `event_id` values become the event lookup ID, normalized to lowercase hexadecimal without hyphens. Missing enrichment does not mean ingestion failed.

For a map problem, check the project and debug ID, matching captured references, positive one-based line/column coordinates, supported map structure, and event-specific reasons. A release label alone is insufficient. Follow [source-map troubleshooting](/docs/sourcemaps/) rather than repeatedly resending the event.

For MCP connection failures, use the installation’s `/mcp`, not the public website’s documentation MCP. Supply the admin bearer header, initialize with a Streamable HTTP client, and send `Content-Type: application/json` plus `Accept: application/json, text/event-stream`. Check proxy forwarding and Origin rules. GET is not unauthenticated discovery, and OAuth is not implemented.

## Response status checks

| HTTP status | Concrete checks |
| --- | --- |
| 400 | Validate IDs, query filters/cursor scope, known OTLP fields, map structure, issue-status body, or MCP protocol input. |
| 401 | Check the admin bearer header and configured token. Ingest credentials cannot authorize admin routes. |
| 403 | Check ingest key/secret pairing or MCP Origin. Also distinguish an edge WAF response from the Worker’s response. |
| 404 | Check the deployment, project ID, and resource within that project. |
| 405 | Check the method. MCP requires POST; event/issue DELETE is unsupported. |
| 406 | For MCP, accept both JSON and event-stream media types. |
| 409 | Different map content already uses this immutable project/debug ID. Use the correct build’s ID. |
| 413 | Reduce request bytes, decoded size, batch size, or the applicable stored-output limit. |
| 415 | Check content type and compression. MCP requires JSON; OTLP requires JSON or binary protobuf. |
| 429 | The project’s UTC-hour accepted-event quota is full. Honor Retry-After. Edge rate limits may also return 429. |
| 500 | Check Worker error logs and D1/R2 availability for internal read/storage or processing failures. |
| 503 | Check ingest storage availability, the admin secret, or a D1-indexed payload missing from R2, depending on the route. |

OTLP errors and MCP JSON-RPC errors differ from REST’s `{ "error": "..." }` response. Diagnose the route and response body, not just the status code.

## Unsupported modes

Bicho has no built-in dashboard, general log archive, traces, metrics, attachments, session replay, alerts, native crash symbolication, automatic reopening, or retention deletion. Source maps must be uploaded; remote source fetching, indexed/sectioned maps, and artifact bundles are unsupported. A separate dashboard can use the authenticated API, but it must not publish the admin token in browser code.