Skip to content

~7 min readgrounded in docs/DEPLOY.md · apps/web/app/api/**/route.ts (error strings) · apps/web/lib/chat/model.ts (preflightModelCheck) · apps/web/lib/rate-limit.ts · lib/limit-message.ts · apps/worker/src/{media,voice,devices,relay}.ts · apps/worker/wrangler.toml

FAQ & troubleshooting·

Every message quoted below is a string the code returns, found by reading the route or worker file named next to it. If you see something not on this page, grep the string in apps/web/app/api or apps/worker/src — every error in this project is a literal, not a template.

Deploying·

BLOCKED — the commit author doesn't have permission to create deployments (Vercel)
CLI deploys carry the HEAD commit's author. A team with Git author permission refuses authors who are not members; the CLI shows UNKNOWN and hangs. Commit as a team member or deploy from a Git connection. vercel ls shows the real state.

/api/health says workerConfigured: false · build log TINY_WORKER_URL not set
The variable is missing on the environment you deployed to. npx vercel env ls production, add it, redeploy. NEXT_PUBLIC_TINY_WORKER_URL is the browser-side copy — set both to the same origin.

Every call the app makes to the worker answers 401 {"error":"unauthorized"}
INTERNAL_API_KEY differs between Vercel and wrangler secret put INTERNAL_API_KEY. Set the same value on both and redeploy the app. This one string accounts for most of the worker's 95 unauthorized returns.

Vectorize: filter on unindexed property (worker log)
The name/userId metadata indexes on the Vectorize indexes were not created. node scripts/bootstrap-cloudflare.mjs is idempotent and adds them.

{"error":"media store not provisioned"} (424) · {"error":"voice not provisioned"} (424)
The worker is missing its MEDIA R2 binding or its VOICE Durable Object (apps/worker/src/media.ts:192, voice.ts:742). Check wrangler.toml against docs/PROVISIONED.md and redeploy the worker. Media uploads, generated images and voice calls need them; nothing else does.

{"ok":false,"error":"auth not configured"} (500) from /api/auth/cli or /api/auth/cli/token
AUTH_JWT_SECRET is unset on the app. Without it no session token can be signed — GitHub login will fail too.

Signing in·

GitHub sends me back to the wrong host · passkey: "invalid RP ID"
NEXT_PUBLIC_APP_URL does not match the origin you are on, or the GitHub OAuth App's callback URL still names the old host. Custom domain lists every place the origin lives. Passkeys are bound to the host they were created on and must be re-enrolled after a move.

{"error":"login required"} (401)
The session cookie is missing or expired, or a CLI token was rejected. This is the single most common string in the app (67 occurrences across the routes). For scripts: Authorization: Bearer <token> from POST /api/auth/cli/token; the token lasts 90 days.

{"error":"invalid challenge"} · {"error":"challenge expired"} (WebAuthn)
The registration or login challenge cookie did not survive the round trip — usually a different host between GET (options) and POST (verify), or more than a few minutes between them. Reload and try once more on one host.

Chatting·

The stream ends immediately with No API key configured for provider 'openai'. Bring your own key via model settings or configure the server.
preflightModelCheck in lib/chat/model.ts refuses before calling the model. Set the server key for the provider you chose (OPENAI_API_KEY, AWS_BEARER_TOKEN_BEDROCK, GEMINI_API_KEY/GOOGLE_API_KEY, AI_GATEWAY_API_KEY) or let users bring their own — Configure models. The frame arrives as {"type":"error","error":…} on the SSE stream, then [DONE].

429 with a sentence like "That's 50 requests a day…"
The rate limiter (lib/rate-limit.ts) is on because KV_REST_API_URL/KV_REST_API_TOKEN are set. The message is composed from the live numbers (lib/limit-message.ts) and says which lever applies: signing in gives a personal window, reputation widens it, and some windows — the ones that shield a third party, such as /api/worker fetching a URL you supply — widen for nobody. X-RateLimit-Limit/Remaining/Reset headers ride along. Raise the base with NEXT_PUBLIC_FREE_TIER_REQUESTS_PER_DAY, or unset the KV variables to run unlimited.

Tiny AI '<name>' not found in the universe · '<name>' is private and can't be consulted
From the ask_tiny tool. The slug is wrong, or the tiny's owner made it private — private tinys answer only their owner and holders of the key.

repo owner '<owner>' is not trusted. Built-in: strands-agents. …
install_tool fetches raw GitHub files only from TOOL_REPO_ALLOWLIST plus owners the user trusted with /tools trust <owner>. The model cannot widen this itself — that is the point.

This tiny charges $… per message. Sign in and fund your wallet at /wallet to chat.
The tiny has a price and payments are enabled. If you did not mean to run payments, they are off by default — see below.

Devices·

{"error":"device rejected our credential — re-enroll with a fresh token"}
An endpoint device answered 401/403 to the worker's bearer. The secret you enrolled with no longer matches what the device expects. DELETE /api/devices {deviceId} and enroll again with the current secret (endpoint devices cannot be adopted).

{"error":"unknown device"} · {"error":"device not found"}
The deviceId is not in the owner's fleet, or the token hash does not match — the worker answers the same way for both so a wrong token reveals nothing about which ids exist. Check the id on /devices; if the token is lost, POST /api/devices/adopt {deviceId} rotates it.

{"error":"payload must be valid JSON ≤8KB"} (400)
A relay envelope or reply over the cap (RELAY_PAYLOAD_MAX_BYTES). Put large results in /api/media and send the URL.

424 from /api/devices/relay
The relay itself let the app down — the worker did not answer inside the 10 s bound. Retryable; the clients already treat 424 as "try again", distinct from a 4xx that means the request was wrong.

The fleet shows my daemon offline though it is running
Presence is last_seen within 60 s (PRESENCE_WINDOW_S). Heartbeat at least every 30 s. Endpoint devices show online: null on purpose — reachability is per call.

The device page says "connecting through the cloud" though we share a Wi-Fi
The daemon is not sending lanUrl in its heartbeat, so the phone has no address to dial directly.

Payments·

/api/wallet*, /api/x402/*, /api/chain/* answer 404
Intended. PAYMENTS_ENABLED is unset — the default. Set it to "true" on both the app and the worker to turn the money routes on; Payments lists what else they need.

{"error":"x402 payments not configured on this deployment"} · withdrawals not configured on this deployment
Payments are on but the chain-side variables are not — X402_PAY_TO, X402_FACILITATOR_URL, PAYOUT_PRIVATE_KEY. Each route names what it is missing in its header comment.

Questions·

Does this talk to tiny.technology?
No. There are zero tiny.technology constants in the code; every origin is an environment variable (Environment variables). Your deployment is its own universe.

Can I run only the Next.js app?
No — the worker is where the data lives (D1, KV, Vectorize, R2, Durable Objects). The app is a thin, session-aware front for it. The two are one system with two deploy targets.

Do I need the iOS/Android apps?
No. The web app is complete on its own; the native apps are clients of the same routes. A laptop joins with npx tiny-vercel, any HTTPS service joins as an endpoint device — Connect a device.

How do I wipe everything?
node scripts/teardown-cloudflare.mjs --yes deletes exactly what docs/PROVISIONED.md lists; npx vercel project rm <name> removes the app. Operate.

Something on this page is wrong
The string you saw and the file it came from make a complete issue: open one.