You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
node-fetch: fetch() throws 'Function.prototype.call was called on a value that is not a function' — same message class as #5135 and the require('stream') case #10759
Compiling node-fetch 3.3.2 from real source succeeds (23 modules, 3m8s), but calling fetch() throws:
Uncaught (in promise) TypeError: Function.prototype.call was called on a value that is not a function
Found by the package-compilability probe on 91a566c8af (v0.5.1605), Linux x64.
Isolated to fetch() itself, not the test scaffolding
A stripped-down GET-only fixture (no POST, no body) against a local node:http server still crashes — immediately after the server starts listening, before any response is read.
An isolated ESM harness doing http.createServer plus a Promise, with no node-fetch involved, runs clean.
So the failure is inside node-fetch's own fetch() machinery rather than in the ESM/http scaffolding around it.
This looks like a recurring class, not a one-off
That error string is Perry's generic runtime message for .call() on a non-function value, and there is prior art in-repo for the same message arising from module-shape problems:
a comment at crates/perry-runtime/src/node_stream_dispatch.rs:230 describing the same message following require('stream')
Which suggests the underlying cause is something resolving to a non-function where a function is expected — plausibly a module-export shape — rather than a defect unique to node-fetch. Worth checking against those existing cases before treating it as new.
Which call site inside node-fetch receives the non-function value. Narrowed to "inside fetch()", not to a specific line, within the probe's time budget.
Diagnosis, recorded before it is verified so it isn't lost — this is a hypothesis traced from source reading, not yet run against a Perry build. Flagged as such deliberately.
Both sibling cases are the same bug family
crates/perry/tests/issue_5135_proxy_compound_and_function_tostring.rs — immer's Function.toString.call(Ctor) threw this exact error because the static member read collapsed to globalThis.toString, a non-function.
crates/perry-runtime/src/object/native_module/constants.rs:203-220 and node_stream_dispatch.rs:210-223 — readable-stream's Stream.prototype.on.call(this, …) threw it because require('stream').prototype resolved to undefined rather than the legacy Stream.prototype.
The family: a built-in or native prototype method read as a value — not called directly as recv.method(...) — resolves to a non-function, and later code does .call()/.apply() on it.
The single throw site for this message is throw_fn_proto_not_callable in crates/perry-runtime/src/object/native_call_method/proto_dispatch.rs:104-118, which confirms it is a generic "receiver isn't a real closure" check rather than anything node-fetch-specific.
request.js:231 calls headers.has('Accept') unconditionally inside getNodeRequestOptions(), which everyfetch() reaches before the socket opens — matching the observed "crashes immediately after the server starts listening, before any response is read".
So the shape is a three-way compound, each part individually plausible as a gap:
computed/dynamic-key member access on a builtin's .prototype — p is a closure-captured, non-literal string;
a constructor that overrides its return value — return new Proxy(this, …);
a Proxy get trap sitting between the access and the object.
Next step for whoever picks this up
Build perry-dev, then test an isolated repro — a user class plus a Proxy get trap returning SomeClass.prototype[p].call(target, args) with p a captured variable — against both a plain user class and a global builtin such as URLSearchParams, since the difference between those two tells you whether the defect is in builtin-prototype resolution or in the computed-key path generally. Then --trace hir --focus on the trap closure to see what SomeClass.prototype[p] actually lowers to.
One trap already hit, worth not repeating: a first draft repro also exercised Headers's inherited .toString(), which triggers a genuine and unrelated Node error (ERR_INVALID_THIS on URLSearchParams.prototype.toString called through a Proxy receiver). That is a quirk of the fixture, not the bug. Call only .has()/.append().
Status
Not compiled, not verified, no fix, no gap test. Recorded so the reasoning survives independently of any one session.
Correcting the diagnosis above, and bounding the bug class it implies.
The comment above states that URLSearchParams "had no arm at all" in populate_builtin_prototype_methods, falling through to _ => {}. That is wrong. On origin/main, crates/perry-runtime/src/object/global_this/proto_methods.rs:1093:
That arm belongs to a deliberate group, and the comment immediately above it documents the intent — these builtins' methods are reached by "type-directed static dispatch or the small-int/handle dispatch tables", so they were never given reified prototype closures; each arm installs only the Symbol.toStringTag descriptor for reflection.
That design is sound for x.method() and has no answer for a method read as a value: URLSearchParams.prototype.append, .prototype["has"], or anything reached through a Proxy get trap. The value-read finds nothing installed and yields undefined.
So this is a gap in a deliberate design, not a missing entry. The distinction matters for how it should be fixed: the question is not "why was this one forgotten" but "what should a statically-dispatched builtin do when one of its methods is read as a value rather than called".
The class is bounded and enumerable
Every arm in that group installs only a toStringTag:
Seven, of which this issue covers one. The other six are candidates for the identical defect — reading any of their prototype methods as a value should return undefined by the same mechanism.
That is a checkable prediction rather than a worry, and it is being tested: one small fixture per builtin, reading a prototype method as a value and calling it (const f = Event.prototype.preventDefault; f.call(ev)). Results to follow. If some of the six pass, that is equally useful — it would mean the toStringTag-only arm is not sufficient to cause the defect, and something else distinguishes them.
The earlier framing — "every builtin without an arm has this latent bug" — was both wrong and unfalsifiable. This version names seven specific candidates and predicts what each should do.
Fixed on main. PR #10807 landed — verified by content rather than PR state, since merge trains close source PRs without firing close keywords: install_noop_proto_methods now installs URLSearchParams's prototype methods in proto_methods.rs.
Note the fix as landed also merged with the pre-existing #10555 toStringTag arm rather than shadowing it. An earlier revision inserted the new arm first in the match, which produced an unreachable pattern warning and silently dropped the install_web_builtin_to_string_tag call — a fresh instance of this very bug class, in a diff that read as pure addition.
The six sibling builtins remain unfixed and are tracked in #10808, including URL.prototype.toString silently resolving to the wrong function. And node-fetch itself is still blocked, now by #10809 (derived class returning a Proxy) — this fix was necessary but not sufficient for that package.
Activity
proggeramlug commentedon Sep 20, 2026
Diagnosis, recorded before it is verified so it isn't lost — this is a hypothesis traced from source reading, not yet run against a Perry build. Flagged as such deliberately.
Both sibling cases are the same bug family
crates/perry/tests/issue_5135_proxy_compound_and_function_tostring.rs— immer'sFunction.toString.call(Ctor)threw this exact error because the static member read collapsed toglobalThis.toString, a non-function.crates/perry-runtime/src/object/native_module/constants.rs:203-220andnode_stream_dispatch.rs:210-223— readable-stream'sStream.prototype.on.call(this, …)threw it becauserequire('stream').prototyperesolved toundefinedrather than the legacyStream.prototype.The family: a built-in or native prototype method read as a value — not called directly as
recv.method(...)— resolves to a non-function, and later code does.call()/.apply()on it.The single throw site for this message is
throw_fn_proto_not_callableincrates/perry-runtime/src/object/native_call_method/proto_dispatch.rs:104-118, which confirms it is a generic "receiver isn't a real closure" check rather than anything node-fetch-specific.The node-fetch trigger
node-fetch/src/headers.js:request.js:231callsheaders.has('Accept')unconditionally insidegetNodeRequestOptions(), which everyfetch()reaches before the socket opens — matching the observed "crashes immediately after the server starts listening, before any response is read".So the shape is a three-way compound, each part individually plausible as a gap:
.prototype—pis a closure-captured, non-literal string;return new Proxy(this, …);gettrap sitting between the access and the object.Next step for whoever picks this up
Build
perry-dev, then test an isolated repro — a user class plus a Proxygettrap returningSomeClass.prototype[p].call(target, args)withpa captured variable — against both a plain user class and a global builtin such asURLSearchParams, since the difference between those two tells you whether the defect is in builtin-prototype resolution or in the computed-key path generally. Then--trace hir --focuson the trap closure to see whatSomeClass.prototype[p]actually lowers to.One trap already hit, worth not repeating: a first draft repro also exercised
Headers's inherited.toString(), which triggers a genuine and unrelated Node error (ERR_INVALID_THISonURLSearchParams.prototype.toStringcalled through a Proxy receiver). That is a quirk of the fixture, not the bug. Call only.has()/.append().Status
Not compiled, not verified, no fix, no gap test. Recorded so the reasoning survives independently of any one session.
proggeramlug commentedon Sep 20, 2026
Correcting the diagnosis above, and bounding the bug class it implies.
The comment above states that
URLSearchParams"had no arm at all" inpopulate_builtin_prototype_methods, falling through to_ => {}. That is wrong. Onorigin/main,crates/perry-runtime/src/object/global_this/proto_methods.rs:1093:What is actually happening
That arm belongs to a deliberate group, and the comment immediately above it documents the intent — these builtins' methods are reached by "type-directed static dispatch or the small-int/handle dispatch tables", so they were never given reified prototype closures; each arm installs only the
Symbol.toStringTagdescriptor for reflection.That design is sound for
x.method()and has no answer for a method read as a value:URLSearchParams.prototype.append,.prototype["has"], or anything reached through a Proxygettrap. The value-read finds nothing installed and yieldsundefined.So this is a gap in a deliberate design, not a missing entry. The distinction matters for how it should be fixed: the question is not "why was this one forgotten" but "what should a statically-dispatched builtin do when one of its methods is read as a value rather than called".
The class is bounded and enumerable
Every arm in that group installs only a toStringTag:
Seven, of which this issue covers one. The other six are candidates for the identical defect — reading any of their prototype methods as a value should return
undefinedby the same mechanism.That is a checkable prediction rather than a worry, and it is being tested: one small fixture per builtin, reading a prototype method as a value and calling it (
const f = Event.prototype.preventDefault; f.call(ev)). Results to follow. If some of the six pass, that is equally useful — it would mean the toStringTag-only arm is not sufficient to cause the defect, and something else distinguishes them.The earlier framing — "every builtin without an arm has this latent bug" — was both wrong and unfalsifiable. This version names seven specific candidates and predicts what each should do.
proggeramlug commentedon Sep 21, 2026
Fixed on main. PR #10807 landed — verified by content rather than PR state, since merge trains close source PRs without firing close keywords:
install_noop_proto_methodsnow installs URLSearchParams's prototype methods inproto_methods.rs.Note the fix as landed also merged with the pre-existing
#10555toStringTag arm rather than shadowing it. An earlier revision inserted the new arm first in the match, which produced anunreachable patternwarning and silently dropped theinstall_web_builtin_to_string_tagcall — a fresh instance of this very bug class, in a diff that read as pure addition.The six sibling builtins remain unfixed and are tracked in #10808, including
URL.prototype.toStringsilently resolving to the wrong function. And node-fetch itself is still blocked, now by #10809 (derived class returning a Proxy) — this fix was necessary but not sufficient for that package.