Skip to content

Wire3 decoder pipeline causes 'too much recursion' on Firefox #83

@CharlonTank

Description

@CharlonTank

Bug

Lamdera apps with large FrontendModel records crash on Firefox with:

Uncaught internal error: too much recursion

The app works fine on Chrome and works locally (lamdera live) but fails in production (lamdera deploy).

Root Cause

In extra/Lamdera/Wire3/Decoder.hs (line 459), decodeRecord generates a decoder pipeline using foldlPairs (|>):

succeedDecode (\a b c ... -> { field1 = a, field2 = b, ... })
    |> andMapDecode decoder1
    |> andMapDecode decoder2
    ...
    |> andMapDecode decoderN

Each |> compiles to a nested function call in JS. For apps with deeply nested records (e.g. a FrontendModel containing OwnerData containing forms, lists, etc.), the total chain can reach 380+ levels of nesting.

In the compiled production JS, this becomes:

i(X, i(Y, i(Z, ... i(W, u(fn)) ...)))
//  380+ nested calls

Firefox's call stack limit is ~5000 frames. With ~12-15 frames per decoder step, 380 steps × ~13 frames ≈ ~5000 frames → stack overflow on Firefox.

Chrome has a higher stack limit (~8800+) so it works there.

How to Reproduce

  1. Create a Lamdera app with a FrontendModel that has many fields (including nested records with many fields)
  2. Deploy to production (lamdera deploy)
  3. Open the deployed app in Firefox → "too much recursion" error
  4. Same app works in Chrome and locally with lamdera live (non-optimized build)

Evidence

Analysis of the compiled production JS (frontend.*.js):

  • Global max parenthesis nesting depth: 218 at the decoder chain
  • 380 consecutive i() calls (the compiled andMapDecode pipeline)
  • The i function is function(r,t){ return A4(function(n){ return L$(n,r) }, t) } — each call adds to the stack

Suggested Fix

In Decoder.hs, chunk the foldlPairs into groups to limit nesting depth:

Instead of:

foldlPairs (|>)  -- creates a single chain of N steps

Split into batches of ~50 fields, decode each batch separately, then compose the results. This would cap the nesting depth regardless of record size.

Alternatively, the encoder (Encoder.hs line 237-254) already avoids this issue by using a flat list:

encodeSequenceWithoutLength $ list fieldEncoders

A similar flat approach for the decoder would eliminate the problem entirely.

Environment

  • Firefox (any version — all have stricter stack limits than Chrome)
  • Lamdera production builds (optimized + minified JS)
  • App with ~100+ total fields across nested FrontendModel records

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions