Operations Runbook
Procedures for common operational conditions. Each entry: symptom → likely cause(s) → what to do. For active incidents needing severity triage and communication, see the Incident Response Guide; this document is the “what do I actually do” reference.
1. A Submission’s AI Processing Appears Stuck
Check, in order:
/admin/jobs— is the job queued, running, or failed?- The organization’s credit balance (
/admin/organizations/[id]orgetUserCreditStatusQuery) — jobs are skipped, not retried, when credits are insufficient (QueueBillingService.checkCreditsAndPlan). This is the single most common cause of a submission that never progresses. Top up or adjust credits, then manually re-trigger the relevant mutation (e.g.bulkTriggerFounderDna) if the job was skipped rather than queued. - Cloudflare dashboard → Workers Logs for the queue worker — look for
runWithD1Retryexhaustion (D1 overload) or an LLM-provider error that exhaustedretryWithBackoffWhile. - Confirm the relevant
llm_servicesrow (Admin → LLM Services) points at a currently-healthy provider — a misconfigured or rate-limited provider will fail every job routed to it.
2. Embeddings / RAG Search Results Look Stale or Missing
The submission-embedding cron sync is known to be paused in production (see the Integration Documentation) due to an unresolved auth gap between the cron worker and the app’s embed-all route. If a newly created or updated submission isn’t showing up in RAG/chat search:
- Confirm whether the cron is actually enabled in the current queue worker config (
src/queue/wrangler-configs/production/wrangler.toml,[triggers]). - If it’s still paused, trigger embedding manually via
POST /api/admin/submissions/:id/embed-all(requires an authenticated admin session) rather than waiting for the cron. - If re-enabling the cron permanently, first resolve the auth gap (add a service-token header to the cron’s outbound call) — otherwise every cron-triggered run will 401 silently.
3. A Third-Party Integration Sync Isn’t Working (Airtable / HubSpot / Attio / Typeform)
- Check whether the integration’s OAuth token has expired or been revoked on the third-party side —
integrationAccountsstores the token but there’s no automated expiry-alerting found in the codebase. - For Airtable specifically: webhook subscriptions expire and must be refreshed — check whether
run-airtable-webhook-refresh-cron.ts(a daily cron) is running; a missed refresh is a plausible cause of silently stopped inbound sync. - For webhook-driven integrations (Airtable, HubSpot, Typeform, Fireflies, Recall.ai, DocuSign), confirm the external platform is actually delivering webhooks (check the integration’s own dashboard/delivery logs on their side) before assuming the fault is on Yareta’s side.
- Check the relevant queue (
processAirtableWebhookQueue,processHubspotWebhookQueue,processTypeformWebhookQueue) for a backlog or repeated failures.
4. Reports (MDD / Team Summary) Contain Malformed Text (Stray Markdown, Wrong Names, Truncated Content)
This class of issue traces back to the prompt, not a code bug, in most observed cases:
- Check whether the live
system_promptsDB row for the affected prompt type matches the current code-side prompt (src/prompts/*.ts) — see Admin Guide §2 for why these can drift. A stale DB prompt missing a formatting or name-resolution instruction present in code is a known root cause of past incidents. - If the DB prompt is current and the issue persists, check whether the affected content type has a defensive post-processing sanitizer (e.g.
sanitizeProse()for team summaries) that might need extending for a new failure pattern — LLM instruction-following on formatting constraints has proven unreliable enough that some sections rely on code-level scrubbing rather than the prompt alone. - For truncated JSON output specifically, check
MODEL_MAX_OUTPUT_TOKENS/resolveCompletionTokenBudgetfor the model in use — this has previously caused a smaller model to silently truncate a large structured response (see ADR-004 and theCRITICAL_SERVICE_DEFAULTSoverride it prompted).
5. Credit Balance Looks Wrong (Under- or Over-Charged)
- Query
llm_usage_logfor the submission/feature in question, filtering onrunId— this dedupes retries of the same logical request; multiple rows with the samerunIdandbilledAtset on only one indicate the dedup worked correctly. - Check
creditUsageHistoryfor theidempotencyKeyassociated with the charge — a duplicate charge should never occur if the idempotency key matches an existing row. - If a job appears to have consumed a full charge but produced no output, check for a
FLAT_PLACEHOLDER_MARKERcharge that was never superseded by a real usage-based charge (see the Technical Design doc) — this indicates the job started billing but never completed the LLM call. - If a balance was drained to zero unexpectedly, check for a
drainBalanceToZeroevent around the same timestamp — this is intentional behavior when a guarded deduction is refused due to a concurrent job, not a bug, but a cluster of these events close together may indicate two jobs racing on the same org’s credits and warrants investigation.
6. Deploy Succeeded but Behavior Didn’t Change
- Confirm which unit actually needed to change — a frontend/API change requires the Pages app deploy; a background-job change requires the queue worker deploy (
scripts/queue-deploy.sh) — these are separate steps inci-cd.ymland one can succeed while the other fails silently (no deploy-failure notification exists — see the Observability doc). Check both steps’ logs in the GitHub Actions run. - If the change was to a prompt template in code, remember it also requires a DB sync (§4 above / Admin Guide §2) — a code deploy alone does not change live LLM behavior for prompt-template changes.
- Check whether the change was to an
.env.example-listed variable that needs to be set as a dashboard Secret/Variable on the relevant Cloudflare project —wrangler.toml/CI only injects a specific known set of vars (NEXT_PUBLIC_APP_URL,GRAPHQL_ENDPOINT,GRAPHQL_TOKEN,QUEUE_API); most other env vars must be set directly on the Cloudflare dashboard for both the Pages project and, separately, the queue Worker (keep_vars = truemeans Worker secrets persist across deploys but must be set at least once).
7. D1 “Overloaded” or Transient Errors Under Load
This is a documented, expected D1 behavior under concurrent write pressure, not necessarily a bug. runWithD1Retry already retries these automatically (3 attempts, linear backoff). If retries are exhausting:
- Check whether a recent change increased
max_concurrencyon a D1-heavy queue beyond what production is tuned for (compare againstsrc/queue/wrangler-configs/production/wrangler.toml’s deliberately conservative settings, e.g.dnaSignalsQueueatmax_concurrency = 1). - Check for an unusually large batch of jobs enqueued at once (e.g. a bulk
bulkTriggerFounderDnacall) that’s exceeding the per-queue tuning.
Related documents: Incident Response Guide · Troubleshooting Guide · Admin Guide · Observability & Monitoring