Troubleshooting

Errors you're likely to hit, what causes them, and how to fix them, followed by the current list of known limitations.

Start with the startup output. fitguard run prints warnings for config problems that don't stop it booting but change how it behaves, such as a key mapped to a user that doesn't exist. Most reports of budgets not working trace back to one of those warnings.

Startup problems

config must define at least one provider

Your config.yaml has nothing under providers:. Run fitguard init, or add a provider block by hand. FitGuard refuses to start with no providers rather than booting into a state where every request fails.

address already in use

Something already holds that port, often another fitguard run you forgot about. Find it with lsof -i :8787, or change port: in your config.

connecting to redis: ...

You set a Redis backend but FitGuard can't reach it. Check Redis is up and that the URL and credentials are right. This is a hard failure by design, since falling back to in-memory would silently return you to per-process budget tracking, which is the situation Redis was configured to avoid.

It won't exit on Ctrl+C

It exits within about 10 seconds. Shutdown stops accepting new connections and waits for in-flight requests to finish. If a request is hung against a slow upstream, the grace period has to expire before the process exits.

Request problems

401 on every request

keys: is non-empty, so you're in multi-tenant mode and a valid Authorization: Bearer <key> is required. Check the key matches an entry in keys: exactly — a trailing newline from a copy-paste is the usual culprit.

429 immediately, even on a fresh budget

The worst-case estimate for this one request exceeds the user's entire daily_limit_usd. Remember the estimate uses max_tokens, or a 4096-token ceiling when you don't set one, so a request with no max_tokens against an expensive model can exceed a small budget on its own.

Fix by raising the budget, setting a realistic max_tokens, or both. See how estimation works.

A budget seems to have no effect at all

Almost always a keys: entry whose user_id has no matching users: entry — a typo, or the user block was removed later. "No budget entry for this user" is treated as "no limit," so this fails open.

fitguard run warns about this at startup. fitguard init can't produce it, since it always writes both together, but a hand-edited config can.

A model 404s or hits the wrong provider

Routing is prefix-based on the model name. If your model doesn't match a known prefix, it defaults to openai. Force it explicitly with a provider prefix:

"model": "groq/my-custom-model"

See the full routing table.

Responses look truncated

Check for the X-AI-Guard-Warning header. Its presence means finish_reason: "length", so max_tokens cut the response off. This normally goes unnoticed because a truncated response is still returned as a success.

On streamed responses that header can't be sent (headers go out before the body). Check the dashboard or logs instead.

Dashboard problems

I can't log in, or I forgot the password

fitguard reset-dashboard-password

No email, no server restart, and you don't need the old password. It also doubles as first-time setup if you skipped the login during init. It rotates the session secret, so existing sessions are signed out too.

Login fails in production but worked locally

The most common cause: the deployed instance is reading a different config.yaml than the one you set the password in. The dashboard account lives in that file, so if the container has a different config mounted (or a fresh one generated at build time), your credentials aren't there.

Confirm the running instance is using the file you think it is with --config, and that it contains a dashboard: block with both session_secret and a user entry.

The dashboard is empty after every deploy

Ephemeral filesystem. Your fitguard.db is being wiped. See persistence and operations.

The dashboard shows only some of my traffic

You're running more than one instance. Cost logging is per-instance because fitguard.db is a local file, so each dashboard shows only what that instance handled. This is true even with budget.backend: redis. See scaling and operations.

The live feed stops updating behind a proxy

The dashboard updates over Server-Sent Events, and nginx buffers those by default. Set proxy_buffering off; — see the VPS HTTPS setup.

database is locked

Multiple processes are pointed at the same fitguard.db. Give each instance its own data_dir, or use genuinely concurrent-safe shared storage.

Known limitations

Current gaps, so you can check them against your requirements before adopting it.

A dropped stream ends without a formal error

Fallback protects you only before the first byte reaches the client. Once a provider's stream is committed, there's no way to retroactively signal failure if it dies partway through. Whatever was streamed is still logged and costed for what it actually delivered.

Embeddings don't use the fallback list

One attempt against the requested model's provider. Chat fallback models aren't valid embedding models, and swapping models partway through an indexing run would write mismatched dimensions into your vector store, so an error is returned instead.

Budget state is per-process unless you use Redis

And cost logging is per-process regardless. See scaling and operations.

Cost estimation is a heuristic

Not exact token counting — it's a pre-flight ceiling used to reserve budget before the upstream call, not the actual token count. It can reserve more than a request ends up costing; that's intentional, since the real completion size isn't known until the response comes back. Text is sized at ~4 chars/token. Images contribute a flat, deliberately generous per-image estimate (pixel dimensions aren't decoded), so a multimodal request no longer reserves as if its images were free.

The pricing table drifts

It's a manually maintained snapshot of public pricing, so it can go stale when a provider changes prices. Correct a stale entry, or add a model the table doesn't know about yet, with a pricing: block in config.yaml — no fitguard release needed. See configuration. Embedding models are still priced input-only (there's no completion side to an embedding request).

The dashboard has one account, not roles

No read-only or viewer accounts yet. Anyone with that one password sees and does everything the dashboard exposes.

No Assistants or Responses API

/v1/chat/completions and /v1/embeddings only.

Still stuck

Open an issue on GitHub. Include your fitguard run startup output (it lists providers, cache, and budget config, and redacts credentials), the status code and type from the error body, and your config with keys removed.

Esc