basquetWi + New ticket
venus VENUS-377

Silent server-action transport failure on /practicas/nueva - 7 unhandled 'Failed to fetch' since June, 6 of 7 Android Chrome mobile, user saw no error

Done normal cvcoder-venus-cc

RESOLVED + SHIPPED f239752 v1.13.17. "at I" IDENTIFIED (this was the open question; it is now answered by measurement, not inference). Fetched the live prod chunk 2r2snb12eb8ap.js?dpl=dpl_7iWxPRj8jU6Q79aKReB7f5otNJDJ and read byte offset 100593 directly: function I(e,t,{actionId:r,actionArgs:c}) ... let A=await fetch(e.canonicalUrl,{method:"POST",headers:T,body:v}). That is Next.js internal fetchServerAction. Not our code, not an image upload, not a third party. CORRECTION to the earlier version of this description: I wrote FIVE rows on /practicas/nueva. It is SEVEN of seven - all 7 rows carry route=/practicas/nueva. The two I had not individually verified (2026-07-05, 2026-06-22) are also on that route. Do not quote the five. WHAT THE STACK CANNOT TELL US, stated so nobody later reads more into it: I is the SHARED Next internal and the stacks have exactly one frame, no caller frames. So the evidence identifies the MECHANISM (a server-action POST that never reached the server) and NOT which action. The form has three server-action awaits and any of them produces this identical frame. lookupPacienteByDni fires on DNI typing so it has far higher call volume than a submit; that is a reason to suspect it, not evidence, and it was not measured. All three were fixed rather than guessing. Corroborating detail: the byte offset is stable per build (99684 x3 across three different chunk names, 99779 x2, 100593 x2) and drifts only with the framework bundle - one surviving code site, not a build artifact. WHY IT WAS SILENT: the rejection happens BEFORE our action runs, so it can never return the {success:false,error} shape the form handles. It escaped all three awaits -> clientUnhandledRejection -> user saw NO error at all on a clinical data-entry form. FIX: null sentinel .catch on all three call sites; crear/editar setError with Spanish copy, lookup degrades to "no hit" (it is advisory and never blocks the save). Retry is safe by construction: idempotencyKey is useState(() => crypto.randomUUID()), stable per mount, so a resubmit dedupes rather than duplicating a practica - the copy tells the user so. Deliberately NOT reported through logActionError/logGuardrail: both post to a SERVER ACTION, which is exactly what is unreachable; that would be a second unhandled rejection. GUARD: src/components/__tests__/practica-form-transport-guard.test.ts, a source guard (the regression is a 4th call site added without the guard - no single rendered scenario catches that). Falsification-verified twice: dropping a .catch fails it, and renaming the import so the scan sees an empty set fails the prove-nonempty assertion instead of vacuously passing. 700 tests green, tsc clean. RESIDUAL: the fix makes the failure VISIBLE; it does not reduce the underlying network drops (~1 every 2 months, 6 of 7 Linux UA). Whether that rate is worth chasing is a separate question and is not claimed here.

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Activity

  • wi-cli-venus created · 2026-08-18
  • wi-cli-venus descriptionChanged · 2026-08-18
    was: Recurring client-side TypeError 'Failed to fetch' on /practicas/nueva. NOT transient network and NOT a new signature - errscan surfaced the 2026-08-18 19:19:46Z row as '1x new signature', but measured against appEvents there are 7 'Failed to fetch' rows since 2026-06-17, FIVE of them on /practicas/nueva (2026-06-17, 07-08, 07-14, 07-15, 08-18). Concentration is the evidence: every one is errorName='TypeError', every one is Android Chrome (150/151), and every one dies at the SAME minified frame 'at I' in the page chunk - the chunk hash changes across deploys (0wvhq5ri18uyu -> 2r2snb12eb8ap) while the frame does not, so it is one surviving code site rather than a build artifact. Random client network loss does not cluster on one route + one platform + one frame for two months. User-recoverable and therefore unreported: two appEvents action='uploaded' info rows land at 19:21:55Z and 19:21:57Z, ~2min after the failure, so that user retried and succeeded. Impact is on the practica-creation path, the app's primary flow. NOT YET IDENTIFIED: which fetch 'at I' resolves to - the stack is minified and no sourcemap was consulted. That is the next step and explicitly not a conclusion. Instrument note for whoever picks this up: the payload lives in appEvents.errorName/errorMessage/errorStack, NOT appEvents.message (message is NULL on every one of these rows - a query selecting 'message' makes them look contentless). Writer is src/app/api/internal/log-error/route.ts, scope='rejection' -> action='clientUnhandledRejection'. Supersedes EVO-90, which I filed to the wrong project (evolutiva) - this is venus-only and belongs on the venus board.
  • wi-cli-venus titleChanged · 2026-08-18
    Silent server-action transport failure on /practicas/nueva - 7 unhandled 'Failed to fetch' rejections since June, user saw no error
  • wi-cli-venus completed · 2026-08-18
    completed
  • wi-cli-venus titleChanged · 2026-08-18
    Silent server-action transport failure on /practicas/nueva - 7 unhandled 'Failed to fetch' since June, 6 of 7 Android Chrome mobile, user saw no error
  • wi-cli-venus note · 2026-08-18
    UA CORRECTION - I broke a claim that was already right. Setting the record straight. The original title said "Android Chrome". I "corrected" it to Linux/Macintosh. THE ORIGINAL WAS RIGHT. Actual UAs, read raw from appEvents: - 6 of 7: Mozilla/5.0 (Linux; Android 10; K) ... Chrome/149|150|151 Mobile Safari -> Android Chrome MOBILE - 1 of 7 (2026-07-05): Macintosh Intel Mac OS X 10_15_7, Chrome/149 desktop HOW I BROKE IT: I classified UAs with re.search(r"(Android|iPhone|Windows|Macintosh|Linux)"). Python alternation returns the LEFTMOST MATCH IN THE STRING, not the first alternative that could match. Android UA strings begin "Mozilla/5.0 (Linux; Android 10; K)" - "Linux" sits at char ~13, "Android" at ~20 - so every Android device silently classified as Linux desktop. The order I wrote the alternatives in was cosmetic and I read it as a priority list. No error, no empty result: a confident wrong answer. WHY IT MATTERS beyond bookkeeping: 6 of 7 being MOBILE is the strongest available evidence for the transport reading - a server-action POST dropped mid-flight is exactly what a phone on a flaky connection does. It makes the silent-failure severity worse, not better: a mobile user loses the submission with no error shown. Read as "Linux desktop" the incident looked like an office network oddity. The 7/7-on-/practicas/nueva correction still stands (verified per row). Fix f239752 v1.13.17 is unaffected - it is UA-independent.
bug
2026-08-18 by wi-cli-venus
2026-08-18
2026-08-18 20:24