Incident response
IR-1: Bedrock API outage (circuit breaker open)
This runbook applies when Amazon Bedrock is the configured provider. Fleet Pi defaults to Google Gemini; if you’re seeing chat failures with the default, treat them as generic provider errors and check yourGEMINI_API_KEY and provider status first.
Trigger: Users report chat returning “Bedrock API is temporarily unavailable” or all /api/chat requests fail with 500 errors.
- Verify the circuit breaker state
- Check application logs for
bedrock-apicircuit breaker events - Look for
openstate transitions in logs withrequestIdcorrelation - Run
curl -sf http://localhost:3000/api/healthto confirm the web server is still healthy
- Check application logs for
- Check Bedrock service status
- Verify AWS credentials are valid:
aws sts get-caller-identity - Check Bedrock model access in the AWS Console for the configured region (default
us-east-1) - Review AWS Service Health Dashboard for regional outages
- Verify AWS credentials are valid:
- Inspect recent error patterns
- Search logs for the last 30 minutes:
grep "bedrock-api"orgrep "circuit breaker" - Identify if errors are throttling (429), auth (403), or model-level (400)
- Note the
errorThresholdPercentage(50%) andvolumeThreshold(5) — the breaker opens after 3 failures within 5 calls
- Search logs for the last 30 minutes:
- Wait for automatic recovery or force reset
- The circuit breaker
resetTimeoutis 30 seconds; it will attempt a half-open call after that period - If Bedrock is confirmed restored but the breaker is still open, restart the dev server to reset the breaker state
- The circuit breaker
- Communicate
- Post in the incident channel: “Bedrock circuit breaker open — root cause under investigation”
- If AWS is at fault, set status page to “degraded” and estimate recovery based on AWS status updates
IR-2: Chat session corruption or data loss
Trigger: Users refresh the page and see an empty transcript, or the chat UI shows “Session reset” repeatedly. The steps below cover local JSONL sessions. On Cloudflare, hosted Agent transcripts live in the signed-in user’s Pi Durable Object, not in.fleet/sessions/.
- Identify the affected session
- Extract
sessionIdfrom browserlocalStorageor from thestartevent in recent/api/chatrequest logs - Locate the Pi session file path under
.fleet/sessions/inside the repo root
- Extract
- Check session file validity
- Verify the session JSONL file exists and is readable
- Ensure the file is inside the repo-scoped session directory (outside files are rejected by
isUsableSessionFile) - Look for truncated or malformed JSONL lines at the end of the file
- Validate localStorage metadata
- If
localStoragecontains an invalidsessionFile(e.g. pointing to/etc/hostsor a non-existent path), the app silently starts a fresh repo-scoped session — this is expected behavior - Instruct the user to clear
localStoragefor the site if the stored metadata is corrupt
- If
- Attempt manual hydration
- Call
POST /api/chat/sessionwith thesessionIdto triggerhydrateChatSession - If the session file cannot be opened, the server returns an empty message list with
sessionReset: true
- Call
- Recover or recreate
- If the file is corrupt beyond repair, archive it and let the user start a new session
- If the issue is widespread, check disk space and file system permissions on
.fleet/sessions/
- Follow up
- Document the root cause (disk full, permission issue, or Pi SDK bug)
- Monitor
SessionManager.openerror rates for 24 hours
Troubleshooting
Bedrock errors
Symptoms: Chat streams terminate witherror events, model picker shows unavailable models, or diagnostics contain model registry errors.
- ThrottlingException (429) — Bedrock is rate-limiting requests.
- Check the
requestIdin logs to confirm it is the same across retries - The Pi SDK auto-retries with exponential backoff; do not manually retry
- If sustained, enable request batching or switch to a lower-traffic model variant
- Check the
- AccessDeniedException (403) — IAM role or profile lacks
bedrock:*permissions.- Verify
AWS_PROFILEandAWS_BEARER_TOKEN_BEDROCKenvironment variables - Ensure the IAM policy includes
bedrock:InvokeModelandbedrock:InvokeModelWithResponseStream
- Verify
- ValidationException (400) — The requested model ID is invalid.
- Check
modelSelectionin the request body against the registry - Model IDs use region prefixes (e.g.
us.anthropic.claude-sonnet-4-6); the backend normalizes candidates but a completely unknown ID will fail
- Check
- ModelNotReadyException — The model is not enabled in the AWS account.
- Visit the Bedrock Console > Model access and enable the model for the current region
- Network / timeout errors — The circuit breaker
timeoutis 30 seconds.- If Bedrock does not respond within 30 seconds, the breaker counts it as a failure
- Check VPC endpoints or corporate proxy settings if running in a restricted network
Session hydration failures
Symptoms: After refreshing the browser, prior messages are gone; the UI shows a blank chat;sessionReset: true appears in /api/chat responses.
- Invalid
sessionFilein localStorage — The browser stores only Pi session metadata (sessionFileandsessionId). IfsessionFilepoints outside the repo session directory,isUsableSessionFilereturnsfalseand a fresh repo-scoped session is created silently.- Remediation: Clear site
localStorageand start a new chat
- Remediation: Clear site
- Missing or moved session file — The session JSONL was deleted or moved after the metadata was stored.
- Remediation: Check
.fleet/sessions/for the file; if missing, the session is unrecoverable
- Remediation: Check
- Corrupt session JSONL — A malformed line causes
SessionManager.opento throw.- Remediation: Inspect the file with
head -n 20andtail -n 5; remove trailing partial lines if safe, otherwise archive and start fresh
- Remediation: Inspect the file with
- Race condition during streaming — If a page refresh happens while the session is being compacted, the file may be in an inconsistent state.
- Remediation: Wait 5 seconds and retry hydration; the compaction lock should release
Circuit breaker states
The Bedrock API call is wrapped byopossum with the following configuration:
- Closed (normal) — Requests flow to Bedrock. Failures are counted.
- Open — All calls are rejected immediately with the fallback error:
"Bedrock API is temporarily unavailable due to repeated failures. Please try again later." - Half-open — The next call is allowed through as a probe. If the probe succeeds, the breaker closes. If it fails, the breaker opens again for another
resetTimeout.
Chat session mirror (Neon Postgres)
Locally, Pi session JSONL files under.fleet/sessions/ remain authoritative. On Cloudflare, the per-user Pi Durable Object holds the hosted Agent transcript and Neon keeps an optional projection. When FLEET_PI_CHAT_DATABASE_URL is set, Fleet Pi mirrors full session entries and run provenance into Neon Postgres so you can query conversations with SQL, power cross-surface history, run analytics, and debug long-running runs.
Mirror writes happen on session create, hydrate, and list paths. In local development, failures are caught and logged with the matching requestId and never interrupt chat streaming. On Cloudflare, mirror errors fail closed.
When to enable it
- You need SQL search or analytics across Pi sessions.
- You run Fleet Pi across multiple surfaces and want a single source for chat history.
- You want durable provenance for tool executions and file mutations beyond what local SQLite captures.
chat:migrate and auth:migrate grant the runtime privileges to the role named by FLEET_PI_APP_ROLE (default fleet_pi_app). The name must be a lower-case Postgres identifier ([a-z_][a-z0-9_]*, max 63 characters, not pg_*). Fleet Pi rejects any other value before running SQL.
Configure
Set both connection strings in .env:
FLEET_PI_CHAT_DATABASE_URL unset to keep Pi conversations in JSONL and local SQLite only.
Run migrations
fleet_pi_chat_migrations.
Tables
Triage
- Mirror disabled unexpectedly: confirm
FLEET_PI_CHAT_DATABASE_URLis loaded in the running process (check/api/healthhost env, not just the.envfile). - Rows missing for a recent session: grep logs for the session’s
requestIdand look for mirror sync warnings; the JSONL file is still authoritative and you can re-trigger sync by hydrating the session. - Migration fails with permission errors: verify
FLEET_PI_CHAT_MIGRATION_DATABASE_URLusesneondb_owner, not the app role.
- Snapshot the production Neon branch.
- Create the new role with SQL as
neondb_owner. Roles created from the Neon console or API joinneon_superuserand getBYPASSRLS, which defeats the forced RLS onpi_*tables. - Run
pnpm --filter web chat:migrateandpnpm --filter web auth:migratewithFLEET_PI_APP_ROLE=<new-role>. - Update the Worker’s database URL secrets to the new role with
wrangler secret, deploy, and confirm sign-in and chat work. - Drop the old role only after the new Worker version serves all traffic.
FLEET_PI_APP_ROLE=<new-role> for chat:migrate and auth:migrate. Otherwise later grants go to the default fleet_pi_app name.
Deployment release gate (Cloudflare + Neon)
Operator workflow for deploying Fleet Pi to Cloudflare Workers with Neon-backed auth and Pi session mirroring. Trust zones, Worker bindings, and required env vars are documented in configuration. What the gate checkspnpm --filter web verify-deployment-readiness validates:
- Required Worker env vars for Better Auth or Neon Managed Auth and the chat mirror.
- Preview trust-zone markers (
FLEET_PI_DEPLOYMENT_TRUST_ZONE=preview). - Distinct preview and production Neon database markers on Preview, and preview database URLs that do not match production.
- Optional owner-connection probes when
FLEET_PI_AUTH_MIGRATION_DATABASE_URLandFLEET_PI_CHAT_MIGRATION_DATABASE_URLare supplied.
assertDeploymentReadyOnBoot() runs the same check during Better Auth initialization, so missing secrets fail before serving auth routes.
CI deploys
The Cloudflare Workers GitHub Actions workflow builds the Worker with pnpm build:cloudflare and then:
- Deploys production with
wrangler deployon every push tomain. The deploy fails whenCLOUDFLARE_API_TOKENorCLOUDFLARE_ACCOUNT_IDis missing. - Deploys a Worker Preview named
pr-<number>for pull requests labelledcloudflare-preview. Previews get Neon preview-branch secrets only, and the workflow refuses production database URLs. Pull requests without Cloudflare credentials skip the deploy with a warning.
.circleci/config.yml) runs the same validation gates on every push and pull request (lint, format, and guards, typecheck, syncpack, web unit tests, build, knip, jscpd, tech-debt, validate-agents-md), plus a human-gated deploy lane:
deploy-readinessruns on every push with the same production-shaped and preview-shaped readiness checks as the GitHub Actions release gate. It needs no credentials.- On
main, anapprovalhold followsdeploy-readiness. Nothing deploys until someone approves. deploy-productionthen rebuilds, runswrangler deploy, and records a CircleCI deployment for componentfleet-pi-webin environmentproduction. It uses thefleet-pi-deploycontext (CLOUDFLARE_API_TOKEN,CLOUDFLARE_ACCOUNT_ID,VITE_NEON_AUTH_URL).
HEAD by the time it is approved. Use it for deliberate redeploys and emergencies, not routine releases. End-to-end tests, PR previews, and Neon preview branches stay on GitHub Actions.
Pre-deploy checklist
- Apply auth grants:
pnpm --filter web auth:migrate - Apply chat mirror migrations:
pnpm --filter web chat:migrate - Run readiness with owner URLs:
- Deploy a Worker Preview against an isolated Neon branch with preview-only secrets.
- Sign in against the preview and confirm session create, resume, and delete succeed.
- Merge to
mainto deploy production only after the gate and smoke checks pass.
- Capture the
verify-deployment-readinessoutput. - Confirm the Neon migration ledger and RLS state manually.
- Set the missing secrets on the Worker with
wrangler secret. Never disable owner-only mirror rules. - Redeploy and re-run the smoke checks.
assertDeploymentReadyOnBoot() or owner-only persistence to unblock traffic.
Rollback
To return production to an earlier Worker version, run the manual rollback CircleCI pipeline. It has no trigger, so it runs only when you start it.
- List the Worker versions and pick the one to restore:
- Start the rollback pipeline on
mainwith the version ID and a reason: - The pipeline fails when
target_versionis empty. Otherwise it runswrangler rollback <worker-version-id> --name fleet-pi-web --message "rollback: <why>" --yes. - Re-run the smoke checks against production.
Owner-only session mirror and user erasure
Cloudflare deployments enforce owner-only Pi session mirroring: every mirror write, resume, and provenance query is scoped to the authenticateduserId under Neon RLS. Local development without FLEET_PI_CHAT_DATABASE_URL keeps anonymous sessions.
Users can erase their own data through the API:
DELETE /api/chat/session?sessionId=…— remove one owned session from the mirror (cascades to runs, tool executions, and file mutations). A hosted session in the Pi Durable Object is only hidden, because Pi Durable has no per-conversation delete.DELETE /api/chat/account— erase all mirrored Pi sessions and BYOK provider credentials for the caller. On Cloudflare it also clears the caller’s Pi Durable Object storage, which erases every hosted transcript.
FLEET_PI_CHAT_MIGRATION_DATABASE_URL (owner connection).
Quick reference
Runtime cache pressure
Symptoms: Memory grows over long sessions; Bedrock calls feel slower than expected after long idle periods.- Pi runtimes are cached per session with a TTL controlled by
FLEET_PI_RUNTIME_TTL_MS(defaults to 10 minutes). - Lower the TTL if you want runtimes evicted sooner; raise it to keep them warmer between turns.
- Restart
pnpm devto forcibly drop all warm runtimes if you suspect leaked Pi state.
Workspace contract drift
Symptoms:GET /api/workspace/health returns missing canonical paths or an unexpected manifest version.
- The contract version is pinned at
WORKSPACE_CONTRACT_VERSION = 1inworkspace-contract.ts. - Missing canonical directories should be re-created by workspace bootstrap on the next run; user-authored content is never overwritten.
- Use
POST /api/workspace/reindexto rebuild the projection index without modifying canonical files.
Related files
apps/web/src/lib/pi/circuit-breaker.ts— Breaker configuration and factory.apps/web/src/lib/pi/server.ts— Bedrock invocation and Pi runtime cache.apps/web/src/lib/pi/server-runtime.ts— Runtime TTL andPI_AGENT_DIRresolution.apps/web/src/lib/pi/plan-mode.ts— Mode allowlists and plan-mode extension.apps/web/src/lib/pi/run-provenance.ts— Provenance recording around session and tool events, including tool executions and file mutations mirrored to Neon.apps/web/src/lib/db/pi-session-mirror.ts— Safe sync helpers that mirror Pi sessions into Neon Postgres whenFLEET_PI_CHAT_DATABASE_URLis set.apps/web/src/lib/pii/sanitizer.ts— Input redaction before logging.apps/web/src/lib/logger.ts— Pino logger with redaction andrequestIdcorrelation.apps/web/src/lib/workspace/workspace-contract.ts— Workspace contract version and section kinds. ino logger with redaction andrequestIdcorrelation.apps/web/src/lib/workspace/workspace-contract.ts— Workspace contract version and section kinds.