Skip to content

methods inherited via Object.setPrototypeOf(obj, proto) run with this=undefined — effect Pipeable/Tag statics return the wrong pipe stage (blocks web.ts, 'Not a valid effect: undefined') #6475

Closed

Description

@proggeramlug

Summary

The last standing failure of effect-compiled-experiment web.ts (after #6449 / #6459 / #6460 / #6463+#6466 / #6465+#6470 / #6469+#6474): startup dies with

Error: Not a valid effect: undefined      (fiberRuntime.ts:1422)

Root of the poisoned value, isolated by three probes:

probe43 — the poisoned binding

Walking web.ts's layer graph binding-by-binding: everything matches node except

HealthLive    node: object (a Layer)   perry: function

HealthLive = HttpApiBuilder.group(Api, "health", ...) — and group returns Router.use((router) => ...) where Router is class Router extends HttpRouter.Tag("@effect/platform/HttpApiBuilder/Router")<Router>() {}.

probe44 — Tag statics misbehave

Recreating the Tag shape directly (class R2 extends HttpRouter.Tag("probe44/R2")<R2>() {}):

check node perry
pipe(1, f, g) / 4 fns / method-style .pipe ✓ ✓ (all fine)
R2.key ✓ ✓
R2.Live Layer Layer ✓
R2.use(f) Layer function (isLayer false)
R2.unwrap(f) Layer object function

use is an own-prop ARROW on the factory's TagClass (TagClass_.use = (f) => TagClass_.pipe(Effect.flatMap(f), Layer.scopedDiscard, Layer.provide(TagClass_.Live))), inherited by R2 through the parent function object. The returned function is consistent with the pipe chain's LAST stage (Layer.provide(live), a data-last curried fn) never being applied — i.e. TagClass_.pipe(...) misfired on the receiver/arguments. TagClass's prototype is Object.getPrototypeOf(Context.GenericTag(id)) (a two-hop chain to Pipeable's pipe() { return pipeArguments(this, arguments) }).

probe45 — a minimal this-drop divergence in the same family

const Proto: any = { collect(this: any, ...args: any[]) { return [this?.tag, args.length, ...args] } }
function F() {}; Object.setPrototypeOf(F, Proto); (F as any).tag = "fn-recv"
const O: any = { tag: "obj-recv" }; Object.setPrototypeOf(O, Proto)

(F as any).collect(10, 20, 30)   // node ["fn-recv",3,10,20,30] | perry ["fn-recv",3,10,20,30] ✓
O.collect(10, 20, 30)            // node ["obj-recv",3,10,20,30] | perry ["null",3,10,20,30]  ✗
O.fixed(1, 2)                    // same: this dropped (null) for the OBJECT receiver

A method inherited through Object.setPrototypeOf(plainObject, proto) runs with this = undefined under perry (function-object receivers, own-prop closures, and arguments-based variadics all work). This is the same resolution family pipeArguments(this, arguments) sits in — with this undefined mid-chain, effect's Pipeable composition returns the wrong stage.

Impact

HttpApiBuilder.group(...) returns a function instead of a Layer, and the poison propagates lazily until the fiber run loop rejects an undefined — blocking the web.ts app (the 4th of the effect repro; the other three are byte-identical to node).

Repro files: probes 43/44/45 as above (no server needed for 44/45).

Activity

  1. proggeramlug commented on Jul 17, 2026

    @proggeramlug
    ContributorAuthor

    Fixed — both root causes landed on main:

    • The Object.setPrototypeOf(obj, proto) this=undefined / Tag-static wrong-pipe-stage bug → fix: static/namespace resolution for effect Tag & Schema — web.ts server starts (#6475) #6496 (fix: static/namespace resolution for effect Tag & Schema): bind this to the receiver on both proto-method call paths, substitute a function-object call-site this for canonical bound class methods, invalidate the cached dispatch strategy on closure registration, walk the parent-closure chain for static-member CALLs (class X extends Tag(id)() inherits static use/serve), and resolve namespace-member spread calls to the function rather than a same-named class.
    • The residual Not a valid effect: undefined → fix(hir): abstract class fields are type-only, emit no runtime slot #6499 (fix(hir): abstract class fields are type-only, emit no runtime slot): @effect/platform's HttpBody declares abstract readonly _tag on the base with concrete _tag on Uint8ArrayImpl/EmptyImpl/RawImpl. Perry lowered the abstract field into a duplicate runtime slot, so the response writer's switch (body._tag) read undefined, fell through, and returned undefined.

    effect-compiled-experiment web.ts now serves byte-identical to Node (homepage + JSON API). Closing as resolved.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions