Repository navigation
cc 2.1.112 on current main: compiles cleanly, then dies at startup with "Cannot set property errorCode of #<VR8> which has only a getter" #11499
Description
Activity
Correction: the #11174 fixes ARE in this arm — my ancestry note above was wrong
The closing note in the issue body said
c025870294and256925ac46are not ancestors off3828c7ec. That is false. Re-checked from a freshly fetched clone:c025870294 IS an ancestor of origin/main and of f3828c7ec 256925ac46 IS an ancestor of origin/main and of f3828c7ecThey landed as #11226 / #11263 on 09-24/25. My check ran against a repo whose ref set was incomplete — that host's shared repo had been re-seeded from another clone, so
merge-baseanswered against refs it did not have. Ancestry from a re-seeded or partially fetched repo is not evidence; that is my error, not a problem with the fix.This matters in the right direction: it explains the good news in this issue. The #11301 failure (
value is not a functionfrom an unregisteredMath) is gone in this binary precisely because the #11174 fix is present — the no-auto HTTP rebuild no longer bundles a runtime built withoutperry-runtime's default features. So that diagnosis is corroborated here, and theerrorCodeaccessor failure below is a genuinely separate, later bug rather than the same one wearing a new face.Root cause: a STATIC write on a class constructor is resolved against the INSTANCE accessor chain
Confirmed in the debugger on the symbolized build (
ccMAIN_sym,f3828c7ec), breaking onclass_chain_setter_applywith the key filtered toerrorCode:class_id = 7515 key = errorCode receiver bits (xmm0) = 0x7ffd041fa5e0bf18 --- runtime's own printer on the receiver --- VR8 [Error] --- js_object_get_class_id(receiver) --- $1 = 7515The receiver is the class constructor object (
VR8 [Error]), not an instance — and it carries a non-zeroclass_id. Native path:class_chain_setter_apply set_field_by_name_object_tail proxy::target_set proxy::create_or_update_receiver_property proxy::ordinary_set_with_receiver js_put_value_set js_put_value_set_packed_miss … cli_2_1_112_js closures (module init)Why that is wrong
In the bundle:
XX = class XX extends Error { get errorCode(){ return this.constructor.errorCode } }; // INSTANCE accessor VR8 = class VR8 extends XX {}; VR8.errorCode = "invalid_request"; // STATIC set on the constructor object
VR8.errorCode = …is anOrdinarySeton the constructor. The constructor's prototype chain is the static side —VR8 → XX → Error → Function.prototype— and there is noerrorCodeaccessor anywhere on it. The getter lives onXX.prototype, which is not on the constructor's chain.set_field_by_name_object_tail(atobject/field_set_by_name/tail.rs:384in this tree) guards only onclass_id != 0before consultingclass_chain_setter_apply, i.e. the instance accessor chain. Since the constructor object carries the class'sclass_id, a static write is resolved against instance accessors, finds the getter-onlyerrorCode, and takes theSome(false)arm:let class_id = (*obj).class_id; if class_id != 0 { match class_chain_setter_apply(class_id, name, this_f64, value) { Some(true) => return, Some(false) => throw "Cannot set property {name} of #<{class_name}> which has only a getter", None => {} } }
The comment above that arm — "Class accessors are properties of the class prototype chain" — is true for instances. The missing condition is that the receiver must be an ordinary instance, not the class object itself.
Fix shape
Apply the instance accessor chain only when the receiver is not a class constructor. A regression test should pin the bundle's shape directly, since it is ordinary ES semantics:
class XX extends Error { get errorCode(){ return this.constructor.errorCode } } const VR8 = class VR8 extends XX {} VR8.errorCode = "invalid_request"; // must NOT throw new VR8("x").errorCode === "invalid_request" // getter delegates to the static
Worth pinning the same for a static data write shadowed by an instance getter of the same name on any base in the chain, and for
Reflect.set(VR8, "errorCode", v)— the failing path here runs throughordinary_set_with_receiver, so the receiver-carrying entry point deserves its own case.Notes on two suspects raised elsewhere
- fix: class methods called on class-id-0 objects whose prototype chain reaches the class #11206 ("class members reached through a class-id-0 object") is the right neighbourhood but not this mechanism as stated: the receiver's
class_idhere is 7515, not 0. - Frame names in the JS stack are unreliable for this bug. The reported stack blames
OBY, butOBYin the bundle is a render loop (function OBY(q,K,_,z,Y,A){let{width,height,index}=q; …}) and is uniquely defined — so it is a nearest-symbol artefact, not the writer. The native backtrace above is the one to work from. - Not GC-sensitive: identical under
PERRY_GEN_GC=0andPERRY_GC_FORCE_EVACUATE=1.
- fix: class methods called on class-id-0 objects whose prototype chain reaches the class #11206 ("class members reached through a class-id-0 object") is the right neighbourhood but not this mechanism as stated: the receiver's
- added 4 commits that reference this issue
on Sep 27, 2026
On current
main(f3828c7ec),claude-code2.1.112 compiles cleanly but still dies at startup — with a different error from the one in #11301:--versionworks (2.1.112 (Claude Code));--helpanddoctorboth fail identically. This is the current blocker for any cc-based runtime measurement.First: the reported compile failure does NOT reproduce
A parallel lane reported "ENTRY MODULE FAILED TO COMPILE — REFUSING TO LINK" for this bundle on
a90cd9d44, and a separate IR dump found a function__44728with undefined SSA values. Neither appears here. Onf3828c7ec:The full 676-line log (kept unfiltered) contains no
__44728, no verifier failure, and no "REFUSING TO LINK". Its only notices are the expected AOT-unsupported sites (3Function(...), 1eval(...), 1import(...),process._rawDebug) plus oneunknown identifier 'MessageEvent'warning. Built with compiler and runtime from the same tree (--no-auto-optimize --enable-wasm-runtime), run under an exclusive heavy-job lock with 28 GB of headroom reserved — the earlier failure was at 24.2 GB peak while other lanes compiled, so resource starvation is the likelier explanation for that report than a codegen defect.The construct
The getter is an instance accessor (it lives on
XX.prototype).VR8.errorCode = …is a static set on the constructor. Perry reports that the class object has a getter-onlyerrorCode, i.e. the instance accessor is visible on the static side, so the static assignment hits it and throws. In node the same code runs andhasOwnPropertyis true on both the class and the prototype.The
new __AnonShape_*frames match the anonymousclass … extends XX {}expressions, andLis esbuild's__esmlazy-init wrapper, so this runs during module initialisation.Characterisation
PERRY_GEN_GC=0andPERRY_GC_FORCE_EVACUATE=1.HOME.value is not a functionno longer appears at all (grep count 0), so this is a distinct, later failure rather than the same one re-surfacing.__esmlazy init in a 47,000-closure program.Note for #11301 / #11469
The commits named there as fixing #11174,
c025870294and256925ac46, are not ancestors off3828c7ec—git merge-base --is-ancestorreports both absent from currentmain. Worth confirming where they actually live before treating that fix as landed.