Troubleshooting
A guide to resolving common development problems.
Start here
Most "this used to work" problems are a missing tool or one that is too old, so before you read any further, ask the CLI:
floot requirements checkIt probes Git, the Dart SDK, FVM, Flutter, Docker, the Supabase CLI, Deno, jq, OpenSSL, the Stripe CLI and your platform toolchains, and reports which of them are absent or below the version this project needs. It exits non-zero when anything is wrong, which makes the repair a one-liner:
floot requirements check || floot requirements installfloot requirements install installs the missing ones through your host's
package manager, asking before it touches anything. See the
CLI reference for the full flag list.
Environment
Every Edge Function fails to boot
ENV VALIDATION FAILED: {
formErrors: [],
fieldErrors: {
SB_SECRET_KEY: [ "Invalid input: expected string, received undefined" ]
}
}The whole environment is validated the moment any Edge Function starts, and
throws if the shape is wrong. Because every function shares that validation,
one missing variable takes them all down at once, which is the point: a
half-configured backend should fail at functions serve rather than at the
first request that needed the variable.
A blank value counts as unset
A value of the empty string is treated as unset before validation runs, so
SMTP_USER= and a completely absent SMTP_USER are the same thing. That is
deliberate, it is what lets the conditionally-required variables sit in the
file as empty placeholders, but it does mean the cure is always to fill a
value in, never just to add the line.
Read the fieldErrors keys: each one names a variable in
supabase/.env.local. These are required in every project, whatever it was
generated with:
DATABASE_WEBHOOK_SECRET, DENO_ENV, ENABLE_MULTI_TENANCY, SMTP_FROM,
SMTP_HOST, SMTP_PASSWORD, SMTP_PORT, SMTP_USER, SB_URL,
SB_PUBLISHABLE_KEY, SB_SECRET_KEY, SB_AUTH_EXTERNAL_REDIRECT_URI,
SB_SEND_EMAIL_HOOK_SECRET, SB_STORAGE_URL, WEBSITE_URL.
The SMTP pair is prefilled, not blank
floot create seeds SMTP_USER and SMTP_PASSWORD with a placeholder,
because the local mail catcher that supabase start runs ignores credentials
anyway. If you blank them while wiring up a real provider, the functions stop
booting until you fill both back in; the validator requires them
unconditionally, whatever the host.
The rest are optional until a feature flag turns them mandatory. The message
says so in as many words: STRIPE_SECRET_KEY is required when ENABLE_STRIPE is "true".
| Flag | Variables it makes mandatory |
|---|---|
| Requires ENABLE_STRIPE | STRIPE_SECRET_KEY, STRIPE_WEBHOOK_SECRET, APP_URL_SCHEME |
| Requires ENABLE_REVENUECAT | REVENUECAT_SECRET_API_KEY, REVENUECAT_WEBHOOK_SECRET |
| Requires ENABLE_SEAT_BASED_BILLING | STRIPE_PORTAL_CONFIRM_CONFIGURATION_ID |
Requires ENABLE_SEAT_BASED_BILLING
Seat-based billing is off in a new project. Set `ENABLE_SEAT_BASED_BILLING=true` in both `.env.local` and `supabase/.env.local` to use this section.
Seat-based billing also validates its own prerequisites, and reports them against itself rather than against the variable that is missing:
ENABLE_MULTI_TENANCY is required when ENABLE_SEAT_BASED_BILLING is "true".
ENABLE_STRIPE is required when ENABLE_SEAT_BASED_BILLING is "true".
Both of those mean the same thing: you turned seats on without turning on what they are built from. Seat billing's moving parts are on Seat-Based Billing.
A release build throws on startup
Exception: Error loading .env: <the underlying failure>
Release builds read `.env`, which is git-ignored and is not created by
`floot create`. Copy `.env.local` to `.env`, replace the local values with your
production ones, then add `- .env` under `flutter: assets:` in pubspec.yaml:
an asset that is not listed there is not bundled.The message names the remedy, but here is the reasoning behind it.
Debug and profile builds load .env.local; release builds load .env. Every
.env* file is git-ignored, and floot create writes only .env.local, so
until you create .env yourself, the first release build you run throws here,
before a single frame is drawn.
There is a second way to hit this with the file sitting right there on disk:
Flutter reads it as a bundled asset, so .env must also be listed under
flutter: assets: in pubspec.yaml. floot create lists .env.local and not
.env, for the same reason it writes one and not the other.
Fix
Both halves are covered step by step in Building for
release: copy .env.local to .env,
swap in your production values, and add .env to the asset list.
Database webhooks fail after you change a secret
401 INVALID_SIGNATURE. Failed to verify the request.
Five values in supabase/.env.local are also read from the Supabase Vault by
SQL, under lowercase names, because Postgres cannot see your environment file:
supabase/.env.local | Vault secret |
|---|---|
ACCOUNT_DELETION_EMAIL_PEPPER | account_deletion_email_pepper |
DATABASE_WEBHOOK_SECRET | database_webhook_secret |
MFA_RECOVERY_CODE_PEPPER | mfa_recovery_code_pepper |
SB_STORAGE_URL | storage_url |
SB_URL | supabase_url |
The [db.vault] block in supabase/config.toml seeds them locally, but only
when the database volume is created. A plain supabase stop && supabase start restores the saved volume and skips seeding entirely, so the Vault keeps
whatever it was seeded with the first time.
That is what the 401 above is: the database signs the webhook with the stale
database_webhook_secret while the Edge Function verifies it against the fresh
DATABASE_WEBHOOK_SECRET from supabase/.env.local. Every database-driven
function is affected at once.
Fix
Destroy the volume so the next start reseeds the Vault.
supabase stop --no-backup && supabase start--no-backup throws away your local database. Migrations and seeds rebuild
the schema, but any data you typed in by hand is gone.
Hosted projects seed nothing
Nothing creates these five for you on a hosted project. Create them yourself
under those exact lowercase names, Studio → Vault, or vault.create_secret(),
or the same webhooks fail there too.
Supabase
The initial migration refuses to run
ERROR: realtime.messages does not have RLS pre-enabled. This project requires the realtime image shipped with Supabase CLI >= 2.116.0 (older images leave RLS disabled, which would expose private broadcast topics). Upgrade the Supabase CLI and rerun.
This is a deliberate guard in supabase/migrations, not a bug. Since 2.116.0
the realtime image owns realtime.messages and ships it with row-level security
already enabled; postgres may add policies to it but can no longer ALTER it.
On an older image RLS stays off, the subscription-topic policies protect
nothing, and any signed-in user can subscribe to any topic, so the migration
stops rather than build that.
Confirm the version you are on.
floot requirements check supabaseUpgrade the Supabase CLI to 2.116.0 or later.
Start from a clean volume, so the new realtime image is pulled and the migration re-runs from the beginning.
supabase stop --no-backup && supabase startAndroid
Emulator is not reaching the backend
Connection refused - errno 101
SocketException: Connection failed (OS Error: Network is unreachable, errno = 101)…
Fix
Turn the Wi-Fi off on emulator.
Connection refused - errno 111
SocketException: Connection refused (OS Error: Connection refused, errno = 111)…
This occurs because the Android emulator does not recognize localhost as the
host machine. Instead, you should use the IP address 10.0.2.2. The port stays
54321, that is the local Supabase API, which is what the app talks to.
SUPABASE_URL=http://10.0.2.2:54321Note
This is the app's .env.local at the project root, not
supabase/.env.local. Change .env the same way if you are pointing a
release build at a local backend. On a physical device neither 127.0.0.1 nor
10.0.2.2 works; use your machine's LAN address, from ipconfig getifaddr en0 on macOS or ipconfig on Windows.
If you would rather keep 127.0.0.1 and avoid a separate Android-only
SUPABASE_URL, forward the port instead; see
Useful Commands.
Flutter
Widget Testing
Pending timers
When running widget tests, you might encounter the following error:
The following assertion was thrown running a test: A Timer is still pending even after the widget tree was disposed.
…'!timersPending’
This typically occurs when an animation or asynchronous operation hasn't completed. Below are two approaches to resolve this using tester.runAsync.
Run the pump and settle in that function.
await tester.runAsync(() async => tester.pumpAndSettle());Apple build failures (Swift Package Manager)
Floot builds all iOS/macOS plugins with Swift Package Manager; there is no
CocoaPods integration, and your project has no Podfile. If you see an error
mentioning pod install or CocoaPods's specs repository, it is coming from a
stale build directory rather than from Floot.
Note
If you are encountering this issue for your Mac app, replace every occurrence
of the ios path with macos in the steps below.
Fix
Run flutter clean to remove build artifacts.
Delete ios/Flutter/ephemeral so the generated Swift package is
rebuilt from scratch.
Run flutter pub get to fetch Dart dependencies.
Run flutter run to build and launch the app.