Skip to content

Troubleshooting

The things that actually go wrong, in roughly the order people hit them.

RuntimeError: Missing environment variable: LLM_API_KEY

Working as designed — config fails loudly at startup rather than mysteriously at request time.

  • Is .env in app/?
  • Any quotes around the value? Don’t quote them.
  • Deployed? Environment changes need a redeploy to take effect.
  • Following an older copy of this workshop? The variable was called GEMINI_API_KEY — that name still works when LLM_PROVIDER is google.
ModuleNotFoundError for a local module like agent or tools

Run from inside app/, not the repo root — that’s the uv project root, and uv run … resolves imports relative to it.

uv: command not found

The installer wrote to a shell profile your current terminal hasn’t read. Open a new terminal, or source ~/.zshrc / source ~/.bashrc.

Tests fail with connection errors

They shouldn’t — the suite needs no network and no keys. If they’re hitting the network, something in the code under test lost its mock. From app/, run uv run --extra dev pytest -v and check which test.

/health says database: false

In order of likelihood:

  1. You haven’t run database/schema.sql yet.
  2. SUPABASE_URL has a trailing slash, or isn’t the https://xxx.supabase.co project URL.
  3. You used the publishable key instead of the secret one. sb_publishable_… is blocked by the RLS you enabled — which is exactly its job. You want sb_secret_… (or the legacy service_role key on an older project).
  4. You put the key in an Authorization: Bearer header as well as apikey. The new keys aren’t JWTs, so that header is at best redundant and at worst a rejected request. Send apikey only.
  5. The project is paused.
POST /runs 500s with column runs.thread_id does not exist

Your database was created before conversations existed. create table if not exists does not add columns to a table that’s already there, so re-running an older copy of the schema changed nothing.

Fix: run database/schema.sql again from the current repo. The alter table … add column if not exists lines near the top of the runs section do the upgrade, and the whole file is safe to re-run on a fresh database too.

The same applies to runs.messages, runs.provider and runs.model.

409 — “This conversation has reached 10 turns”

Working as intended. Every turn re-sends the whole history, so a thread costs more per question the longer it runs and eventually stops fitting in the context window. MAX_THREAD_TURNS turns that into an error you can act on.

Start a new conversation — the New conversation button in the client, or just omit thread_id on the next POST /runs. Raise the limit only if you know your model’s context window can take it.

The agent won’t answer anything about recent events

Check web_search on /health. If it’s false, no TAVILY_API_KEY is set and the agent only has Wikipedia — which is written after the fact and genuinely can’t help. It should say so rather than guess; if it’s guessing instead, that’s worth knowing.

If it’s true and the agent still won’t search, look at the step log for Searching the web. No such step means the model chose not to call the tool — usually a sign the question doesn’t read as time-sensitive.

Everything worked yesterday, today nothing does

Free Supabase projects pause when they see too little database activity across a 7-day window. Open the dashboard and hit Resume project — you have 90 days before the backup is dropped.

This is the single most common way a workshop project dies. A paused database looks identical to a broken app. A few requests a day is enough to prevent it.

??? failure “extension \"vector\" is not available

Supabase ships pgvector, so this means you’re on plain Postgres elsewhere. Either install the extension or delete the memories block — nothing else depends on it.

404 from Gemini

Model names get retired and the error is unhelpfully generic. Check what’s current at https://aistudio.google.com and set LLM_MODEL.

The default is gemini-flash-latest, an alias that shouldn’t 404. If you pinned a specific version, that’s almost certainly the cause.

429 Too Many Requests

Free-tier limits are per key, per minute, per day, and per token. One agent run is 5–10 model calls.

app/llm.py already retries with backoff. If you’re still hitting it, you’re sharing a key — everyone needs their own.

Empty response from model (finishReason=…)

Usually a safety filter, or the model spent its whole budget thinking. The finishReason in the message tells you which.

A run sits at running forever

Either it’s genuinely still working — check the steps table for the most recent row — or the server process restarted mid-run and took the BackgroundTasks job with it.

That’s the known limitation of BackgroundTasks; see the upgrade path.

Worth adding: mark runs stuck at running for >10 minutes as failed, so the UI stops spinning.

RuntimeError: Agent exceeded 12 steps

A guardrail doing its job. Either the question genuinely needs more research (raise MAX_AGENT_STEPS) or the agent is looping — check steps to see whether it’s repeating the same search.

The agent calls the same tool repeatedly

Your instructions or tool docstrings are too vague. Both are prompt. Tightening the Args: descriptions is usually the whole fix.

??? failure “"Failed to fetch"”

Almost always CORS.

  • Set ALLOWED_ORIGINS to your frontend’s origin, no trailing slash: https://yourname.github.io
  • Redeploy after changing it.
  • Check you’re not calling an http:// backend from an https:// page — browsers block mixed content.
  • allow_origins=["*"] with allow_credentials=True is silently rejected by browsers. If you need credentials, list real origins.
It works locally, fails when deployed

Nine times out of ten it’s CORS or a missing environment variable. Check /health on the deployed URL first — that isolates “is the service alive” from “can the browser talk to it”.

Build fails: uv: command not found

Render’s Python image doesn’t ship uv. The build command needs pip install uv && uv sync --frozen.

Not an issue on FastAPI Cloud, which uses uv itself when it finds a pyproject.toml and uv.lock.

Build fails: no pyproject.toml found

The uv project lives in app/, not the repo root, and both hosts default to the repo root.

  • Render — Root Directory → app. deploy/render.yaml handles this via rootDir: app, but a service created by hand needs the field set.
  • FastAPI Cloud — Root Directory → app when you create the app, or App → SettingsApplication DirectoryappUpdate afterwards.
Build succeeds, service never starts

Check the start command binds 0.0.0.0, not 127.0.0.1. Inside a container that’s the difference between reachable and invisible, and it shows up as a port-scan timeout.

First request takes a minute

Free-tier cold start. Not a bug. Hit /health ten minutes before a demo. Render spins down after ~15 minutes idle; FastAPI Cloud’s Hobby plan scales to zero by default.

Pushed to GitHub, but FastAPI Cloud didn’t deploy

Only pushes to the default branch trigger a deploy — pushes to other branches are ignored, and pull-request preview deployments aren’t supported. Merge to your default branch, or deploy that commit with uv run fastapi deploy.

A FastAPI Cloud variable should have been a secret, and wasn’t

Variables can only be marked Secret at creation. You can’t convert one afterwards in either direction — delete it and add it again with the toggle on.

And if a key was visible in a screenshot or a shared screen, rotate it. That’s cheaper than wondering.

Vercel build fails: no package.json

Root Directory → client. The Vite project isn’t at the repo root, and this is the single most commonly missed field in the whole deploy.

Changed VITE_API_URL, the site still calls the old backend

Vite inlines VITE_* variables into the bundle at build time, so there’s no running process to pick up the new value. Redeploy.

If it’s still wrong after a redeploy, check the Backend URL field in the UI — it’s saved in your browser and overrides the built-in value. Clear it.

The deployed client 404s on refresh or a deep link

The static host is looking for a real file at that path. A single-page app needs every path rewritten to /index.html — that’s what the rewrites block in client/vercel.json does. On another host, find its equivalent.

The GitHub Pages copy loads blank, with 404s on the JS

A project page is served from /<repo>/, not the domain root, so the client has to be built with a matching --base. website/scripts/sync-assets.mjs passes it — if you built by hand, pass it too.


The four things that will silently break a project you’re not touching daily:

What When Fix
Supabase pauses ~1 week idle Ping weekly, or resume manually
The agent host sleeps Render ~15 min idle; FastAPI Cloud scales to zero Hit /health before demos
Gemini key rate-limits per minute / day Your own key; retry with backoff
Disk is wiped every restart Never write to disk; use Postgres

A scheduled GitHub Action is enough to stop Supabase pausing:

.github/workflows/keepalive.yml
name: Keep the stack awake
on:
schedule:
- cron: "0 6 * * 1" # Mondays, 06:00 UTC
workflow_dispatch:
jobs:
ping:
runs-on: ubuntu-latest
steps:
- run: curl -sS --fail --max-time 120 "${{ secrets.BACKEND_URL }}/health"

/health queries the database, so one request keeps both the backend and Supabase awake. Add BACKEND_URL to your repo secrets.

  • Hit /health — confirm ok: true and database: true
  • Run one real query end to end
  • Open the live URL in an incognito window (catches “works because I’m logged in” and cache lies)
  • Open it on a phone
  • Have a recording as a fallback