A public Brazilian election-results page fails before it can load a candidate profile in Lightpanda. The page is an Angular application. While opening a candidate route, it loads one extra JavaScript file. That file runs a small feature check: it asks whether the browser accepts a ReadableStream as the body of a Request. Firefox accepts the request object, so the application continues. Lightpanda throws TypeError instead and Angular stops loading that part of the page.
This is reproducible without the TSE website (see Minimal independent reproduction). It appears to be caused by Lightpanda requiring a stream to be closed when Request is created. Web browsers accept an open stream at that point; the stream may produce data later.
Environment
Reproduction
lightpanda fetch \
--log-level debug \
--wait-ms 8000 \
--dump semantic_tree_text \
'https://divulgacandcontas.tse.jus.br/divulga/#/candidato/SUDESTE/ES/2045202024/80002363211/2024/56553'
Output with `TypeError`
```
$time=1786290795155 $scope=telemetry $level=info $msg="telemetry status" disabled=false
$time=1786290795155 $scope=app $level=debug $msg="startup" mode=fetch dump_mode=semantic_tree_text url=https://divulgacandcontas.tse.jus.br/divulga/#/candidato/SUDESTE/ES/2045202024/80002363211/2024/56553 snapshot=true
$time=1786290795161 $scope=frame $level=info $msg="navigate" url=https://divulgacandcontas.tse.jus.br/divulga/#/candidato/SUDESTE/ES/2045202024/80002363211/2024/56553 method=GET reason=address_bar body=false type=root
$time=1786290795286 $scope=http $level=debug $msg="script fetch start" req=GET https://divulgacandcontas.tse.jus.br/divulga/runtime.113bd67c35d7d457.js
$time=1786290795286 $scope=browser $level=info $msg="executing script" src=https://divulgacandcontas.tse.jus.br/divulga/runtime.113bd67c35d7d457.js kind=module
$time=1786290795354 $scope=http $level=debug $msg="script fetch start" req=GET https://divulgacandcontas.tse.jus.br/divulga/polyfills.260c5b1f06020e59.js
$time=1786290795354 $scope=browser $level=info $msg="executing script" src=https://divulgacandcontas.tse.jus.br/divulga/polyfills.260c5b1f06020e59.js kind=module
$time=1786290795386 $scope=http $level=debug $msg="script fetch start" req=GET https://divulgacandcontas.tse.jus.br/divulga/scripts.46623e42e37f6d34.js
$time=1786290795386 $scope=browser $level=info $msg="executing script" src=https://divulgacandcontas.tse.jus.br/divulga/scripts.46623e42e37f6d34.js kind=javascript
$time=1786290795501 $scope=http $level=debug $msg="script fetch start" req=GET https://divulgacandcontas.tse.jus.br/divulga/main.764e646b5bc838bc.js
$time=1786290795502 $scope=browser $level=info $msg="executing script" src=https://divulgacandcontas.tse.jus.br/divulga/main.764e646b5bc838bc.js kind=module
$time=1786290795603 $scope=http $level=info $msg="request complete" source=xhr url=https://divulgacandcontas.tse.jus.br/divulga/rest/v1/sistema status=200 len=240
$time=1786290795607 $scope=http $level=info $msg="request complete" source=xhr url=https://divulgacandcontas.tse.jus.br/divulga/rest/v1/ata/ordinarias status=200 len=3628
$time=1786290795620 $scope=http $level=info $msg="request complete" source=xhr url=https://divulgacandcontas.tse.jus.br/divulga/rest/v1/eleicao/anos-eleitorais status=200 len=61
$time=1786290795662 $scope=http $level=debug $msg="script fetch start" req=GET https://divulgacandcontas.tse.jus.br/divulga/common.d23f7d2b9571464e.js
$time=1786290795667 $scope=http $level=debug $msg="script fetch start" req=GET https://divulgacandcontas.tse.jus.br/divulga/829.e77b9db3d69bf20e.js
$time=1786290795803 $scope=http $level=debug $msg="script fetch start" req=GET https://divulgacandcontas.tse.jus.br/divulga/684.398dd03bcb7e68ff.js
$time=1786290795804 $scope=browser $level=info $msg="executing script" src=https://divulgacandcontas.tse.jus.br/divulga/684.398dd03bcb7e68ff.js kind=module
$time=1786290795829 $scope=http $level=debug $msg="script fetch start" req=GET https://divulgacandcontas.tse.jus.br/divulga/636.85c158c04b59c757.js
$time=1786290795829 $scope=browser $level=info $msg="executing script" src=https://divulgacandcontas.tse.jus.br/divulga/636.85c158c04b59c757.js kind=module
$time=1786290795841 $scope=browser $level=info $msg="executing script" src=https://divulgacandcontas.tse.jus.br/divulga/common.d23f7d2b9571464e.js kind=module
$time=1786290795841 $scope=browser $level=info $msg="executing script" src=https://divulgacandcontas.tse.jus.br/divulga/829.e77b9db3d69bf20e.js kind=module
$time=1786290795871 $scope=js $level=warn $msg="console.error" param.0=Error: Uncaught (in promise): TypeError
TypeError
at https://divulgacandcontas.tse.jus.br/divulga/829.e77b9db3d69bf20e.js:1:112936
at 1829 (https://divulgacandcontas.tse.jus.br/divulga/829.e77b9db3d69bf20e.js:1:113073)
at r (https://divulgacandcontas.tse.jus.br/divulga/runtime.113bd67c35d7d457.js:1:128)
at v.invoke (https://divulgacandcontas.tse.jus.br/divulga/polyfills.260c5b1f06020e59.js:1:6567)
at Object.onInvoke (https://divulgacandcontas.tse.jus.br/divulga/main.764e646b5bc838bc.js:1:409686)
at v.invoke (https://divulgacandcontas.tse.jus.br/divulga/polyfills.260c5b1f06020e59.js:1:6507)
at L.run (https://divulgacandcontas.tse.jus.br/divulga/polyfills.260c5b1f06020e59.js:1:1933)
at https://divulgacandcontas.tse.jus.br/divulga/polyfills.260c5b1f06020e59.js:1:16777
at v.invokeTask (https://divulgacandcontas.tse.jus.br/divulga/polyfills.260c5b1f06020e59.js:1:7185)
at Object.onInvokeTask (https://divulgacandcontas.tse.jus.br/divulga/main.764e646b5bc838bc.js:1:409502)
1 RootWebArea 'Divulgação de Candidaturas e Contas Eleitorais'
7 navigation 'Dark offcanvas navbar'
9 [i] link 'Divulgação de Candidaturas e Contas Eleitorais'
12 [i]
14 'Eleições Municipais 2024'
15 [i] button 'Toggle navigation'
18 heading 'Eleições Municipais 2024'
19 [i] button 'Close'
23 [i]
25 'Eleições'
27 [i]
29 'Atas de Convenção'
31 [i]
33 'Diplomação'
39 contentinfo
42 'TSE - Tribunal Superior Eleitoral'
45 'Versão 2.8.12'
```
Other candidate routes with the same result:
Expected behavior
The first URL should render the public profile for DR THIAGO PEÇANHA, a 2024 mayoral candidate in Itapemirim, ES.
Firefox renders the profile successfully after a hard reload. Its console has no JavaScript errors; the only unrelated messages are font glyph bounding-box adjustments and a 403 for favicon.ico.
The Firefox HAR shows that, after loading the same lazy chunk, the application successfully requests these resources (all HTTP 200):
/divulga/rest/v1/eleicao/eleicao-atual?idEleicao=2045202024
/divulga/rest/v1/candidatura/buscar/2024/56553/2045202024/candidato/80002363211
/divulga/rest/v1/prestador/consulta/2045202024/2024/56553/11/40/40/80002363211
Actual behavior
Lightpanda loads the document and successfully completes these initial XHRs:
/divulga/rest/v1/sistema
/divulga/rest/v1/ata/ordinarias
/divulga/rest/v1/eleicao/anos-eleitorais
It then fetches and starts executing this lazy-loaded module:
https://divulgacandcontas.tse.jus.br/divulga/829.e77b9db3d69bf20e.js
Execution immediately logs this error:
Error: Uncaught (in promise): TypeError
TypeError
at https://divulgacandcontas.tse.jus.br/divulga/829.e77b9db3d69bf20e.js:1:112936
at 1829 (https://divulgacandcontas.tse.jus.br/divulga/829.e77b9db3d69bf20e.js:1:113073)
at r (https://divulgacandcontas.tse.jus.br/divulga/runtime.113bd67c35d7d457.js:1:128)
at v.invoke (https://divulgacandcontas.tse.jus.br/divulga/polyfills.260c5b1f06020e59.js:1:6567)
at Object.onInvoke (https://divulgacandcontas.tse.jus.br/divulga/main.764e646b5bc838bc.js:1:409686)
at v.invoke (https://divulgacandcontas.tse.jus.br/divulga/polyfills.260c5b1f06020e59.js:1:6507)
at L.run (https://divulgacandcontas.tse.jus.br/divulga/polyfills.260c5b1f06020e59.js:1:1933)
at https://divulgacandcontas.tse.jus.br/divulga/polyfills.260c5b1f06020e59.js:1:16777
at v.invokeTask (https://divulgacandcontas.tse.jus.br/divulga/polyfills.260c5b1f06020e59.js:1:7185)
at Object.onInvokeTask (https://divulgacandcontas.tse.jus.br/divulga/main.764e646b5bc838bc.js:1:409502)
Lightpanda does not make any of the three candidate-specific requests that Firefox makes after loading this chunk, and the rendered semantic tree has no candidate content.
The Firefox HAR + console text for this test is attached: divulgacandcontas.tse.jus.br_2026-08-09.zip
Minimal independent reproduction
This HTML has no dependency on the TSE application or on a network response. Save it as request-open-stream.html and load it in Lightpanda and Firefox.
<!doctype html>
<pre id="result">not run</pre>
<script>
// This is the observable result of the feature probe: Firefox reads `duplex`.
let duplexAccessed = false;
try {
// This stream is deliberately still open: nothing has closed it yet.
const stream = new ReadableStream();
// No fetch() is performed. This only constructs a Request object.
const request = new Request("https://example.com/", {
method: "POST",
body: stream,
get duplex() {
duplexAccessed = true;
return "half";
},
});
document.querySelector("#result").textContent =
"OK: " + request.method + "; duplex accessed: " + duplexAccessed;
} catch (error) {
document.querySelector("#result").textContent =
"ERROR: " + error.name + ": " + error.message;
}
</script>
| Browser |
Result |
| Firefox |
OK: POST; duplex accessed: false |
Lightpanda nightly 8691568f |
ERROR: TypeError: |
An open stream is important here. A request body stream is allowed to produce data asynchronously; creating a Request should not require that the producer has already closed it. The example also never sends the request, so there is no need to turn the stream into bytes merely to create the object.
Root cause identified from Lightpanda source
This Lightpanda binary identifies commit 8691568f. I cloned lightpanda-io/browser and inspected that exact commit.
The important detail is simple: Lightpanda currently turns a request body into bytes while new Request(...) is running. An open ReadableStream cannot be turned into a complete byte sequence yet, so Lightpanda throws TypeError and never creates the Request.
The sequence in the source is:
Request.init immediately extracts opts.body (around line 125).
- For a
ReadableStream, body_init.zig calls ReadableStream.collectBodyBytes().
collectBodyBytes() explicitly returns error.TypeError when the stream is still readable (open); it accepts only a closed stream.
In other words, this is not an error from the TSE API, CORS, or an attempted HTTP upload. It happens locally, while JavaScript is constructing a Request object.
The TSE lazy chunk uses this as a Fetch capability probe. Its relevant minified behavior is equivalent to:
let duplexAccessed = false;
new Request(origin, {
method: "POST",
body: new ReadableStream(),
get duplex() {
duplexAccessed = true;
return "half";
},
});
It is not trying to send a streaming upload at that moment. It only checks whether the browser accepts that kind of request. Firefox accepts it; Lightpanda throws; the unhandled exception aborts initialization of the Angular lazy chunk.
There is already a Lightpanda test that documents the current buffering limitation: fetch_readablestream_body_reject expects fetch() with an open stream to reject. That is consistent with the present synchronous buffering implementation, but Request construction uses the same path. Consequently, even a feature probe that does not call fetch() fails.
The needed compatibility behavior is for new Request(url, { method: "POST", body: openReadableStream, duplex: "half" }) to succeed, as it does in Firefox/Chromium. The complete solution may retain or transfer the stream and consume it when the request is sent. If streaming uploads are not yet supported, accepting this constructor case would at least prevent this standard capability check from crashing applications.
A public Brazilian election-results page fails before it can load a candidate profile in Lightpanda. The page is an Angular application. While opening a candidate route, it loads one extra JavaScript file. That file runs a small feature check: it asks whether the browser accepts a
ReadableStreamas the body of aRequest. Firefox accepts the request object, so the application continues. Lightpanda throwsTypeErrorinstead and Angular stops loading that part of the page.This is reproducible without the TSE website (see Minimal independent reproduction). It appears to be caused by Lightpanda requiring a stream to be closed when
Requestis created. Web browsers accept an open stream at that point; the stream may produce data later.Environment
1.0.0-nightly.8580+8691568fReproduction
lightpanda fetch \ --log-level debug \ --wait-ms 8000 \ --dump semantic_tree_text \ 'https://divulgacandcontas.tse.jus.br/divulga/#/candidato/SUDESTE/ES/2045202024/80002363211/2024/56553'Output with `TypeError`
``` $time=1786290795155 $scope=telemetry $level=info $msg="telemetry status" disabled=false $time=1786290795155 $scope=app $level=debug $msg="startup" mode=fetch dump_mode=semantic_tree_text url=https://divulgacandcontas.tse.jus.br/divulga/#/candidato/SUDESTE/ES/2045202024/80002363211/2024/56553 snapshot=true $time=1786290795161 $scope=frame $level=info $msg="navigate" url=https://divulgacandcontas.tse.jus.br/divulga/#/candidato/SUDESTE/ES/2045202024/80002363211/2024/56553 method=GET reason=address_bar body=false type=root $time=1786290795286 $scope=http $level=debug $msg="script fetch start" req=GET https://divulgacandcontas.tse.jus.br/divulga/runtime.113bd67c35d7d457.js $time=1786290795286 $scope=browser $level=info $msg="executing script" src=https://divulgacandcontas.tse.jus.br/divulga/runtime.113bd67c35d7d457.js kind=module $time=1786290795354 $scope=http $level=debug $msg="script fetch start" req=GET https://divulgacandcontas.tse.jus.br/divulga/polyfills.260c5b1f06020e59.js $time=1786290795354 $scope=browser $level=info $msg="executing script" src=https://divulgacandcontas.tse.jus.br/divulga/polyfills.260c5b1f06020e59.js kind=module $time=1786290795386 $scope=http $level=debug $msg="script fetch start" req=GET https://divulgacandcontas.tse.jus.br/divulga/scripts.46623e42e37f6d34.js $time=1786290795386 $scope=browser $level=info $msg="executing script" src=https://divulgacandcontas.tse.jus.br/divulga/scripts.46623e42e37f6d34.js kind=javascript $time=1786290795501 $scope=http $level=debug $msg="script fetch start" req=GET https://divulgacandcontas.tse.jus.br/divulga/main.764e646b5bc838bc.js $time=1786290795502 $scope=browser $level=info $msg="executing script" src=https://divulgacandcontas.tse.jus.br/divulga/main.764e646b5bc838bc.js kind=module $time=1786290795603 $scope=http $level=info $msg="request complete" source=xhr url=https://divulgacandcontas.tse.jus.br/divulga/rest/v1/sistema status=200 len=240 $time=1786290795607 $scope=http $level=info $msg="request complete" source=xhr url=https://divulgacandcontas.tse.jus.br/divulga/rest/v1/ata/ordinarias status=200 len=3628 $time=1786290795620 $scope=http $level=info $msg="request complete" source=xhr url=https://divulgacandcontas.tse.jus.br/divulga/rest/v1/eleicao/anos-eleitorais status=200 len=61 $time=1786290795662 $scope=http $level=debug $msg="script fetch start" req=GET https://divulgacandcontas.tse.jus.br/divulga/common.d23f7d2b9571464e.js $time=1786290795667 $scope=http $level=debug $msg="script fetch start" req=GET https://divulgacandcontas.tse.jus.br/divulga/829.e77b9db3d69bf20e.js $time=1786290795803 $scope=http $level=debug $msg="script fetch start" req=GET https://divulgacandcontas.tse.jus.br/divulga/684.398dd03bcb7e68ff.js $time=1786290795804 $scope=browser $level=info $msg="executing script" src=https://divulgacandcontas.tse.jus.br/divulga/684.398dd03bcb7e68ff.js kind=module $time=1786290795829 $scope=http $level=debug $msg="script fetch start" req=GET https://divulgacandcontas.tse.jus.br/divulga/636.85c158c04b59c757.js $time=1786290795829 $scope=browser $level=info $msg="executing script" src=https://divulgacandcontas.tse.jus.br/divulga/636.85c158c04b59c757.js kind=module $time=1786290795841 $scope=browser $level=info $msg="executing script" src=https://divulgacandcontas.tse.jus.br/divulga/common.d23f7d2b9571464e.js kind=module $time=1786290795841 $scope=browser $level=info $msg="executing script" src=https://divulgacandcontas.tse.jus.br/divulga/829.e77b9db3d69bf20e.js kind=module $time=1786290795871 $scope=js $level=warn $msg="console.error" param.0=Error: Uncaught (in promise): TypeError TypeError at https://divulgacandcontas.tse.jus.br/divulga/829.e77b9db3d69bf20e.js:1:112936 at 1829 (https://divulgacandcontas.tse.jus.br/divulga/829.e77b9db3d69bf20e.js:1:113073) at r (https://divulgacandcontas.tse.jus.br/divulga/runtime.113bd67c35d7d457.js:1:128) at v.invoke (https://divulgacandcontas.tse.jus.br/divulga/polyfills.260c5b1f06020e59.js:1:6567) at Object.onInvoke (https://divulgacandcontas.tse.jus.br/divulga/main.764e646b5bc838bc.js:1:409686) at v.invoke (https://divulgacandcontas.tse.jus.br/divulga/polyfills.260c5b1f06020e59.js:1:6507) at L.run (https://divulgacandcontas.tse.jus.br/divulga/polyfills.260c5b1f06020e59.js:1:1933) at https://divulgacandcontas.tse.jus.br/divulga/polyfills.260c5b1f06020e59.js:1:16777 at v.invokeTask (https://divulgacandcontas.tse.jus.br/divulga/polyfills.260c5b1f06020e59.js:1:7185) at Object.onInvokeTask (https://divulgacandcontas.tse.jus.br/divulga/main.764e646b5bc838bc.js:1:409502) 1 RootWebArea 'Divulgação de Candidaturas e Contas Eleitorais' 7 navigation 'Dark offcanvas navbar' 9 [i] link 'Divulgação de Candidaturas e Contas Eleitorais' 12 [i] 14 'Eleições Municipais 2024' 15 [i] button 'Toggle navigation' 18 heading 'Eleições Municipais 2024' 19 [i] button 'Close' 23 [i] 25 'Eleições' 27 [i] 29 'Atas de Convenção' 31 [i] 33 'Diplomação' 39 contentinfo 42 'TSE - Tribunal Superior Eleitoral' 45 'Versão 2.8.12' ```Other candidate routes with the same result:
Expected behavior
The first URL should render the public profile for DR THIAGO PEÇANHA, a 2024 mayoral candidate in Itapemirim, ES.
Firefox renders the profile successfully after a hard reload. Its console has no JavaScript errors; the only unrelated messages are font glyph bounding-box adjustments and a
403forfavicon.ico.The Firefox HAR shows that, after loading the same lazy chunk, the application successfully requests these resources (all HTTP 200):
Actual behavior
Lightpanda loads the document and successfully completes these initial XHRs:
It then fetches and starts executing this lazy-loaded module:
Execution immediately logs this error:
Lightpanda does not make any of the three candidate-specific requests that Firefox makes after loading this chunk, and the rendered semantic tree has no candidate content.
The Firefox HAR + console text for this test is attached: divulgacandcontas.tse.jus.br_2026-08-09.zip
Minimal independent reproduction
This HTML has no dependency on the TSE application or on a network response. Save it as
request-open-stream.htmland load it in Lightpanda and Firefox.OK: POST; duplex accessed: false8691568fERROR: TypeError:An open stream is important here. A request body stream is allowed to produce data asynchronously; creating a
Requestshould not require that the producer has already closed it. The example also never sends the request, so there is no need to turn the stream into bytes merely to create the object.Root cause identified from Lightpanda source
This Lightpanda binary identifies commit
8691568f. I clonedlightpanda-io/browserand inspected that exact commit.The important detail is simple: Lightpanda currently turns a request body into bytes while
new Request(...)is running. An openReadableStreamcannot be turned into a complete byte sequence yet, so Lightpanda throwsTypeErrorand never creates theRequest.The sequence in the source is:
Request.initimmediately extractsopts.body(around line 125).ReadableStream,body_init.zigcallsReadableStream.collectBodyBytes().collectBodyBytes()explicitly returnserror.TypeErrorwhen the stream is stillreadable(open); it accepts only aclosedstream.In other words, this is not an error from the TSE API, CORS, or an attempted HTTP upload. It happens locally, while JavaScript is constructing a
Requestobject.The TSE lazy chunk uses this as a Fetch capability probe. Its relevant minified behavior is equivalent to:
It is not trying to send a streaming upload at that moment. It only checks whether the browser accepts that kind of request. Firefox accepts it; Lightpanda throws; the unhandled exception aborts initialization of the Angular lazy chunk.
There is already a Lightpanda test that documents the current buffering limitation:
fetch_readablestream_body_rejectexpectsfetch()with an open stream to reject. That is consistent with the present synchronous buffering implementation, butRequestconstruction uses the same path. Consequently, even a feature probe that does not callfetch()fails.The needed compatibility behavior is for
new Request(url, { method: "POST", body: openReadableStream, duplex: "half" })to succeed, as it does in Firefox/Chromium. The complete solution may retain or transfer the stream and consume it when the request is sent. If streaming uploads are not yet supported, accepting this constructor case would at least prevent this standard capability check from crashing applications.