Summary
new ImportedClass(args) from another module drops the constructor arguments when ImportedClass has no own constructor and its extends clause is a runtime value (an IIFE-returned class read through a namespace re-export). The instance ends up with no own properties. Constructing the same class inside its defining module works, and so does an exported class that declares constructor(a) { super(a) }.
This is the current runtime wall of OpenCode v1.18.30 (tracker #10107). effect v4's effect/PlatformError declares export class SystemError extends Data.Error { get message() {…} } (Data.Error = core.Error, an IIFE-returned class Base extends YieldableError { constructor(args) { super(args?.message, …); Object.assign(this, args) } }). When a missing file is read, SystemError loses _tag / module / method, so Effect.catchReason("PlatformError", "NotFound", …) in OpenCode's FSUtil.readFileStringSafe does not match, the failure becomes a defect, and every command dies with Error: Unexpected error / [object Object] right after loading path=~/.config/opencode/config.json.
Repro (4 files, no dependencies)
// core.ts
export const YieldableError = (function () { class YieldableError extends globalThis.Error {}; return YieldableError })()
export const Error = (function () {
return class Base extends YieldableError {
constructor(args?: any) { super(args?.message); if (args) Object.assign(this, args) }
}
})()
// data.ts
import * as core from "./core"
export const Error = core.Error
// platform2.ts
import * as Data from "./data"
export class A_getter extends (Data.Error as any) { get message() { return "A" } }
export class B_nogetter extends (Data.Error as any) {}
export class C_method extends (Data.Error as any) { describe() { return "C" } }
class D_local_getter extends (Data.Error as any) { get message() { return "D" } }
export const makeD = (o: any) => new D_local_getter(o)
export class E_getter_other extends (Data.Error as any) { get other() { return "E" } }
export class F_getter_ctor extends (Data.Error as any) { constructor(a: any) { super(a) } get message() { return "F" } }
// main2.ts
import { A_getter, B_nogetter, C_method, makeD, E_getter_other, F_getter_ctor } from "./platform2"
import * as Data from "./data"
const t = (name: string, f: () => any) => { const e = f(); console.log(name, JSON.stringify({ tag: e?._tag, m: e?.module, keys: Object.keys(e).filter((k) => !k.startsWith("__perry_cap")), msg: String(e?.message) })) }
const o = () => ({ _tag: "NotFound", module: "FS" })
t("A exported + get message()", () => new A_getter(o()))
t("B exported, empty", () => new B_nogetter(o()))
t("C exported + method", () => new C_method(o()))
t("D local + get message()", () => makeD(o()))
t("E exported + get other()", () => new E_getter_other(o()))
t("F exported + ctor + get message()", () => new F_getter_ctor(o()))
class M_main_getter extends (Data.Error as any) { get message() { return "M" } }
t("M main-module + get message()", () => new M_main_getter(o()))
bun 1.3.14:
A exported + get message() {"tag":"NotFound","m":"FS","keys":["_tag","module"],"msg":"A"}
B exported, empty {"tag":"NotFound","m":"FS","keys":["_tag","module"],"msg":""}
C exported + method {"tag":"NotFound","m":"FS","keys":["_tag","module"],"msg":""}
D local + get message() {"tag":"NotFound","m":"FS","keys":["_tag","module"],"msg":"D"}
E exported + get other() {"tag":"NotFound","m":"FS","keys":["_tag","module"],"msg":""}
F exported + ctor + get message() {"tag":"NotFound","m":"FS","keys":["_tag","module"],"msg":"F"}
M main-module + get message() {"tag":"NotFound","m":"FS","keys":["_tag","module"],"msg":"M"}
perry 0.5.1564 (Linux x86_64):
A exported + get message() {"keys":[],"msg":""}
B exported, empty {"keys":[],"msg":""}
C exported + method {"keys":[],"msg":""}
D local + get message() {"tag":"NotFound","m":"FS","keys":["_tag","module"],"msg":"D"}
E exported + get other() {"keys":[],"msg":""}
F exported + ctor + get message() {"tag":"NotFound","m":"FS","keys":["_tag","module"],"msg":"F"}
M main-module + get message() {"tag":"NotFound","m":"FS","keys":["_tag","module"],"msg":"M"}
Related divergences seen while reducing (same area, possibly separate fixes)
class E extends Error { constructor(a) { super(a?.message) } } with a.message === undefined: perry new E({}).message === "undefined" (node/bun ""), and that own message shadows a subclass get message().
- Instances of
Data.TaggedError(tag) subclasses expose internal keys like __perry_cap_23m0000b400ee61 in Object.keys (node/bun: only the user fields).
fs.promises.readFile(missing) resolves undefined (node rejects with ENOENT), and fs.readFileSync(missing) does not throw.
Summary
new ImportedClass(args)from another module drops the constructor arguments whenImportedClasshas no own constructor and itsextendsclause is a runtime value (an IIFE-returned class read through a namespace re-export). The instance ends up with no own properties. Constructing the same class inside its defining module works, and so does an exported class that declaresconstructor(a) { super(a) }.This is the current runtime wall of OpenCode v1.18.30 (tracker #10107). effect v4's
effect/PlatformErrordeclaresexport class SystemError extends Data.Error { get message() {…} }(Data.Error = core.Error, an IIFE-returnedclass Base extends YieldableError { constructor(args) { super(args?.message, …); Object.assign(this, args) } }). When a missing file is read,SystemErrorloses_tag/module/method, soEffect.catchReason("PlatformError", "NotFound", …)in OpenCode'sFSUtil.readFileStringSafedoes not match, the failure becomes a defect, and every command dies withError: Unexpected error / [object Object]right afterloading path=~/.config/opencode/config.json.Repro (4 files, no dependencies)
bun 1.3.14:
perry 0.5.1564 (Linux x86_64):
Related divergences seen while reducing (same area, possibly separate fixes)
class E extends Error { constructor(a) { super(a?.message) } }witha.message === undefined: perrynew E({}).message === "undefined"(node/bun""), and that ownmessageshadows a subclassget message().Data.TaggedError(tag)subclasses expose internal keys like__perry_cap_23m0000b400ee61inObject.keys(node/bun: only the user fields).fs.promises.readFile(missing)resolvesundefined(node rejects withENOENT), andfs.readFileSync(missing)does not throw.