fespalier-troubleshooting
Diagnosing fespalier failures where the message or the symptom does not name the cause — every fsp diagnostic grouped by area (folder names, view files, binding and types, data.dart, guards, tabs and
Files25
View on GitHubSKILL.mdskills/fespalier-troubleshooting/SKILL.md
GitHubfespalier-troubleshooting
Verified against fespalier
fdaad6b8(2026-10-08), release v0.13.0. These skills ship in the fespalier repository, and CI checks them against its code on every change. Version-sensitive claims say the release they became true in; if your app pins another fespalier, trust that release's code over this page. See Versions.
Start here: symptom to cause
| Symptom | Very likely cause |
|---|---|
flutter analyze: The function 'XRoute' isn't defined |
lib/app.g.dart is stale. fsp check passes anyway (it compares nothing). Run fsp gen |
fsp gen fails and app.g.dart does not change |
By design: errors leave the old file. Read the first error; fix lib/app/, never the generated file |
flutter analyze: Undefined class 'ActionForm' (or 'FieldCodec', 'ActionField'...), or Target of URI doesn't exist: 'package:fespalier_forms/fespalier_forms.dart'; or fsp: `form()` is the form of `action()`, and since 0.11.0 forms are in the fespalier_forms package |
Forms moved to fespalier_forms in 0.11.0. Add it next to fespalier at the same url and ref, flutter pub get, restart fsp watch or fsp dev if one is running, fsp gen, and import the package where your code names a form type: references/diagnostics-data-and-hooks.md, fespalier-migration |
A URL shows Nothing at /path (or your not_found.dart) |
No route matches: case (/Products), an unparsable segment, $$rest needs one part, or a guard loop. Since 0.7.0 fsp check warns about a string path that is a literal in lib/ (next row) |
A guard or redirect.dart threw, and the screen shows the error view (or not-found): EXCEPTION CAUGHT BY FESPALIER, then was thrown while running a guard or redirect.dart (since 0.21.0) |
The guard itself: the value it did not expect, the provider it reads, or the store it reads. Fix it, or let it answer instead of throwing: references/diagnostics-data-and-hooks.md, "Runtime errors that are not from fsp" |
no route matches `/x`, so it shows not-found [unknown_path] |
A string path in lib/ matches no route (a typo, a renamed folder): fix it, use a typed route, or // fsp:ignore unknown_path (0.7.0): references/diagnostics-config-and-meta.md |
`/products/abc` reaches /products/:id, but `abc` is not an int, so it shows not-found [unknown_path] |
A string path in lib/ reaches a route whose segment type cannot read it (an int, an enum, a catch-all part...): fix the literal, use a typed route, or // fsp:ignore unknown_path (0.8.1): references/diagnostics-config-and-meta.md |
The login page itself shows Nothing at /login |
The guard covers the login page: its from= redirect loops until go_router gives up. Move login/ outside |
X is unreachable: $a/page.dart (/:a) comes first ... |
A (group) holding a dynamic route cannot be sorted around a dynamic sibling outside it |
A deep link builds a page you did not open (/orders/1/refund under .../refund/confirm) |
A page is the parent of the routes below it. To make one a sibling with a compound path: const nest = false; in its route.dart (0.4.0), see fespalier-routing |
`nest = false` takes ... / `nest = false` is about ... |
nest with nothing to leave, or a layout it would escape: references/diagnostics-config-and-meta.md |
A page keeps its state (a counter, a text field) when /c/1 becomes /c/2, or loses it when ?page=2 changes |
go_router keys a page by its path template, so it is kept by default. remount (0.6.0) in the pubspec or a route.dart says when it starts again: Remount.onSegments on a segment change only, onLocation on the query too, see fespalier-routing |
A deferred page flashes loading.dart, or shows it for ever on the web, or a test says The code of ... is not loaded, and a widget test can't load it while it pumps. |
const deferred = true; (0.7.0) loads the page's code on demand: preload it, await AppRoutes.loadDeferred() before runApp off the web, and in a test use pumpRouter or await tester.runAsync(AppRoutes.loadDeferred) before your own pumpWidget: references/flutter-pitfalls.md, fespalier-testing |
`deferred` ... / `X` is declared in this page.dart, which is deferred ... |
deferred not a true/false literal or declared twice, or an enum or extra class declared in a deferred page: move it to a file of its own, or const deferred = false;: references/diagnostics-config-and-meta.md |
`remount` ... / `remount` has no effect here ... |
remount not const, not one of the three values, declared twice, or a transition.dart that takes no key: references/diagnostics-config-and-meta.md |
can't fill `x`: it isn't a segment of this path ... |
A required parameter that is not a $segment, data, or an optional nullable query parameter |
`$id` is String in a/data.dart:2 but int here |
Files disagree on one segment's type: change it in every file that asks for it |
A page gets null or defaults |
An optional nullable parameter became a query parameter; check fsp routes --json params |
lib/app.g.dart is gone after dart run build_runner build (or fsp dev with a build_runner task), or warning: lib/app.dart: `build_runner` can delete `lib/app.g.dart` ... |
A lib/app.dart and a builder that writes .g.dart files (json_serializable, riverpod_generator): build_runner claims lib/app.g.dart for it and deletes it, with nothing logged. Set output: lib/routes.g.dart or rename lib/app.dart (fsp warns since 0.17.0): references/flutter-pitfalls.md |
A freezed Note? extra is null after a web reload or a restore, though extra_codec.dart lists Note; or fespalier: a _Note isn't registered in the ExtraCodec; ... in the debug console |
freezed's const factory Note(...) = _Note makes a private _Note, which ExtraCodec({Note: ...}) (an exact lookup) never matches. Use ExtraCodec.of([ExtraType<Note>(...)]) (since 0.17.0): references/flutter-pitfalls.md, fespalier-routing |
ListTile background color or ink splashes may be invisible |
Page under the layout's Scaffold: wrap it in Material(type: MaterialType.transparency, ...) |
| Routes do not animate on go_router 18 | No root transition.dart; Flutter's MaterialApp is not the one go_router 18 looks for |
| Every layout shell animates after upgrading to 0.3.0 | A transition.dart at or above a layout animates its shell now (fespalier-migration) |
expected a public widget class / found the widget class X and the function |
A view file needs one public widget class or one function, not both |
| Deep link to a dialog opens a blank screen | The dialog route has no parent page above it |
A test hangs, or A Timer is still pending |
Awaiting read/refresh under a fake clock, booting twice with delayed fakes, or (0.7.0) a deferred page's loadLibrary() outside runAsync (fespalier-testing) |
fsp upgrade: this fsp is the one `dart run fespalier` keeps ..., it cannot look up the latest release, or it refuses a download (checksum, pins, version, unwritable folder) |
The binary is the launcher's cached copy (change the ref: instead), or curl/the network is missing: references/diagnostics-dev.md |
currentLocation is the old location after a push |
fespalier 0.3.0 or older: it followed go only. Upgrade (0.4.0 follows push), or read currentConfiguration.last.matchedLocation |
No fespalier tab in DevTools, or its status line says This app does not use fespalier 0.7.0 or later, or runs in release mode |
A release build, fespalier before 0.7.0, or --dart-define=fespalier.devtools=false: references/devtools.md |
fsp telemetry fails (needs Docker with Compose v2.20, needs a running Docker daemon, docker compose up failed, the dashboards were not imported), or the telemetry dashboards are empty |
Docker is missing or stopped, a port is taken, an older fsp telemetry left OpenObserve restarting (since 0.21.0), or the app sends nothing (telemetry not on, a web app in runGuarded on otel_zone before v0.8.1, a phone without --lan, a release build): references/diagnostics-telemetry.md |
fsp dev says more than one device, no device to run on, `before` step 1 of `dev` failed, or an app that looks stale after a hot reload; fespalier.tasks messages |
A device to name after --, a before step that fails, a route change that needs R, or a mistake in tasks: (0.9.0): references/diagnostics-dev.md |
fsp create says fsp create takes the new app's folder as its argument, is not empty, does not make a Dart package name, unknown feature, Flutter ... is too old, or flutter pub get failed with The app is in ... |
A --project, a folder that is not empty, a name that is not a package, a feature that does not exist (--list-features), an old or missing Flutter, or a step after the move that you finish by hand (0.15.0): references/diagnostics-create.md |
invalid pubspec.yaml: fespalier: unknown field ... |
A misspelled or unknown key in the fespalier: section |
fespalier_auth: no AuthConfig. Return restoreAuth(config) from startup() ... |
authConfig was read before the app set it: return restoreAuth(authSetup()) from startup(), or pass overrides: fakeAuth(...) to pumpRouter (0.9.0): references/diagnostics-auth.md |
Signing in does not move the user off the sign-in page, or the first frame is blank on a deep link to a signed-in page, or AuthRejected: the server refused the session (invalid_grant) |
The sign-in route has no redirectIfSignedIn guard, startup() does not return restoreAuth(...), or the refresh token was refused (a signed-out user, not a bug): references/diagnostics-auth.md (0.9.0) |
OidcException: the ID token was issued by ..., not ..., the redirect's state does not match ..., the server answered the sign-in with error invalid_request: Missing parameter: code_challenge_method, or Keycloak's page Invalid parameter: redirect_uri |
OidcBackend (0.9.0): the issuer is Keycloak's configured hostname (an emulator reaches the host as 10.0.2.2), a second sign-in was open, the client has no PKCE S256, or redirectUri is not in its valid redirect URIs: references/diagnostics-auth.md |
invalid_request: DPoP proof has already been used, or DPoP proof is not active |
A RetryClient under the session client re-sends one proof (wrap authHttpClient in it instead), or the device clock is off (0.9.0): references/diagnostics-auth.md |
Target of URI doesn't exist: 'package:fespalier_dio/http.dart' after an upgrade, a fespalier_http pub-resolution error, fespalier_dio: PUT /me failed and was about to be sent again ..., WriteNotRetried: ..., DioException [request cancelled], or a server's 422 that does not show under its form field |
fespalier_dio (0.9.0) and fespalier_http (0.15.0; fespalier_dio/http.dart is gone, change the import): the guard refused a retried write (this is it working), WriteGuard is not first, a page was left mid-request, or withFieldErrors() found no field: references/diagnostics-errors-and-http.md |
fespalier_sign_keypair: the web has no secure element for a DPoP key ... (DpopUnavailable), or DpopProofInvalid: ..., or a user who is signed out at every web reload with DPoP |
DpopProof.device() refuses the web, Windows and Linux by default: pick DpopFallback.software or bearer on purpose; verifyDpopProof names the check that failed; a software key in memory is lost on a reload (0.9.0): references/diagnostics-auth.md |
A flagged entry still shows after its flag turned off, every flag is its fallback, or fespalier_flags: reading <key> threw, so its fallback (<fallback>) is used: <error> |
fespalier_flags (0.9.0): the guard has follow: false or is the old ProviderContainer form, the entry NavRefused.show; startup() has no flagSource override, or a key typo (FakeFlags.strict); a read threw: references/diagnostics-flags-storage-network.md |
A restart shows loading.dart, not the saved value, or fespalier_storage: could not open shared preferences, so nothing is saved: Bad state: ... |
fespalier_storage (0.9.0): no fakePrefsStore() in the test (open() is null), or startup() passes a Future or no dataCacheStorage; S1 to S7: references/diagnostics-flags-storage-network.md |
refetchOnReconnect never reloads, an offline banner stays up, or Flutter's report while activating platform stream on channel dev.fluttercommunity.plus/connectivity_status |
fespalier_connectivity (0.9.0): no reconnectSignal override, a hand-written signal with no resume re-check, or a widget test with no FakeConnectivity (C3): references/diagnostics-flags-storage-network.md |
A page that watches a fespalier_frb topic keeps its old value while the core is busy, or reloads on every event |
A key whose events come faster than its data() reloads (0.21.0): give its topic a ChangeBudget (fespalier-data, its change-feeds reference) |
fespalier_image: "products/3.jpg" is not a URL and no image CDN is configured ..., or The following ImageUrlError was thrown while building the URL of the image ..., or an image that flickers in a hero, loads twice or is blank on the web |
fespalier_image (0.9.0): no imageCdnProvider override in startup(), a misconfigured builder, a hero without ResponsiveImage.flightShuttle, a precache at another width than the page, or CORS missing on the image server: references/diagnostics-images.md |
Invalid argument (tracesSampleRate): must be between 0 and 1: 1.5, or fespalier_sentry: tracing: true needs Sentry to sample transactions ..., or nothing in Sentry, or two ui.load per screen |
fespalier_sentry (0.9.0): a sample rate outside 0 to 1; tracing: true on an SDK that samples nothing; an empty DSN or no install; a plain SentryNavigatorObserver next to the sink: references/diagnostics-sentry.md |
A hook that never fires, or while running onEnter of ..., or no spans, or a blank Flutter web app with otel_zone |
observe.dart and telemetry (0.8.1): hooks run after the frame; a hook that throws is reported; telemetry needs telemetry: true and an installed sink; OtelZone.runGuarded blanked the web before otel_zone v0.8.1. See diagnostics-observability.md |
router.go('/a'); router.push('/b'); ends with /b on the old location (/home > /b) or currentConfiguration is a frame late; or an adapter fails to compile with can't define method 'onEnter' and have field 'FespalierAdapter.onEnter' |
An adapter has an onEnter (a getter since 0.16.0), so go_router parses every navigation asynchronously and loses a go() followed by a push() (before 0.16.0 any adapters: entry did); an onEnter method is now an error: references/diagnostics-app-main.md, fespalier-migration |
No TranslationScope found above this widget., No MaterialLocalizations found., raw keys such as products.title on screen, the wrong language, or Target of URI doesn't exist: 'package:fespalier_tolgee/fespalier_adapter.dart' |
fespalier_tolgee (0.10.0): MaterialApp.router got the router and not TranslationScope.routerConfig(...), no GlobalMaterialLocalizations.delegates, no translationsConfig override (every key is itself, silently), or adapters: names the package (it has no adapter): references/diagnostics-tolgee.md |
fespalier_cratestack: override crateStackTransport ..., crateStackScope is null (nobody is signed in), serve needs a fetch unless the policy is localOnly, CrateStackNoLocalData(<key>), a queued write that never sends or ran twice, or another page's request cancelled |
fespalier_cratestack (0.10.0): a missing override in startup(), signed out, no autoSync in the root layout, a server that namespaces idempotency keys by the Authorization header, or a provider built inside ref.cancellable: references/diagnostics-cratestack.md |
fespalier_download_background: an upload was not started, because its entry could not be saved ..., ... the uploads registry could not be loaded ..., an upload Failed(other) after a 500 that retry refuses, or Failed(storage) on every upload after a restart |
fespalier_download_background (0.16.0): the registry that remembers a write could not be saved or read, so nothing was sent; or a 5xx is an unknown outcome for an upload without a key: references/diagnostics-downloads.md |
fsp <version> isn't cached and the download failed (offline?) |
dart run fespalier with an empty cache and no network: run once online, or set FSP_BINARY |
How fsp reports
Diagnostics print with a code frame at the file and line of the parameter or
declaration at fault, error: or warning:; with --json each is one JSON object
(file, line, column, severity, message). Errors stop generation and leave
app.g.dart as it was (N error(s); lib/app.g.dart left unchanged, exit 1); warnings do
not. A file the parser cannot fully read is a warning (couldn't fully parse this file; ...): fsp worked with what it could, and the Dart compiler reports the exact
error. fsp routes and fsp check print the same diagnostics. Since 0.7.0 a diagnostic can
also point at a file outside the app folder (lib/screens/home.dart): the string-path lint
reads every Dart file under lib/, and --json gives that file's path from the project root
like any other.
Fix the first error first: later ones can be consequences of it.
The catalogue
Each page lists the message text, the cause and the fix, taken from the source and
confirmed by running fsp on a tree that triggers it.
Broken trees. The messages of fifteen of the commonest diagnostics are pinned by
trees that trigger them: the smallest app folder for each, with the output of
fsp check --json as a golden, under
cli/tests/fixtures/diagnostics/.
cli/tests/diagnostics.rs fails when fsp words one of them differently or
when the reference page stops quoting it, so a quote on the pages named in that README is
current. To see a message for yourself, copy a tree and run fsp check on it.
| Area | Reference |
|---|---|
| Folder names, view files, duplicate URLs, route names, unreachable routes | references/diagnostics-tree.md |
Filling parameters, segment and query types, enums, extra |
references/diagnostics-binding.md |
data.dart (three forms), action.dart, guard.dart, redirect.dart, transition.dart, present.dart |
references/diagnostics-data-and-hooks.md |
Layouts, tabs, tabOptions, container, navigator.dart |
references/diagnostics-layouts-and-navigators.md |
observe.dart (0.8.1), the telemetry key, fsp new --observe, telemetry at run time, otel_zone and pub resolution |
references/diagnostics-observability.md |
nav.dart: label(), order, headings (0.8.1); fespalier_adaptive (0.9.0): no bottom bar, a rail in a test, a tab that lost its state on a resize |
references/diagnostics-menus.md |
The generated main() (0.8.1): app.dart, startup.dart, splash.dart, main: |
references/diagnostics-app-main.md |
Pubspec config, fsp links, fsp maestro and fsp test, the CLI and launcher, meta.dart, route.dart, localized paths, extra_codec.dart |
references/diagnostics-config-and-meta.md |
Stale app.g.dart, the ink assertion, silent query parameters, case, go_router 18, the web |
references/flutter-pitfalls.md |
fespalier_auth (0.9.0): the run-time messages of the session, the guards, refresh, the HTTP client, OpenID Connect, Keycloak and DPoP; a sign-in that goes nowhere |
references/diagnostics-auth.md |
fespalier_dio (0.9.0) and fespalier_http (0.15.0): the run-time messages, the removed fespalier_dio/http.dart and the pub resolution of fespalier_http; of the write guard and of cancellation; a write that is retried or not, a 422 that misses its field |
references/diagnostics-errors-and-http.md |
fespalier_image (0.9.0): the run-time messages of the CDN builders and the widget, an image that flickers, loads twice, is blurry, a 403 from EmgR, blank on the web |
references/diagnostics-images.md |
fespalier_flags, fespalier_storage and fespalier_connectivity (0.9.0): the messages of a flag source, AsyncFlags, FakeFlags.strict, the two storages and the connectivity source; a flagged entry that stays, a restart that shows no saved value, a reconnect that never loads |
references/diagnostics-flags-storage-network.md |
fespalier_tolgee (0.10.0): the run-time messages of the scope, the base locale, malformed ICU messages, the strict test fake, the in-context editor, the Flutter errors around them; raw keys, the wrong language, texts that never update |
references/diagnostics-tolgee.md |
fespalier_cratestack (0.10.0): the run-time messages of the transport, the scope, serve, the intent queue, the stores and the fakes; a queued write that never sends, ran twice, is stuck failed or conflict, or a request cancelled with another page |
references/diagnostics-cratestack.md |
fespalier_download_background uploads (0.16.0): the two debug lines of a registry that cannot be saved or read, UploadStoreException; an upload that ends Failed(other) after a 5xx and is not retried, Failed(storage) on every upload without a key |
references/diagnostics-downloads.md |
fespalier_sentry (0.9.0): the run-time messages (a bad sample rate, tracing: true on a non-sampling SDK), nothing in Sentry, no otel.trace_id, two ui.load per screen, the first screen on Android and iOS, an event from a page's first frame that names the wrong page |
references/diagnostics-sentry.md |
fsp telemetry and the dashboards (0.8.1): every message, the importer's lines, empty dashboards |
references/diagnostics-telemetry.md |
fsp create (0.15.0): every refusal before anything is made, Flutter's checks, the steps after the move, a leftover .dir.fsp-create-<pid> folder |
references/diagnostics-create.md |
fsp dev, fsp build, fsp run and tasks: (0.9.0): every config message, the run-time messages, devices, quitting, symptoms |
references/diagnostics-dev.md |
| The DevTools extension (0.7.0): setup, every status line, the traps, the protocol | references/devtools.md |
| README claims that were false in 0.3.0 (and what to do instead) | references/known-wrong-docs.md |
Three things to do before anything else
fsp gen, thenflutter analyze. Most "it does not work" is a staleapp.g.dartor an unread error line.fsp routes(or--json): it shows which file serves which URL, which parameters are path and which are query, and every tag (data,action,guard,observe,layout,transition,present,root,redirect,siblingfor anest = falseroute,remount, anddeferredfor a page whose code loads on demand).- Open the
.rsor.dartbefore the docs when they disagree:known-wrong-docs.mdlists the ones that have bitten, and the version that fixed them.
Grep narrows; read confirms. A missing message does not mean a rule does not exist:
several checks live in emit.rs (ordering, tab starts, root children) rather than
resolve.rs, and a few behaviours (the guard-skipping rule, the silent query parameter)
produce no message at all.