Litestore · Developer guide

Troubleshooting

Learn the failure modes this codebase actually produces, what causes each one, and which plausible-sounding cause to rule out.

Every entry here points at a specific line of behaviour in the source. Where a common guess is wrong, it says so.

The entries are grouped by where the failure shows up — sign-in, the database, checkout, the checks, background work — rather than by which subsystem turns out to be at fault. The symptom is what you have when you start looking.

Sign-in

Magic links do not arrive.

Sign-in sends a magic link through Resend. That is the whole delivery path, which is why RESEND_API_KEY and RESEND_SENDER_EMAIL are required variables rather than optional ones.

Check the Resend sender domain first. A key that validates and a sender address that Resend will not send from produce the same visible result: nothing in the inbox.

Do not assume Redis. checkRateLimit fails open, so an unreachable Redis degrades rate limiting without blocking a sign-in. Redis is genuinely required in production, but it is not the cause of a missing email.

Google sign-in is absent from the page.

The provider registers only when both AUTH_GOOGLE_ID and AUTH_GOOGLE_SECRET are set.

One without the other yields no button and no error. Nothing fails; the provider is simply never added, so the sign-in page renders without it.

Database

The first request hangs, then everything is slow.

services/db.ts sets:

connectionTimeoutMillis: 30_000

A database that is reachable but far away, or a pool that is exhausted, spends that entire budget before failing. The request does not error quickly — it waits.

Check the region pairing between the app and Postgres first. Distance between the two is the common cause, and it shows up as slowness rather than as a connection error.

bun run db:seed fails.

The script is declared as tsx prisma/seed.ts, but tsx is not a dependency of this repository. The script body names a runner that is not installed.

Run the seed directly instead:

bun prisma/seed.ts

That executes the same seed file without the missing runner.

Prisma disagrees with the schema after pulling.

The generated client is out of date with schema.prisma. Regenerate it:

bun run db:generate

postinstall already does this, so a fresh bun install also fixes it.

Checkout

Starting a checkout throws.

getPaymentProvider() throws when STRIPE_SECRET_KEY or STRIPE_WEBHOOK_SECRET is missing.

This is a narrow failure, not a broken store. Browsing and carting work without Stripe; taking money does not.

An unknown provider name fails loudly.

That is deliberate. The name arrives from the storefront checkout action as an optional string, and an unrecognised value throws rather than silently resolving to Stripe.

A silent fallback would take a payment through a processor the caller did not ask for. Throwing keeps that from happening.

Build and checks

Typecheck runs out of memory.

bun run typecheck already sets its own ceiling:

NODE_OPTIONS='--max-old-space-size=12288'

If you invoke tsc directly you will need to set the same ceiling yourself. The build uses 6144.

bun run lint passes but bun run verify fails.

They are not the same gate. lint is the short working loop and skips five checks; verify is the full one — see The Harness.

A clean lint therefore says nothing about the five checks it did not run. Run verify before you conclude the tree is green.

A failing contract test is a signal, not an obstacle

scripts/check-critical-tests.ts ratchets coverage over the critical paths. A contract test that starts failing is reporting that a guarantee changed. Deleting it turns off the alarm without fixing the fault.

Background work

Scheduled work never runs.

Inngest is not configured by env.ts at all. There is no variable whose absence will tell you about it, and no startup error.

An unconnected store therefore serves normally while every cron and workflow stays idle. Connect the deployment to Inngest and the schedules register on first sync.

Which kind of failure is this

Two patterns run through the list above.

Anything on the path where money moves fails loudly. getPaymentProvider() throws on missing Stripe keys, and an unrecognised provider name throws rather than falling back.

Everything optional degrades quietly instead. An unreachable Redis fails open, Google without both variables shows no button and no error, and an unconnected Inngest leaves every schedule idle while the store serves fine.

So the useful first question is not "what is broken?" but:

Is the feature erroring, or is it simply absent? An absent feature is usually a missing environment variable, not a bug.

On this page