Repository navigation
Service worker swallows server-issued redirects, so ?farm= never reaches the login form #778
Description
Activity
- addedbugSomething isn't workingSomething isn't workingarea:frontendReact/Vite web clientReact/Vite web clientseverity:p3Defect: degraded or partial behaviourDefect: degraded or partial behaviourepic-1.6Phase 1.6 — Multi-farm tenancyPhase 1.6 — Multi-farm tenancypriority:tier3Real product weight, real costReal product weight, real costand removedepic-1.6Phase 1.6 — Multi-farm tenancyPhase 1.6 — Multi-farm tenancy
on Sep 12, 2026 Triaged in the 2026-09-13 issue cleanup. Kept open, labelled
priority:tier3, and the staleepic-1.6label removed — epic #530 closed today (Phase 1.6 is complete bar #556), so that label pointed at a closed parent.Why it stays open rather than being fixed now: this issue's "possible directions" section correctly says the tradeoff is the owner's, and it is a real one. Option 1 costs an offline-capable root, which is a PWA decision, not a bug fix — the app ships an installable PWA baseline (#142) and a service-worker guarantees check runs in CI (
ci.yml, "Verify service-worker guarantees"). Choosing wrong here trades a narrow prefill gap for a broader offline regression.Why tier3 rather than higher: the affected population is narrow and the condition self-heals. It needs all of — a service worker from an earlier build, never having signed in with a farm code, and arriving at the root rather than a
?farm=link. One successful sign-in populatescluckwork.farmCodesand the cache path takes over permanently.Two things for whoever picks it up, so they are not re-derived:
theme-init.jsis affected identically and is easy to forget. It reads the same parameter for per-farm branding (cluckwork.brand:<slug>, SPA: per-farm brand palette, keyed by slug so it survives pre-paint #586), so an upgraded install gets the default palette where a fresh one gets the farm's. A fix that restores?farm=forLogin.tsxbut not for the branding script fixes half the symptom.- The reproduction needs a real installed service worker — a private window is the control, not the test. A hard reload does not change the outcome, which is the detail that makes this look like a server bug when it is a client one.
Nothing about the analysis above changes; it is accurate and was verified against
sw.js'sNavigationRoutedenylist, which excludes only/apiand/health.Closing as won't fix — the analysis in the issue is correct, the fix is not, and the reason is worth recording.
Why the listed directions were all declined. Option 1 costs an offline-capable root, which is a PWA regression traded for a narrow prefill gap — the same reasoning as the 2026-09-13 triage. Option 2 needs a configured default farm code, and no such constant exists to configure: this deployment serves several farms (#530), so there is no single right answer to bake into a frontend build or to stamp from the edge. Option 3 is what the code already does.
Two alternatives were examined after triage and also do not work, so they are recorded here rather than re-derived later:
- A request header instead of a URL parameter. Browsers cannot attach a custom header to a navigation — only
fetch/XHRcan, which is whyX-Cluckwork-Accountworks on/auth/refreshand cannot work here. Independently fatal: the service worker intercepts the navigation before the server sees the request, so a request header has no opportunity to influence the response. A URL parameter is a thing that travels there; the trip is the thing that does not happen. - A response header on the cached
index.html. This one is mechanically viable — the worker could read it and rewrite the navigation — but the precachedindex.htmlis one file identical for every URL, so any header it carries says the same thing to every farm. It degenerates to option 2's missing constant.
What the gap actually reduces to. The affected visitor is one whose service worker predates the farm-code feature, who has never signed in with a code, and who arrives at the root rather than at a
?farm=link. In that state the app cannot name the farm from anything on the device — which is exactly the information the missing redirect would have supplied. The remembered-codes roster is the only device-side source that works across all three of the issue's own rows, and it is already implemented (#535, #587). One successful sign-in ends the condition permanently.theme-init.jsis affected identically, per triage point 1, and is left affected for the same reason: its pre-paint brand lookup already falls back to a roster of one, and the default palette is the documented cost of not being able to attribute a brand to a farm (the pre-#586 un-namespaced key, purged at startup rather than guessed at).Reopen if the population turns out to be wider than tier3 assumes.
- A request header instead of a URL parameter. Browsers cannot attach a custom header to a navigation — only
What happens
Login.tsxprefills the farm code from?farm=<slug>(#535). A visitor who arrives at/and is redirected by the server to/login?farm=<slug>never gets that prefill, because the service worker answers the navigation before it reaches the network.sw.jsregisters:Only
/apiand/healthare denylisted, so every other navigation, including/, is served from the precachedindex.html. The request never leaves the browser, the server's302is never seen, and the SPA renders at/with no query string to read.Reproduction
200, served from the service worker, URL stays at/, farm code field empty./login?farm=<slug>, and the field is prefilled.A hard reload does not change the outcome. Confirmed against 0.1.0.
Why it matters
?farm=The third row is the gap. It self-heals after one successful sign-in, because
rememberFarmCodethen populatescluckwork.farmCodesand the cache path takes over. But it is exactly the "I don't know my farm code" case the prefill exists to solve, and for an upgraded install it is the first thing the user meets.Worth noting
theme-init.jsreads the same parameter for per-farm branding (cluckwork.brand:<slug>, #586), so it is affected identically — an upgraded install gets the default palette where a fresh one gets the farm's.Possible directions
No strong preference, and the tradeoffs are yours:
/to theNavigationRoutedenylist, or exclude navigations carrying a query string. Costs an offline-capable root, which may matter for the PWA case.?farm=and no remembered code, fall back to a configured default. Keeps the service worker as-is and removes the dependency on a redirect being observable at all.Option 2 seems the most robust from the outside, since it does not depend on how the parameter is delivered — but it needs a way to configure the default, which may not be wanted.