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
cc --help parity gate down on main: defineProperty-on-non-object at startup — window pins #9326 (indexed writes with a recorded custom array prototype) #9341
The cc --help parity gate is DOWN on main: the natively-compiled claude-code bundle (cli_2.1.112.js, the standard PERRY_NO_CACHE=1 … --no-auto-optimize --enable-wasm-runtime recipe) exits rc=1 with
Uncaught (in promise) TypeError: Object.defineProperty called on non-object
at <anonymous>
instead of printing the 9,175-byte help text. Deterministic — three independent binaries, every run:
All three compiled on perrymaster with the same recipe and verified against the same node cli_2.1.112.js --help output (rc=0, 9,175 bytes). The perry-runtime lib suite is green at tip (2904 passed) — the breakage is invisible to it.
Window
83754818e..9943dd95c is 28 commits, but d1bef4c (#9323, "cc --help segfault fixed, parity gate back online") re-verified the gate at its own merge point — which already contains #9300/#9302/#9315/#9316/#9317/#9318. That leaves exactly two commits between the verified-green #9323 and the failing #9328 tip:
so unless #9323's gate claim was not actually run, #9326 is the culprit by elimination.
Mechanism hypothesis, consistent with the error: the bundle retargets array prototypes early (polyfill/subclass boilerplate), which arms the process latch; after that, array[k] / HasProperty on any array with a recorded prototype routes through the new array_spec_get / array_spec_has_index lanes. A read that now yields undefined (or a changed HasProperty verdict feeding an index walk) lands in Object.defineProperty(<that value>, …) → throw_object_type_error(b"Object.defineProperty called on non-object") (object_ops/define_property.rs:429).
Found while measuring an unrelated runtime change (shape-descriptor no-copy reads) whose A/B had to be moved back to 83754818e because neither arm can pass the gate on current main. Also note: tonight's r6 headline measurement in /root/claude-arch/measure_r6.log silently measured the BROKEN binary (its --help run does ~0.69B instructions vs node's 4.05B because it dies early) — those numbers should be discarded.
Revert merged (#9345) — main should be back to a working cc --help.
I merged #9326, so this one is on me, and the miss is worth stating precisely because it wasn't a gap in the analysis of the change itself.
I validated #9326 against the gap fixture it targets (test_gap_9220_9221_array_proto_paths.ts, byte-identical to the pinned oracle), the perry-runtime suites, the full lint set, and a clean release build. All green. What I did not do was run the cc --help parity workload — which #9323 had restored to service three merges earlier, in a PR I had reviewed and merged myself that same evening.
That is the actual lesson here: a runtime-semantics change to indexed writes is exactly the kind of thing that primary workload exists to catch, and the fact that the gate had just come back online is what should have made me reach for it. A green gap fixture and a green unit suite were never going to see this — as #9341 notes, the runtime lib suite is green at the failing tip too.
One clarification on scope, since #9326 was #9297 plus an adjustment of mine. My part was moving array_spec_set back into indexing.rs so its seven pre-existing raw-handle sites kept their existing ceiling, plus a pub(crate) visibility bump and an import fix — a pure relocation with no semantic change, which compiled and tested clean. The behavioural change in the window is #9297's own: honouring a recorded custom array prototype on indexed writes. I mention this only so the fix-forward looks in the right place, not to move the responsibility for merging it.
Happy to help with the diagnosis when someone picks this up. Object.defineProperty called on non-object on a path that now consults a recorded array prototype suggests the new lookup is answering for a receiver the old one declined, and the defineProperty path is then handed something that isn't an object — but that's a hypothesis, not a finding, and the preserved known-good/known-bad binaries at /root/claude-shapedesc/ are a better starting point than my guess.
A measured finding that matters beyond this issue: the gap suite cannot see this regression.
I built the pre-revert commit (6bc775d9b9, containing #9326) as a known-bad binary and ran the full 609-test gap suite against it, then the same suite against post-revert main. The two runs are indistinguishable:
The 5 failures are the recorded known set in both cases. I also wrote targeted defineProperty probes — prototype index setters, Array.prototype index installation, defineProperty on values read back through a custom-prototype array, sparse reads, growth through the custom-prototype path — and every one is byte-identical to node on the known-bad build.
So the detection surface for this class is currently: perry-runtime --lib green, 609-test gap suite green, hand-written probes green, and cc --help the only thing that fails. That is a single point of detection for a path that changes indexed-write semantics.
Two consequences worth acting on:
This is why #9326 landed. I validated it against its target gap fixture, the runtime suites, the lint set and a clean build — all green, and now demonstrably all incapable of catching it. The lesson I drew earlier ("I should have run cc --help") is right but incomplete: no locally available gate would have caught it either.
A fixture for this would close a real hole. Whatever minimal shape reproduces the Object.defineProperty called on non-object failure is worth adding to test-files/ as a gap test, because the class it represents — prototype-sensitive [[Set]] routing — is exactly the kind of change that gets made often and can currently land green. The preserved binaries at /root/claude-shapedesc/ are the obvious starting point; my probes above did not find the shape, so it likely needs something structural the bundle does that a small fixture has to reproduce deliberately.
I have held #9339 back for the same reason and said so there — it reintroduces the sole call site of the reverted path, and a green gap run on it would not be evidence of anything.
Negative result, posted so nobody repeats it: I tried to minimise this into a fixture against a purpose-built known-bad binary (6bc775d9b9, containing #9326) and could not reproduce it. Three rounds, all byte-identical to node on the broken build:
Direct shapes — defineProperty with a prototype index setter, installing an index on Array.prototype, defineProperty on non-objects (null, a number) to check the TypeError path itself.
Prototype + growth — defineProperty applied to values read back out of an array with a custom prototype, including after 40 indexed writes that grow it, and via a sparse read at index 100.
Bundler idioms — the esbuild/tsup __defProp / __export / __copyProps helpers over a module registry held in an array, the same with a custom-prototype registry, and the whole thing again inside a Promise.resolve().then(...) chain to match the "Uncaught (in promise)" framing.
So the trigger is something more specific than "defineProperty near a prototype-sensitive indexed write". That narrows it usefully: it is unlikely to be the plain helper shapes, and more likely to need something structural the bundle does — the wasm runtime path, a deeper prototype chain, or a receiver that only becomes non-object after a particular sequence.
Whoever picks this up is far better placed than I am: the known-good/known-bad binaries at /root/claude-shapedesc/ plus the bundle allow bisecting the input rather than guessing the shape. A --trace hir --focus on the frame that calls defineProperty in the failing binary would probably identify the receiver in one pass.
Standing conclusion from the earlier comment is unchanged: the 609-test gap suite returns an identical verdict on the known-bad and known-good builds, so this class currently has exactly one detector, and it is not one that runs locally.
Correction: #9326 was NOT the culprit. The revert (#9345) is merged and cc --help still fails identically.
Measured on e539a6359 — which contains the revert (14fcf6b942) — freshly built and compiled on perrymaster:
=== PARITY GATE ===
perry rc=1
node rc=0
< Uncaught (in promise) TypeError: Object.defineProperty called on non-object
Same error, same rc, 5/5. So the revert removed correct work and fixed nothing, and the real culprit is still on main.
Where the attribution went wrong
The elimination rested on "#9323's fix branch was verified cc-green and contained everything through ~#9322." That premise was false, and I had already disproved it before accepting the conclusion: the #9323 work was based on d6794cf771 (#9313), which does not contain #9300, #9315 or #9317 — I checked exactly that with git merge-base --is-ancestor and recorded "window is bigger than #9326," then let a second, independently-derived elimination override my own contradicting check instead of reconciling the two.
The lesson, stated plainly because it is the reusable part: when a second line of evidence contradicts a check you have already run, the disagreement is the finding — reconcile it before acting. Two confident attributions agreeing on a wrong answer is exactly what "independent confirmation" is supposed to prevent, and it did not, because both inherited the same false premise about what the verified-good point contained.
The real window
Last verified-green: 83754818e (#9242, per the independent shapedesc build) / d6794cf771 (#9313, the #9323 base).
Broken: current main.
Candidates between them (all merged since), several of them mine: #9312 JSON tape gate · #9314 ELF/scrypt · #9300GC map v5 · #9315own-keys enumeration · #9316 strided fill kernel · #9318 affine index bound · #9317tombstone old_carrier · #9319 mysql2 · #9320/#9321/#9322 lint/green fixups · #9326 **(now exonerated by the revert)`.
On the error's own evidence, #9315 deserves first look — it changed class-member registration and own-keys enumeration, and the failure is a defineProperty receiver arriving as a non-object during help-path module init. #9300 (GC map) and #9317 (shape publish) are the other two runtime-semantics candidates.
A proper bisect over that window is starting now. #9297's content should be re-landed once the true culprit is found — the revert bought nothing and #9220/#9221 remain real bugs.
app_Dsegfaults. It never prints the TypeError. Anyone differentially debugging #9341 against it is chasing the pre-#9323 longjmp segfault, not this issue.
So the confirmed bracket is green 83754818e → broken a03be729c, and a03be729c predates #9331/#9332/#9333/#9340. (measure_sd2.sh also records 9943dd95c (#9328) as already failing, which is release-only — consistent with the culprit being at or before #9326's merge point.)
2. The failure is deterministic and completely GC-independent
Five kill switches on the reproducing binary, all 5/5 identical failures:
Turning the moving collector off and raising the heap limit does not change the failure by one byte. This is not a GC or rooting bug. That eliminates, as mechanisms:
Both emit the identical byte string, so the message alone cannot tell them apart. I found a genuine 4-line reproducer for the util_inherits one:
constutil=require("util");classBase2{}classDerived2{}util.inherits(Derived2,Base2);console.log(typeofDerived2.super_,Derived2.super_===Base2);// node: function true// perry: TypeError: Object.defineProperty called on non-object rc=1
But it fails identically on the green 83754818e build. It is a pre-existing gap, not this regression — filed separately below. Since 83754818e compiles cc --help byte-identical to node, the bundle cannot be reaching that site. The cc throw is define_property.rs:429: a receiver that is neither a registered class-ref, nor object-like, nor a small handle.
and class_ref_id returns Someonly if is_class_id_registered(class_id). A class-ref whose id is not in REGISTERED_CLASS_IDS and has no vtable entry is an INT32-tagged non-pointer, so it fails value_is_object_like and throws precisely this message. That is consistent with the earlier session's gdb note that the receiver "resolves via helper to a class-ref".
4. What I could not reproduce in isolation (35 shapes, all byte-identical on the broken compiler)
So that nobody re-runs these: on the broken a03be729c compiler, all of the following are byte-identical to node — defineProperty on empty classes, static-field-only, getter-only, method-only, class expressions (named and anonymous), subclasses, classes returned from a factory, class prototypes, symbol-keyed classes, static-method-only; getOwnPropertyNames on constructors and prototypes; typeof on class refs; and the whole of #9315's changed computed-symbol lowering surface ([Symbol.iterator] generator and non-generator, [Symbol.asyncIterator], [Symbol.toPrimitive], [util.inspect.custom], static [Symbol.hasInstance], [Symbol.toStringTag] getter and method, and a mixed class where a computed member precedes static/accessor/method members). Also byte-identical: the real util.inherits shapes the bundle actually uses — delayed-stream, combined-stream, EventEmitter subclassing, zlib.Inflate as superCtor, and Ctor.prototype = {…} wiring.
A diagnostic runtime build (receiver bits, tag, class id, is_class_id_registered, and the property key at the throw) is building now; one cc compile with it should name the failing receiver and key directly.
Unasked finding, worth its own issue: util.inherits() with an ES class constructor throws
set_super_property handles a closure ctor and an object ctor, and throws for everything else. A class constructor reaches it as an INT32-tagged class-ref, which is neither — so util.inherits(SomeClass, X) throws Object.defineProperty called on non-object where Node sets SomeClass.super_. Reproducer is the 4 lines above; it fails on 83754818e as well as on tip, so it is long-standing and unrelated to this regression. Note the asymmetry: util.inherits(function(){}, SomeClass) is fine — only a class in the first position fails. The fix is presumably to route a class-ref ctor through the class static-property store the way define_property.rs already does via class_ref_id. Happy to file it with the fixture if nobody else has.
Still broken on tip. #9340 did not fix it, and the revert of #9326 did not fix it.
Fresh build at 0f95fbc9f (current main — contains #9340 "preserve array prototypes across growth", #9351, #9337, the #9345 revert, and everything else):
LIBS_RC=0 BIN_RC=0 CC_RC=0
PARITY: 0/5 byte-identical (rc=0)
Uncaught (in promise) TypeError: Object.defineProperty called on non-object
A bisect over that range is running, along with fixture probes against paired green/broken builds — a minimized fixture is the higher-value deliverable and will be posted here as soon as it exists, with or without the commit attribution.
What this costs, stated plainly
cc --help is the project's primary real-application parity workload. It has now been broken on main for the better part of a day, which means every cc measurement any session takes against main is void, and any branch measured on top of it appears to be the cause. This is the third such episode this week (#9169's window, #9305, this one) and the direct argument for the pre-merge cc parity gate proposed in #9346 — every one of the three shipped with its author's fixtures and unit suites green, and only the bundle catches the failing shape.
Status + one correction to my own earlier post, because it contradicts something I wrote above.
Reconciliation first
My previous comment listed emitter-fire, zlib-inherits, protoobj-inherits and ~35 other shapes as "byte-identical to node on the broken compiler". That is exactly what they are — identical, therefore useless as bisect fixtures. They do not differentiate green from broken; they differentiate nothing. I am stating that plainly because a list of green probes reads like progress and is not.
The two shapes I did find that diverge from node both fail on the green build too:
So: I do not yet have a fixture that separates green from broken. That is the thing still missing, and I am not going to present either of the above as if it were one.
Confirmed: still broken on tip
Independently confirmed by the parallel run on 0f95fbc9f — CC_RC=0, then PARITY: 0/5. #9340 did not fix it. The bracket is 83754818e green → tip broken, still live.
The window is 52 commits, not 15
Worth correcting, because the candidate list in circulation (#9312, #9314, #9300, #9315, #9316, #9318, #9317, #9319, #9320-#9322, #9326) is only its tail. git rev-list --count 83754818e..a03be729c = 52. The head of the window contains, among others:
9ee99197c #9250 inline array-store tier covers tagged receivers
322b6c1e4 #9257 receiver-region model + equivalence lint
c1c8623ed #9270 use live receiver in stable packed loops
6d08afc29 #9272 inline address window in front of the registry probes
3a9b203ad #9284 keep shape barriers in source modules
87270e6f2 #9291 Bloom filter for the heap-allocated registries; #9225 linear scan gated
0515179a4 #9295 classify prototype wiring as class-default
986f4e121 #9296 preserve renamed namespace constructor args
730e0a997 #9302 overflow-slot properties prime the constant-key ICs
Several of these are much closer to "a receiver arrives as a non-object" than anything in the tail. Anyone reasoning about this window should be using the full 52.
A narrowing that is evidence rather than a hypothesis
The failing throw is define_property.rs:429 (established above: the only other site is util_inherits.rs, and that one fails on green, which compiles cc --help byte-identical). The guard that reaches it is exactly:
descriptor_helpers.rs, addr_class.rs, class_ref_values.rs, define_property.rs and class_meta.rs are untouched across the whole window. So if the receiver is a closure that stopped being recognised, #9272 owns it; if it is a class ref whose id stopped being registered, the registration call sites (codegen/HIR) own it rather than the predicate. That does not name the culprit, but it is a real constraint rather than a prior. (I read #9272's is_closure_ptr change closely — it is a pure reordering of a conjunction, and I could not fault it on inspection. Inspection is not measurement.)
What the receiver can actually be
Instrumenting Object.defineProperty on the real node cli_2.1.112.js --help run (patching the builtin and filtering by stack to calls originating in the bundle) gives 5,177 distinct call shapes, of which the dominant ones are zod v4's $constructor:
[651] object ZodString key=_zod @ $ZodType.z (cli.js:68:78640)
[540] object ZodOptional key=_zod @ $ZodType.z
[161] CLASS (class A extends Y{}) key=name @ b1 (cli.js:68:78939)
[161] function O key=init / Symbol(Symbol.hasInstance) / name @ b1
$ZodType.z is Object.defineProperty(inst, "_zod", …) where inst = params?.Parent ? new Definition() : this — i.e. the receiver is this, which lines up with the earlier session's gdb note that the receiver "resolves via helper to a class-ref". That is the single most-executed defineProperty receiver on the help path, so it is where I would expect the failure to sit.
I could not reproduce it in isolation: faithful reductions of $constructor (with and without Parent, chained 3 deep, 200 evaluations, Symbol.hasInstance, per-evaluation class identity, 500 repeated class evaluations) are node-identical on the broken compiler, and the real zod 4.4.3 (spike/tests/02-zod.ts against a real node_modules) compiles and runs byte-identically on green and broken. So whatever it is needs more of the bundle's context than a reduction reproduces — which is why I stopped reducing and went to instrumentation.
In flight
A diagnostic runtime built at a03be729c prints, at the define_property.rs:429 throw, the receiver's raw bits, NaN-box tag, class id, is_class_id_registered, pointer/undefined/null predicates and the property key — with the util_inherits site given a distinct marker so the two can never be confused again. Validated first on fixtures that hit each site deliberately (a diagnostic that cannot fire teaches nothing). The cc compile with it is running now; it should name the receiver and the key directly.
Method note for whoever picks this up next
/root/claude-keep/app_D segfaults rather than reproducing this bug (see my earlier comment) — and separately, two cc-scale compiles were running concurrently on perrymaster earlier today with available memory at 0 GB. A cc compile peaks around 29 GB, and an OOM kill leaves a plausible-looking binary that only rc=137 reveals. Worth gating on free before starting one.
Root cause identified. The failing call is Object.defineProperty(undefined, "getContentType", …) — a class's .prototype read is returning undefined.
The measurement
A diagnostic runtime built at a03be729c, instrumenting the define_property.rs:429 throw (and giving the util_inherits.rs site a distinct marker so the two can never be confused). One cc compile, one --help run:
TEMPDIAG9341 DP bits=0x7ffc000000000001 tag=0x7ffc cid=0 proto_flag=false
registered=false is_ptr=false is_undef=true is_null=false key=Some("getContentType")
Uncaught (in promise) TypeError: Object.defineProperty called on non-object
Exactly one such throw in the whole run. The receiver is undefined — tag 0x7FFC, is_undef=true. It is not a class ref, not a closure, not a handle, not a corrupted pointer. It is a value that should have been an object and is undefined.
That kills three standing theories outright, including one of my own: it is not an unregistered class ref (cid=0, registered=false because there is no class id at all), not a closure the predicate stopped recognising, and not the util_inherits site.
Where it is in the bundle
The key names it. getContentType is generated by axios's AxiosHeaders.accessor, which in cli_2.1.112.js (line 68) is:
staticaccessor(q){let_=(this[lb7]=this[lb7]={accessors:{}}).accessors,z=this.prototype;// <-- z is undefinedfunctionY(A){letO=VU6(A);if(!_[O])xp5(z,A),_[O]=!0}returnH1.isArray(q)?q.forEach(Y):Y(q),this}
...
kU6.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]);
xp5(obj, header) is axios's buildAccessors, which does Object.defineProperty(obj, "get"+Name, …) for get/set/has. Content-Type is the first entry and get is the first method, so getContentType is the first accessor built — i.e. this fails on the very first call, which is why there is exactly one diag line.
So the receiver xp5 gets is z, and z = this.prototype evaluated to undefined inside a static class method where this is the class.
Restating the bug in one line: reading .prototype off a class returns undefined in the compiled bundle. Everything downstream — the TypeError, the "in promise" framing, the 0-byte --help — follows from that.
Two corroborating facts
--version works on every broken build (rc=0, 21 bytes, byte-identical to node) while --help fails. The failure is on the command/option-tree construction path that --version short-circuits past.
Under PERRY_FOR_IN_DIAG=1 the broken binary emits only 4 for-in checks before dying, so this is early on that path, not deep in help rendering.
What does not reproduce it (so nobody repeats this)
I reduced the axios shape faithfully and it is byte-identical to node on the broken compiler, and identical between the green and broken builds:
this.prototype inside a static method — fine;
Object.defineProperty(this.prototype, …) inside a static method — fine;
the full AxiosHeaders.accessor shape: symbol-keyed static write this[SYM]=this[SYM]={accessors:{}}, let z=this.prototype captured by a nested named function, Array.isArray(q)?q.forEach(Y):Y(q),this comma-sequence return, ["get","set","has"].forEach inside the builder, over the real six-header list — fine, accessors built and callable;
the same as a class expression assigned to a variable and aliased — fine;
with and without the symbol write, the comma sequence, the closure capture — all fine;
real axios is not the only consumer: real zod 4.4.3 (spike/tests/02-zod.ts against actual node_modules) compiles and runs byte-identically on green and broken.
One genuine divergence turned up on the way — C.accessor = function(){ return this.prototype } returns undefined in perry and the prototype object in node — but it fails on the green build too, so it is pre-existing and not this regression. (Filed as part of the sweep below.)
The reduction gap is itself informative: the shape is correct but the context is not. A property read that is correct cold and wrong warm is the signature of an inline cache, which is where I would look first.
Which commits can do this
Asking git which of the 52 window commits touch the class-.prototype read path (object/field_get_set/, object/class_registry/, object/prototype_chain.rs) gives exactly four:
87270e6f2 #9291 Bloom filter in front of the heap-allocated registries; #9225's linear scan gated
730e0a997 #9302 overflow-slot properties prime the CONSTANT-KEY ICs (27 -> 5 ms)
d6794cf77 #9313 keep main green after the #9302/#9303/#9307 batch
8b2cfe6e7 #9315 class-prototype own-keys enumeration
this.prototype is a constant-key property read, and #9302 changes how constant-key ICs are primed — a warm-only wrong answer is exactly what that can produce, and it is exactly what the reduction gap points at. #9313 exists specifically to "keep main green after the #9302 batch", which says that batch had fallout. #9291's filter is release-only in effect (its audit is #[cfg(debug_assertions)]), which would explain invisibility to every debug-run suite.
I am not claiming any of these yet — this list is what the failing path can reach, not evidence.
In flight
A build at d6794cf77 (#9313) with #9323's longjmp fix cherry-picked (it applies cleanly — #9323's own base is d6794cf77) is compiling now. The cherry-pick matters: every point below #9323 segfaults on cc --help for the unrelated reason #9323 fixed, so without it a bisect step below that line answers a different question.
Two more filings from the reduction sweep, both pre-existing (identical on green 83754818e and broken a03be729c) but both in the exact machinery this regression lands on:
Together with #9362 and #9364 that is four distinct ways .prototype handling answers undefined or crashes on green, all silent. That is the reason this bug cost three wrong attributions: the failure mode is a silent undefined, so it surfaces hundreds of instructions later as a defineProperty type error with no connection to its cause. A store that cannot be honoured should not be discarded silently — making #9365 loud would likely have turned #9341 into a one-hour bug.
To be explicit about what is still missing: none of these four is the regression, and I do not yet have a fixture that separates green from broken. Roughly 90 shapes tried (full axios and zod $constructor reductions, real zod 4.4.3, esbuild lazy-init class expressions, per-evaluation class identity, forced codegen-unit splitting at 2/8/32 units) are either identical across the two builds or diverge from node on both. The bisect is the path; a build at d6794cf77 (#9313) + #9323's longjmp fix is in its cc compile now.
Bisect step 1: d6794cf77 (#9313) + #9323's longjmp fix is GREEN — 3/3 byte-identical to node.
p9313 d6794cf7719165dcc2cb06ba03265646981ec32b GREEN (3/3)
Two things follow.
1. The premise everyone has been leaning on is now actually verified. "#9323's branch was cc-green on base d6794cf77" has been load-bearing since the first attribution and had never been independently rebuilt — it is the claim whose containment half was false and which produced the #9326 misattribution. Its greenness half holds: freshly built and compiled here, that content is byte-identical to node.
The cherry-pick matters and is worth stating: every point below #9323 segfaults on cc --help for the unrelated reason #9323 fixed, so a bisect step down there without it answers a different question. #9323's own base is d6794cf77, so it applies cleanly.
2. The window is now #9312 … #9336, i.e. the commits between d6794cf77 and the confirmed-broken a03be729c:
69b068a23 #9312 JSON.parse<T[]> tape gate
9c8cdfc2e #9314 ELF archive grouping + scrypt
015ec5fe1 #9300 GC map v5
8b2cfe6e7 #9315 class-prototype own-keys enumeration
19460b717 #9316 strided constant-fill kernel
b96f2fb0d #9320 lint/green fixups
124b38ecc #9318 affine index magnitude bound
804663479 #9317 tombstone shape publish / old_carrier
f850ec1df #9319 mysql2 prepared statements
192ff13d2 #9321 rustfmt
cb0102fb1 #9322 lint gates
1f8ad06fa #9326 (exonerated — its revert is on main and the break persists)
9943dd95c #9328 release: macOS dependency cache
21f1c4c15 #9329 dyn-IC transition fast path
a03be729c #9336 keep main green after #9329
Against the evidence already in hand, #9300 and #9317 are out as mechanisms (the failure is completely GC-independent — five kill switches, all identical), and #9315 is the only commit in this list that touches the class-.prototype read path the diagnostic implicates. So the coordinator's original prior on #9315 now has evidence under it rather than intuition — but that is still a prior, and I am not going to call it before the build says so.
Step 2 is running:015ec5fe1 (#9300), which is #9315's immediate parent.
Either way the next step after it is decisive. Infrastructure, fixtures, eliminations and the decision tree are written up in /root/claude-9341b/README.md and NEXT_STEPS.txt on perrymaster so this is resumable by anyone.
So the trigger is: a non-generator computed [Symbol.iterator]() member in a class, followed by a static method that reads this.prototype and hands it to Object.defineProperty. With the computed member, this.prototype inside the static method becomes a non-object; without it, it is the prototype.
It also reproduces with the class as an expression assigned to an outer variable (H3 in my fixture), which is the form the bundle uses.
Why that is exactly the bundle
axios's AxiosHeaders is that class, member for member:
kU6=class{…[Symbol.iterator](){returnObject.entries(this.toJSON())[Symbol.iterator]();}// non-generator, computedstaticaccessor(q){…letz=this.prototype;…xp5(z,A)…}// z === undefined};kU6.accessor(["Content-Type","Content-Length","Accept","Accept-Encoding","User-Agent","Authorization"]);
and xp5 = buildAccessors, whose first defineProperty is getContentType — the exact key the diagnostic reported.
And at the cc --help level, d6794cf77 (#9313) + #9323's fix is GREEN 3/3 byte-identical.
8b2cfe6e7 (#9315) is building now. If it fails the fixture, the attribution is complete: green at its parent 015ec5fe1, broken at it.
#9315 is the prior mechanism too, and this fixture is precisely the surface it changed. #9315 narrowed generic_computed_member_key so that only three shapes keep the special lowering — a generator[Symbol.iterator], a static [Symbol.hasInstance] method, and a [Symbol.toStringTag] getter:
Before that change, is_symbol_iterator_key(&computed.expr) matched any[Symbol.iterator] member. So a non-generator[Symbol.iterator]() — axios's exact form — moved from the special path into the generic computed-member path, and the generic path sets seen_generic_computed_member, which gates how the members after it register (can_source_order_register). The static accessor method that follows is one of those members.
That is a hypothesis about the why; the build will settle the whether.
Please add this to test-files/
This is the fixture that has been missing all week — the class this bug belongs to (a computed class member changing how later members register) can currently land green through every local gate: perry-runtime --lib, the 609-test gap suite, hand-written defineProperty probes and the real libraries all pass on the broken build. I verified separately that real zod 4.4.3 and ~90 hand-built shapes are byte-identical across green and broken; only this one separates them.
Worth pinning both arms — the failing class and the H2 control — since a fixture that only asserts the failing case cannot show that the computed member is the trigger.
Culprit: #9315 (8b2cfe6e7) — green at its parent, broken at it
015ec5fe1 #9300 (immediate parent of #9315) + #9323 longjmp fix fixture PASSES <- green parent
8b2cfe6e7 #9315 + #9323 longjmp fix fixture FAILS <- broken at
Both arms built with the identical recipe and the identical cherry-picked #9323 fix, and both tested with the same fixture. The failure at 8b2cfe6e7 is the exact #9341 message:
H1-static-this-prototype => object
H1-accessor !! Object.defineProperty called on non-object <- #9341
H1-iterate => [["a",1]]
H2-no-computed-accessor => function <- control, still works
H3-expr-computed-accessor !! Object.defineProperty called on non-object
015ec5fe1 is 8b2cfe6e7's immediate parent, so the window is closed to one commit.
A cc --help build at 015ec5fe1 is finishing now as independent confirmation at the bundle level; the fixture chain above already closes the attribution.
Mechanism
#9315 narrowed generic_computed_member_key in crates/perry-hir/src/lower_decl/class_decl.rs. Before, any well-known-symbol computed key kept the special lowering:
Note the is_generator conjunct. A non-generator[Symbol.iterator]() — which is exactly axios's form — therefore moved from the special path into the generic computed member path. The generic path sets seen_generic_computed_member, which gates can_source_order_register for every member declared after it. In the failing class the member after it is the static accessor method, and inside that method this.prototype comes back a non-object.
The control in the fixture isolates it: delete only the [Symbol.iterator]() line and the same class works on the broken build.
That this reached cc --help and nothing else is consistent with the shape being uncommon — a computed well-known-symbol member followed by a static method that reflects on this.prototype. axios's AxiosHeaders is that class, and getContentType is the first accessor it builds, which is the key the diagnostic reported.
Recommendation
The narrowing is not wrong in intent — it is what makes the #9226 own-keys enumeration correct — so I would not reflexively revert #9315 and lose that. The defect is that the generic path's seen_generic_computed_member / can_source_order_register gating changes how subsequent members register in a way that leaves the class's prototype unreachable from a later static method. The narrow fix is in that gating, not in the is_generator predicate.
If a revert is wanted for release safety in the meantime, revert #9315 — but please re-land it with the gating fixed, since it is the only thing holding the #9226 fixture at 0 diverging lines.
Also worth acting on, from the same investigation: #9297's content should still be re-landed (the #9326 revert bought nothing — #9314/#9326 were never the cause), and #9365 should be fixed on its own merits, because a .prototype store that silently does nothing is what turned a one-line lowering change into a week of misattribution.
Reproduction
Fixture and both arms: /root/claude-9341b/fx/r1.ts on perrymaster; README.md there has the full method, the eliminations, and the four adjacent pre-existing bugs (#9362, #9364, #9365, #9366). Each arm is a ~6 minute compiler build plus a ~2 minute fixture compile — no cc compile needed to test this any more.
Inside one function, on one object, the same property read gives two different answers depending on whether it is reached through this or through the class's own name.this === H1 is true, typeof this is "function", this.name is "H1" — the receiver is correct by every other measure. Only this.prototype is undefined.
Remove the [Symbol.iterator]() line and this.prototype is an object again. Verified pre-existing-free: r2 is byte-identical to node on 83754818e.
The class-ref .prototype read lives in get_field_by_name.rs:
if name == "prototype"
&& class_id != 0
&& is_class_id_registered(class_id)
&& !is_prototype_ref
{let value = class_registry::class_decl_prototype_value(class_id);if value.to_bits() == TAG_UNDEFINED{returnclass_prototype_ref_value(class_id);// synthetic fallback}return value;}
Reached with a recognised class ref it cannot return undefined — there is a fallback. So this in the static method is not being recognised as a class ref on this path, the branch is skipped, and the read falls through to a generic lookup that finds nothing. Reading H1.prototype through the named binding still takes the class-ref path, which is why the two disagree.
What #9315 changes is upstream of that. Its narrowing of generic_computed_member_key moves a non-generator [Symbol.iterator]() into the generic computed-member path, which sets seen_generic_computed_member; from that point on, every subsequent member also gets lower_noncomputed_class_member_registration pushed into computed_members (class_decl.rs, the three if seen_generic_computed_member && can_source_order_register sites). The static accessor / static probe method is one of those members, and the extra registration is what leaves its this in a form the .prototype branch does not accept.
Suggested direction for the fix
Not the is_generator predicate — that narrowing is what makes #9226's own-keys enumeration correct, and reverting it loses that. The defect is that the extra source-order registration applied to members after a generic computed member changes the receiver form those members see. Two candidates, in order of preference:
Failing that, make the class-ref .prototype branch accept the receiver form that this takes in these methods, so the two reads cannot disagree.
Whichever is chosen, the pair H1 (with the computed member) and H2 (without) belongs in test-files/ — asserting only the failing case would not show that the computed member is the trigger, and this whole bug is a case of a change to member registration silently altering member dispatch.
Fixtures on perrymaster: /root/claude-9341b/fx/r1.ts (axios-shaped, throws the #9341 message) and /root/claude-9341b/fx/r2.ts (the three-way this vs named-binding probe above). Both ~2 minutes per arm.
My previous comment said the trigger was the ordering gate — seen_generic_computed_member causing members declared after the computed one to re-register. That is wrong, and my next measurement contradicted it, so here is the reconciliation before anything else.
A static method declared before the computed member fails identically to one declared after:
static-BEFORE-computed => undefined <- kills the ordering story
static-AFTER-computed => undefined
instance-method-after => object (instance methods unaffected)
named-binding => object (H1.prototype still fine)
So it is not about order. It is: any class carrying a generic computed member loses this.prototype inside every static method of that class.
Read the last row carefully. A plain computed-key member already broke this.prototype on the green build. The defect is not new. What #9315 did was route the well-known-symbol computed members onto that same already-broken path:
generic_computed_member_key was narrowed, so non-generator [Symbol.iterator], [Symbol.asyncIterator], [Symbol.toPrimitive], [util.inspect.custom] and friends stopped getting special lowering and became generic computed members;
and separately, the generator [Symbol.iterator] branch changed from methods.push(wrapper) to computed_members.push(ClassComputedMember { … }), which puts that one on the generic path too — which is why the generator row broke as well even though it kept needs_special_lowering.
#9315 is the culprit, but it did not introduce the defect — it moved every well-known-symbol computed class member onto a generic computed-member path that already lost this.prototype in static methods. axios's AxiosHeaders has a non-generator [Symbol.iterator]() and a static accessor() that reads this.prototype, so the bundle moved onto the broken path and cc --help went down.
This is worth stating plainly because it changes the fix: reverting #9315 restores cc --help but leaves the underlying hole, one plain computed key away from the next occurrence — and it costs the #9226 own-keys fix. Fixing the generic computed-member path's effect on static-method this fixes both, and closes the pre-existing plain-computed-key case at the same time.
That pre-existing half is filed separately as #9367 so it can be fixed on its own merits whichever way the revert decision goes.
/root/claude-9341b/fx/r2.ts — the contradiction in one line: this === H1 is true, typeof H1.prototype is object, typeof this.prototype is undefined.
/root/claude-9341b/fx/r3.ts — the table above; separates the regression from the pre-existing case, and is the one worth committing since it pins which member kinds may take the generic path.
I would put r3 in test-files/ over the other two: it is the only one that would have caught #9315and would catch a future change that widens the generic path again.
Correction to the comment above: the pre-existing half is #9369, not #9367 — I wrote the number before the issue was created and guessed wrong. Everything else in that comment stands.
#9369: class E { [K]() {} static probe() { return typeof this.prototype; } } with a computed key K — this.prototype is undefined inside every static method while E.prototype is an object, on green as well as broken.
015ec5fe1 is 8b2cfe6e7's immediate parent, and both arms were built with the same recipe and the same cherry-picked #9323 fix. Culprit: #9315 (8b2cfe6e7).
Restating the finding one last time, because the fix decision turns on it: #9315did not introduce the defect. A class with a generic computed member has always lost this.prototype inside its static methods (#9369, reproduces on green). #9315 routed the well-known-symbol computed members — [Symbol.iterator] in both generator and non-generator form, [Symbol.asyncIterator], [Symbol.toPrimitive], [util.inspect.custom] — onto that path. axios's AxiosHeaders is a class with a non-generator [Symbol.iterator]() and a static accessor() that opens with let z = this.prototype, and its first accessor is getContentType — the exact key the diagnostic reported.
So: reverting #9315 will restore cc --help and will lose #9226's own-keys fix while leaving #9369 live. Fixing #9369 restores cc --help, keeps #9226, and closes the plain-computed-key case too. I would fix #9369.
Independent of either: #9297's content should be re-landed — the #9326 revert was collateral from a wrong attribution and bought nothing.
Culprit: #9315 (8b2cfe6e7) — but the defect it exposes is older, and the right fix is #9369, not a revert
Established with green-parent/broken-at evidence at both levels. Every arm built with the identical recipe and the identical cherry-picked #9323 longjmp fix — that cherry-pick is essential, because every point below #9323 segfaults on cc --help for an unrelated reason, so a step down there without it answers a different question:
The failing call is Object.defineProperty(undefined, "getContentType", …) — receiver is plain undefined (tag 0x7FFC), exactly one occurrence in the run. It is axios's AxiosHeaders.accessor, where let z = this.prototype evaluates to undefined.
#9315 did not introduce the defect. A class with a generic computed member has always lost this.prototype inside its static methods — that is #9369, and it reproduces on green with a plain computed key. What #9315 did was narrow generic_computed_member_key and move the generator-[Symbol.iterator] branch from methods.push to computed_members.push, routing all well-known-symbol computed members onto the already-broken path. AxiosHeaders has a non-generator [Symbol.iterator]() plus a static accessor() opening with this.prototype.
The sharpest symptom, inside that one static method: this === H1 is true and typeof H1.prototype is "object", but typeof this.prototype is "undefined" — same property, two answers, one function.
A revert restores cc --help but loses #9226's own-keys fix and leaves the hole one plain computed key away from the next occurrence. #9369 is the real bug and it is independently reachable.
Minimized fixtures — ~2 min per arm, no cc compile
/root/claude-9341b/fx/{r1,r2,r3}.ts; r3.ts is the one for test-files/ — the only one that separates this regression from the pre-existing case and pins which member kinds take the generic path (Symbol.iterator, Symbol.asyncIterator, Symbol.toPrimitive, generator form, and a plain computed key).
Five pre-existing .prototype-adjacent bugs filed along the way
#9362 (util.inherits with a class constructor), #9364 (SIGSEGV on chained class D extends <param> factory), #9365 (o.prototype = v silently dropped after a computed-key write), #9366 (exported class expression reads .prototype as undefined cross-module), and #9369.
Handover with the method, the eliminations, and ~90 non-differentiating shapes is at /root/claude-9341b/README.md so none of that ground is walked twice.
Activity
proggeramlug commentedon Sep 1, 2026
Revert merged (#9345) —
mainshould be back to a workingcc --help.I merged #9326, so this one is on me, and the miss is worth stating precisely because it wasn't a gap in the analysis of the change itself.
I validated #9326 against the gap fixture it targets (
test_gap_9220_9221_array_proto_paths.ts, byte-identical to the pinned oracle), theperry-runtimesuites, the full lint set, and a clean release build. All green. What I did not do was run thecc --helpparity workload — which #9323 had restored to service three merges earlier, in a PR I had reviewed and merged myself that same evening.That is the actual lesson here: a runtime-semantics change to indexed writes is exactly the kind of thing that primary workload exists to catch, and the fact that the gate had just come back online is what should have made me reach for it. A green gap fixture and a green unit suite were never going to see this — as #9341 notes, the runtime lib suite is green at the failing tip too.
One clarification on scope, since #9326 was #9297 plus an adjustment of mine. My part was moving
array_spec_setback intoindexing.rsso its seven pre-existing raw-handle sites kept their existing ceiling, plus apub(crate)visibility bump and an import fix — a pure relocation with no semantic change, which compiled and tested clean. The behavioural change in the window is #9297's own: honouring a recorded custom array prototype on indexed writes. I mention this only so the fix-forward looks in the right place, not to move the responsibility for merging it.Happy to help with the diagnosis when someone picks this up.
Object.defineProperty called on non-objecton a path that now consults a recorded array prototype suggests the new lookup is answering for a receiver the old one declined, and the defineProperty path is then handed something that isn't an object — but that's a hypothesis, not a finding, and the preserved known-good/known-bad binaries at/root/claude-shapedesc/are a better starting point than my guess.proggeramlug commentedon Sep 1, 2026
A measured finding that matters beyond this issue: the gap suite cannot see this regression.
I built the pre-revert commit (
6bc775d9b9, containing #9326) as a known-bad binary and ran the full 609-test gap suite against it, then the same suite against post-revertmain. The two runs are indistinguishable:Gap snapshot OKGap snapshot OKThe 5 failures are the recorded known set in both cases. I also wrote targeted
definePropertyprobes — prototype index setters,Array.prototypeindex installation, defineProperty on values read back through a custom-prototype array, sparse reads, growth through the custom-prototype path — and every one is byte-identical to node on the known-bad build.So the detection surface for this class is currently:
perry-runtime --libgreen, 609-test gap suite green, hand-written probes green, andcc --helpthe only thing that fails. That is a single point of detection for a path that changes indexed-write semantics.Two consequences worth acting on:
This is why #9326 landed. I validated it against its target gap fixture, the runtime suites, the lint set and a clean build — all green, and now demonstrably all incapable of catching it. The lesson I drew earlier ("I should have run
cc --help") is right but incomplete: no locally available gate would have caught it either.A fixture for this would close a real hole. Whatever minimal shape reproduces the
Object.defineProperty called on non-objectfailure is worth adding totest-files/as a gap test, because the class it represents — prototype-sensitive[[Set]]routing — is exactly the kind of change that gets made often and can currently land green. The preserved binaries at/root/claude-shapedesc/are the obvious starting point; my probes above did not find the shape, so it likely needs something structural the bundle does that a small fixture has to reproduce deliberately.I have held #9339 back for the same reason and said so there — it reintroduces the sole call site of the reverted path, and a green gap run on it would not be evidence of anything.
proggeramlug commentedon Sep 1, 2026
Negative result, posted so nobody repeats it: I tried to minimise this into a fixture against a purpose-built known-bad binary (
6bc775d9b9, containing #9326) and could not reproduce it. Three rounds, all byte-identical to node on the broken build:definePropertywith a prototype index setter, installing an index onArray.prototype,definePropertyon non-objects (null, a number) to check the TypeError path itself.definePropertyapplied to values read back out of an array with a custom prototype, including after 40 indexed writes that grow it, and via a sparse read at index 100.__defProp/__export/__copyPropshelpers over a module registry held in an array, the same with a custom-prototype registry, and the whole thing again inside aPromise.resolve().then(...)chain to match the "Uncaught (in promise)" framing.So the trigger is something more specific than "defineProperty near a prototype-sensitive indexed write". That narrows it usefully: it is unlikely to be the plain helper shapes, and more likely to need something structural the bundle does — the wasm runtime path, a deeper prototype chain, or a receiver that only becomes non-object after a particular sequence.
Whoever picks this up is far better placed than I am: the known-good/known-bad binaries at
/root/claude-shapedesc/plus the bundle allow bisecting the input rather than guessing the shape. A--trace hir --focuson the frame that callsdefinePropertyin the failing binary would probably identify the receiver in one pass.Standing conclusion from the earlier comment is unchanged: the 609-test gap suite returns an identical verdict on the known-bad and known-good builds, so this class currently has exactly one detector, and it is not one that runs locally.
proggeramlug commentedon Sep 1, 2026
Correction: #9326 was NOT the culprit. The revert (#9345) is merged and
cc --helpstill fails identically.Measured on
e539a6359— which contains the revert (14fcf6b942) — freshly built and compiled on perrymaster:Same error, same rc, 5/5. So the revert removed correct work and fixed nothing, and the real culprit is still on main.
Where the attribution went wrong
The elimination rested on "#9323's fix branch was verified cc-green and contained everything through ~#9322." That premise was false, and I had already disproved it before accepting the conclusion: the #9323 work was based on
d6794cf771(#9313), which does not contain #9300, #9315 or #9317 — I checked exactly that withgit merge-base --is-ancestorand recorded "window is bigger than #9326," then let a second, independently-derived elimination override my own contradicting check instead of reconciling the two.The lesson, stated plainly because it is the reusable part: when a second line of evidence contradicts a check you have already run, the disagreement is the finding — reconcile it before acting. Two confident attributions agreeing on a wrong answer is exactly what "independent confirmation" is supposed to prevent, and it did not, because both inherited the same false premise about what the verified-good point contained.
The real window
Last verified-green:
83754818e(#9242, per the independent shapedesc build) /d6794cf771(#9313, the #9323 base).Broken: current main.
Candidates between them (all merged since), several of them mine:
#9312JSON tape gate ·#9314ELF/scrypt ·#9300GC map v5 ·#9315own-keys enumeration ·#9316strided fill kernel ·#9318affine index bound ·#9317tombstone old_carrier ·#9319mysql2 ·#9320/#9321/#9322lint/green fixups ·#9326**(now exonerated by the revert)`.On the error's own evidence,
#9315deserves first look — it changed class-member registration and own-keys enumeration, and the failure is adefinePropertyreceiver arriving as a non-object during help-path module init.#9300(GC map) and#9317(shape publish) are the other two runtime-semantics candidates.A proper bisect over that window is starting now. #9297's content should be re-landed once the true culprit is found — the revert bought nothing and #9220/#9221 remain real bugs.
proggeramlug commentedon Sep 1, 2026
Interim: three eliminations, one corrected artifact, and the throw site narrowed to one of two.
Posting before the attribution is finished because several of these are immediately reusable.
1.
/root/claude-keep/app_Dis not a reproducer of this bugIt is worth stating plainly, because it has been passed around as "the known-bad binary":
app_Dsegfaults. It never prints the TypeError. Anyone differentially debugging #9341 against it is chasing the pre-#9323 longjmp segfault, not this issue.The binaries that actually reproduce #9341 are:
/root/claude-shapedesc/app_basea03be729c(#9336)Object.defineProperty called on non-object/root/claude-arch/app_r7/root/claude-shapedesc/app_base383754818e(#9242)/root/claude-keep/app_ASo the confirmed bracket is green
83754818e→ brokena03be729c, anda03be729cpredates #9331/#9332/#9333/#9340. (measure_sd2.shalso records9943dd95c(#9328) as already failing, which is release-only — consistent with the culprit being at or before #9326's merge point.)2. The failure is deterministic and completely GC-independent
Five kill switches on the reproducing binary, all 5/5 identical failures:
PERRY_GC_MOVING_LOOP_POLLS=0PERRY_GC_HEAP_LIMIT=4096PERRY_LAZY_STACK_MAPS=0PERRY_STACKMAP_WALKER=unwindPERRY_OBJECT_TOMBSTONES=0PERRY_LAZY_STACK_MAPS=0+PERRY_GC_MOVING_LOOP_POLLS=0Turning the moving collector off and raising the heap limit does not change the failure by one byte. This is not a GC or rooting bug. That eliminates, as mechanisms:
PERRY_LAZY_STACK_MAPS=0restores the v4-style eager path and the failure is unchanged;old_carrier) — its entire subject is a receiver's keys array under an evacuating minor GC, and there is no evacuating GC dependence here;a03be729cat all, and the kill switch is inert.It also says the receiver is not a corrupted object. It is a value that was never object-like on this path.
3. There are exactly two throw sites — and it is the second one
Both emit the identical byte string, so the message alone cannot tell them apart. I found a genuine 4-line reproducer for the
util_inheritsone:But it fails identically on the green
83754818ebuild. It is a pre-existing gap, not this regression — filed separately below. Since83754818ecompilescc --helpbyte-identical to node, the bundle cannot be reaching that site. The cc throw isdefine_property.rs:429: a receiver that is neither a registered class-ref, nor object-like, nor a small handle.That matters for where to look. The guard is
and
class_ref_idreturnsSomeonly ifis_class_id_registered(class_id). A class-ref whose id is not inREGISTERED_CLASS_IDSand has no vtable entry is an INT32-tagged non-pointer, so it failsvalue_is_object_likeand throws precisely this message. That is consistent with the earlier session's gdb note that the receiver "resolves via helper to a class-ref".4. What I could not reproduce in isolation (35 shapes, all byte-identical on the broken compiler)
So that nobody re-runs these: on the broken
a03be729ccompiler, all of the following are byte-identical to node —definePropertyon empty classes, static-field-only, getter-only, method-only, class expressions (named and anonymous), subclasses, classes returned from a factory, class prototypes, symbol-keyed classes, static-method-only;getOwnPropertyNameson constructors and prototypes;typeofon class refs; and the whole of #9315's changed computed-symbol lowering surface ([Symbol.iterator]generator and non-generator,[Symbol.asyncIterator],[Symbol.toPrimitive],[util.inspect.custom], static[Symbol.hasInstance],[Symbol.toStringTag]getter and method, and a mixed class where a computed member precedes static/accessor/method members). Also byte-identical: the realutil.inheritsshapes the bundle actually uses — delayed-stream, combined-stream, EventEmitter subclassing,zlib.Inflateas superCtor, andCtor.prototype = {…}wiring.A diagnostic runtime build (receiver bits, tag, class id,
is_class_id_registered, and the property key at the throw) is building now; onecccompile with it should name the failing receiver and key directly.Unasked finding, worth its own issue:
util.inherits()with an ES class constructor throwsset_super_propertyhandles a closure ctor and an object ctor, and throws for everything else. A class constructor reaches it as an INT32-tagged class-ref, which is neither — soutil.inherits(SomeClass, X)throwsObject.defineProperty called on non-objectwhere Node setsSomeClass.super_. Reproducer is the 4 lines above; it fails on83754818eas well as on tip, so it is long-standing and unrelated to this regression. Note the asymmetry:util.inherits(function(){}, SomeClass)is fine — only a class in the first position fails. The fix is presumably to route a class-ref ctor through the class static-property store the waydefine_property.rsalready does viaclass_ref_id. Happy to file it with the fixture if nobody else has.proggeramlug commentedon Sep 1, 2026
Still broken on tip. #9340 did not fix it, and the revert of #9326 did not fix it.
Fresh build at
0f95fbc9f(current main — contains #9340 "preserve array prototypes across growth", #9351, #9337, the #9345 revert, and everything else):State of the attribution
83754818e(fix(release): clear field-shadow and WebAssembly parity blockers #9242) — independently confirmed byte-identical to node.0f95fbc9f(tip), and every point tested in between.Remaining candidates: #9312, #9314, #9300 (GC map v5), #9315 (class own-keys enumeration), #9316, #9318, #9317 (tombstone shape publish), #9319, #9320/#9321/#9322, #9337, #9340, #9349–#9358.
A bisect over that range is running, along with fixture probes against paired green/broken builds — a minimized fixture is the higher-value deliverable and will be posted here as soon as it exists, with or without the commit attribution.
What this costs, stated plainly
cc --helpis the project's primary real-application parity workload. It has now been broken onmainfor the better part of a day, which means every cc measurement any session takes against main is void, and any branch measured on top of it appears to be the cause. This is the third such episode this week (#9169's window, #9305, this one) and the direct argument for the pre-merge cc parity gate proposed in #9346 — every one of the three shipped with its author's fixtures and unit suites green, and only the bundle catches the failing shape.proggeramlug commentedon Sep 1, 2026
Status + one correction to my own earlier post, because it contradicts something I wrote above.
Reconciliation first
My previous comment listed
emitter-fire,zlib-inherits,protoobj-inheritsand ~35 other shapes as "byte-identical to node on the broken compiler". That is exactly what they are — identical, therefore useless as bisect fixtures. They do not differentiate green from broken; they differentiate nothing. I am stating that plainly because a list of green probes reads like progress and is not.The two shapes I did find that diverge from node both fail on the green build too:
83754818ea03be729cutil.inherits(SomeClass, X)→ TypeErrorclass D extends <param>chained 2 levels → SIGSEGVSo: I do not yet have a fixture that separates green from broken. That is the thing still missing, and I am not going to present either of the above as if it were one.
Confirmed: still broken on tip
Independently confirmed by the parallel run on
0f95fbc9f—CC_RC=0, thenPARITY: 0/5. #9340 did not fix it. The bracket is83754818egreen → tip broken, still live.The window is 52 commits, not 15
Worth correcting, because the candidate list in circulation (#9312, #9314, #9300, #9315, #9316, #9318, #9317, #9319, #9320-#9322, #9326) is only its tail.
git rev-list --count 83754818e..a03be729c= 52. The head of the window contains, among others:Several of these are much closer to "a receiver arrives as a non-object" than anything in the tail. Anyone reasoning about this window should be using the full 52.
A narrowing that is evidence rather than a hypothesis
The failing throw is
define_property.rs:429(established above: the only other site isutil_inherits.rs, and that one fails on green, which compilescc --helpbyte-identical). The guard that reaches it is exactly:value_is_object_like=typed_array_addr_from_value∨is_valid_obj_ptr∨is_closure_ptr;class_ref_id= tag0x7FFE∧is_class_id_registered.Asking git which of the 52 commits touch any file implementing those predicates gives exactly two:
descriptor_helpers.rs,addr_class.rs,class_ref_values.rs,define_property.rsandclass_meta.rsare untouched across the whole window. So if the receiver is a closure that stopped being recognised, #9272 owns it; if it is a class ref whose id stopped being registered, the registration call sites (codegen/HIR) own it rather than the predicate. That does not name the culprit, but it is a real constraint rather than a prior. (I read #9272'sis_closure_ptrchange closely — it is a pure reordering of a conjunction, and I could not fault it on inspection. Inspection is not measurement.)What the receiver can actually be
Instrumenting
Object.definePropertyon the realnode cli_2.1.112.js --helprun (patching the builtin and filtering by stack to calls originating in the bundle) gives 5,177 distinct call shapes, of which the dominant ones are zod v4's$constructor:$ZodType.zisObject.defineProperty(inst, "_zod", …)whereinst = params?.Parent ? new Definition() : this— i.e. the receiver isthis, which lines up with the earlier session's gdb note that the receiver "resolves via helper to a class-ref". That is the single most-executeddefinePropertyreceiver on the help path, so it is where I would expect the failure to sit.I could not reproduce it in isolation: faithful reductions of
$constructor(with and withoutParent, chained 3 deep, 200 evaluations,Symbol.hasInstance, per-evaluation class identity, 500 repeated class evaluations) are node-identical on the broken compiler, and the real zod 4.4.3 (spike/tests/02-zod.tsagainst a realnode_modules) compiles and runs byte-identically on green and broken. So whatever it is needs more of the bundle's context than a reduction reproduces — which is why I stopped reducing and went to instrumentation.In flight
A diagnostic runtime built at
a03be729cprints, at thedefine_property.rs:429throw, the receiver's raw bits, NaN-box tag, class id,is_class_id_registered, pointer/undefined/null predicates and the property key — with theutil_inheritssite given a distinct marker so the two can never be confused again. Validated first on fixtures that hit each site deliberately (a diagnostic that cannot fire teaches nothing). Thecccompile with it is running now; it should name the receiver and the key directly.Method note for whoever picks this up next
/root/claude-keep/app_Dsegfaults rather than reproducing this bug (see my earlier comment) — and separately, twocc-scale compiles were running concurrently on perrymaster earlier today with available memory at 0 GB. Acccompile peaks around 29 GB, and an OOM kill leaves a plausible-looking binary that onlyrc=137reveals. Worth gating onfreebefore starting one.proggeramlug commentedon Sep 1, 2026
Root cause identified. The failing call is
Object.defineProperty(undefined, "getContentType", …)— a class's.prototyperead is returningundefined.The measurement
A diagnostic runtime built at
a03be729c, instrumenting thedefine_property.rs:429throw (and giving theutil_inherits.rssite a distinct marker so the two can never be confused). Onecccompile, one--helprun:Exactly one such throw in the whole run. The receiver is
undefined— tag0x7FFC,is_undef=true. It is not a class ref, not a closure, not a handle, not a corrupted pointer. It is a value that should have been an object and isundefined.That kills three standing theories outright, including one of my own: it is not an unregistered class ref (
cid=0,registered=falsebecause there is no class id at all), not a closure the predicate stopped recognising, and not theutil_inheritssite.Where it is in the bundle
The key names it.
getContentTypeis generated by axios'sAxiosHeaders.accessor, which incli_2.1.112.js(line 68) is:xp5(obj, header)is axios'sbuildAccessors, which doesObject.defineProperty(obj, "get"+Name, …)forget/set/has.Content-Typeis the first entry andgetis the first method, sogetContentTypeis the first accessor built — i.e. this fails on the very first call, which is why there is exactly one diag line.So the receiver
xp5gets isz, andz = this.prototypeevaluated toundefinedinside a static class method wherethisis the class.Restating the bug in one line: reading
.prototypeoff a class returnsundefinedin the compiled bundle. Everything downstream — the TypeError, the "in promise" framing, the 0-byte--help— follows from that.Two corroborating facts
--versionworks on every broken build (rc=0, 21 bytes, byte-identical to node) while--helpfails. The failure is on the command/option-tree construction path that--versionshort-circuits past.PERRY_FOR_IN_DIAG=1the broken binary emits only 4 for-in checks before dying, so this is early on that path, not deep in help rendering.What does not reproduce it (so nobody repeats this)
I reduced the axios shape faithfully and it is byte-identical to node on the broken compiler, and identical between the green and broken builds:
this.prototypeinside a static method — fine;Object.defineProperty(this.prototype, …)inside a static method — fine;AxiosHeaders.accessorshape: symbol-keyed static writethis[SYM]=this[SYM]={accessors:{}},let z=this.prototypecaptured by a nested named function,Array.isArray(q)?q.forEach(Y):Y(q),thiscomma-sequence return,["get","set","has"].forEachinside the builder, over the real six-header list — fine, accessors built and callable;spike/tests/02-zod.tsagainst actualnode_modules) compiles and runs byte-identically on green and broken.One genuine divergence turned up on the way —
C.accessor = function(){ return this.prototype }returnsundefinedin perry and the prototype object in node — but it fails on the green build too, so it is pre-existing and not this regression. (Filed as part of the sweep below.)The reduction gap is itself informative: the shape is correct but the context is not. A property read that is correct cold and wrong warm is the signature of an inline cache, which is where I would look first.
Which commits can do this
Asking git which of the 52 window commits touch the class-
.prototyperead path (object/field_get_set/,object/class_registry/,object/prototype_chain.rs) gives exactly four:this.prototypeis a constant-key property read, and #9302 changes how constant-key ICs are primed — a warm-only wrong answer is exactly what that can produce, and it is exactly what the reduction gap points at. #9313 exists specifically to "keep main green after the #9302 batch", which says that batch had fallout. #9291's filter is release-only in effect (its audit is#[cfg(debug_assertions)]), which would explain invisibility to every debug-run suite.I am not claiming any of these yet — this list is what the failing path can reach, not evidence.
In flight
A build at
d6794cf77(#9313) with #9323's longjmp fix cherry-picked (it applies cleanly — #9323's own base isd6794cf77) is compiling now. The cherry-pick matters: every point below #9323 segfaults oncc --helpfor the unrelated reason #9323 fixed, so without it a bisect step below that line answers a different question.That point splits the four candidates cleanly:
Either answer is worth having; the second especially, because that premise is the one that produced both previous wrong attributions.
Sweep findings filed separately
util.inherits(SomeClass, …)throws the identical message (set_super_propertyhas no class-ref arm). Pre-existing; matters here because the two throw sites emit the same bytes.class D extends <param>, chained two levels, then instantiated (zod v4 $constructor shape) #9364 — SIGSEGV on a factory returningclass D extends <param>chained two levels then instantiated (the zod v4$constructoridiom). Pre-existing on green and broken.proggeramlug commentedon Sep 1, 2026
Two more filings from the reduction sweep, both pre-existing (identical on green
83754818eand brokena03be729c) but both in the exact machinery this regression lands on:prototypeis silently dropped when written through a function parameter (and after a dynamic-key write) #9365 — a statically-namedo.prototype = vstore is silently dropped after a single computed-key write on the receiver. Three lines. Scoped precisely: of 14 stores (7 names x static/computed), onlyo.prototype = vin its static-name form is discarded;o["prototype"] = vand every other name survive..prototypeasundefinedfrom an importing module (class declarations are fine) #9366 — an exported class expression reads.prototypeasundefinedfrom an importing module, while a class declaration is fine. Two files. This is the shape of the failing site: axios'skU6 = class { ... static accessor(q){ let z = this.prototype; ... } }is a class expression.Together with #9362 and #9364 that is four distinct ways
.prototypehandling answersundefinedor crashes on green, all silent. That is the reason this bug cost three wrong attributions: the failure mode is a silentundefined, so it surfaces hundreds of instructions later as adefinePropertytype error with no connection to its cause. A store that cannot be honoured should not be discarded silently — making #9365 loud would likely have turned #9341 into a one-hour bug.To be explicit about what is still missing: none of these four is the regression, and I do not yet have a fixture that separates green from broken. Roughly 90 shapes tried (full axios and zod
$constructorreductions, real zod 4.4.3, esbuild lazy-init class expressions, per-evaluation class identity, forced codegen-unit splitting at 2/8/32 units) are either identical across the two builds or diverge from node on both. The bisect is the path; a build atd6794cf77(#9313) + #9323's longjmp fix is in itscccompile now.proggeramlug commentedon Sep 1, 2026
Bisect step 1:
d6794cf77(#9313) + #9323's longjmp fix is GREEN — 3/3 byte-identical to node.Two things follow.
1. The premise everyone has been leaning on is now actually verified. "#9323's branch was cc-green on base
d6794cf77" has been load-bearing since the first attribution and had never been independently rebuilt — it is the claim whose containment half was false and which produced the #9326 misattribution. Its greenness half holds: freshly built and compiled here, that content is byte-identical to node.The cherry-pick matters and is worth stating: every point below #9323 segfaults on
cc --helpfor the unrelated reason #9323 fixed, so a bisect step down there without it answers a different question. #9323's own base isd6794cf77, so it applies cleanly.2. The window is now
#9312 … #9336, i.e. the commits betweend6794cf77and the confirmed-brokena03be729c:Against the evidence already in hand, #9300 and #9317 are out as mechanisms (the failure is completely GC-independent — five kill switches, all identical), and #9315 is the only commit in this list that touches the class-
.prototyperead path the diagnostic implicates. So the coordinator's original prior on #9315 now has evidence under it rather than intuition — but that is still a prior, and I am not going to call it before the build says so.Step 2 is running:
015ec5fe1(#9300), which is #9315's immediate parent.Either way the next step after it is decisive. Infrastructure, fixtures, eliminations and the decision tree are written up in
/root/claude-9341b/README.mdandNEXT_STEPS.txton perrymaster so this is resumable by anyone.proggeramlug commentedon Sep 1, 2026
The minimized fixture — green passes, broken throws the exact #9341 error
Same message, same site, no bundle required. ~2 minutes per arm instead of a 25-minute
cccompile.The control that makes it a bisect fixture
Delete one line — the computed
[Symbol.iterator]()member — and the identical class works on the broken build:So the trigger is: a non-generator computed
[Symbol.iterator]()member in a class, followed by a static method that readsthis.prototypeand hands it toObject.defineProperty. With the computed member,this.prototypeinside the static method becomes a non-object; without it, it is the prototype.It also reproduces with the class as an expression assigned to an outer variable (
H3in my fixture), which is the form the bundle uses.Why that is exactly the bundle
axios's
AxiosHeadersis that class, member for member:and
xp5=buildAccessors, whose firstdefinePropertyisgetContentType— the exact key the diagnostic reported.Attribution so far
83754818e(#9242)015ec5fe1(#9300) — immediate parent of #9315 — + #9323's longjmp fixa03be729c(#9336)And at the
cc --helplevel,d6794cf77(#9313) + #9323's fix is GREEN 3/3 byte-identical.8b2cfe6e7(#9315) is building now. If it fails the fixture, the attribution is complete: green at its parent015ec5fe1, broken at it.#9315 is the prior mechanism too, and this fixture is precisely the surface it changed. #9315 narrowed
generic_computed_member_keyso that only three shapes keep the special lowering — a generator[Symbol.iterator], a static[Symbol.hasInstance]method, and a[Symbol.toStringTag]getter:Before that change,
is_symbol_iterator_key(&computed.expr)matched any[Symbol.iterator]member. So a non-generator[Symbol.iterator]()— axios's exact form — moved from the special path into the generic computed-member path, and the generic path setsseen_generic_computed_member, which gates how the members after it register (can_source_order_register). The staticaccessormethod that follows is one of those members.That is a hypothesis about the why; the build will settle the whether.
Please add this to
test-files/This is the fixture that has been missing all week — the class this bug belongs to (a computed class member changing how later members register) can currently land green through every local gate:
perry-runtime --lib, the 609-test gap suite, hand-writtendefinePropertyprobes and the real libraries all pass on the broken build. I verified separately that real zod 4.4.3 and ~90 hand-built shapes are byte-identical across green and broken; only this one separates them.Worth pinning both arms — the failing class and the
H2control — since a fixture that only asserts the failing case cannot show that the computed member is the trigger.proggeramlug commentedon Sep 1, 2026
Culprit: #9315 (
8b2cfe6e7) — green at its parent, broken at itBoth arms built with the identical recipe and the identical cherry-picked #9323 fix, and both tested with the same fixture. The failure at
8b2cfe6e7is the exact #9341 message:015ec5fe1is8b2cfe6e7's immediate parent, so the window is closed to one commit.Full evidence chain
83754818e(#9242)cc --help83754818e(#9242)d6794cf77(#9313) + #9323cc --help015ec5fe1(#9300) + #93238b2cfe6e7(#9315) + #9323Object.defineProperty called on non-objecta03be729c(#9336)cc --help+ fixture0f95fbc9f(tip)cc --helpA
cc --helpbuild at015ec5fe1is finishing now as independent confirmation at the bundle level; the fixture chain above already closes the attribution.Mechanism
#9315 narrowed
generic_computed_member_keyincrates/perry-hir/src/lower_decl/class_decl.rs. Before, any well-known-symbol computed key kept the special lowering:After, only three shapes do:
Note the
is_generatorconjunct. A non-generator[Symbol.iterator]()— which is exactly axios's form — therefore moved from the special path into the generic computed member path. The generic path setsseen_generic_computed_member, which gatescan_source_order_registerfor every member declared after it. In the failing class the member after it is thestatic accessormethod, and inside that methodthis.prototypecomes back a non-object.The control in the fixture isolates it: delete only the
[Symbol.iterator]()line and the same class works on the broken build.That this reached
cc --helpand nothing else is consistent with the shape being uncommon — a computed well-known-symbol member followed by a static method that reflects onthis.prototype. axios'sAxiosHeadersis that class, andgetContentTypeis the first accessor it builds, which is the key the diagnostic reported.Recommendation
The narrowing is not wrong in intent — it is what makes the #9226 own-keys enumeration correct — so I would not reflexively revert #9315 and lose that. The defect is that the generic path's
seen_generic_computed_member/can_source_order_registergating changes how subsequent members register in a way that leaves the class's prototype unreachable from a later static method. The narrow fix is in that gating, not in theis_generatorpredicate.If a revert is wanted for release safety in the meantime, revert #9315 — but please re-land it with the gating fixed, since it is the only thing holding the #9226 fixture at 0 diverging lines.
Also worth acting on, from the same investigation: #9297's content should still be re-landed (the #9326 revert bought nothing — #9314/#9326 were never the cause), and #9365 should be fixed on its own merits, because a
.prototypestore that silently does nothing is what turned a one-line lowering change into a week of misattribution.Reproduction
Fixture and both arms:
/root/claude-9341b/fx/r1.tson perrymaster;README.mdthere has the full method, the eliminations, and the four adjacent pre-existing bugs (#9362, #9364, #9365, #9366). Each arm is a ~6 minute compiler build plus a ~2 minute fixture compile — nocccompile needed to test this any more.proggeramlug commentedon Sep 1, 2026
Sharper root cause — and a smaller fixture that shows the contradiction directly
Inside one function, on one object, the same property read gives two different answers depending on whether it is reached through
thisor through the class's own name.this === H1istrue,typeof thisis"function",this.nameis"H1"— the receiver is correct by every other measure. Onlythis.prototypeisundefined.Remove the
[Symbol.iterator]()line andthis.prototypeis an object again. Verified pre-existing-free:r2is byte-identical to node on83754818e.Why that produces #9341
The class-ref
.prototyperead lives inget_field_by_name.rs:Reached with a recognised class ref it cannot return
undefined— there is a fallback. Sothisin the static method is not being recognised as a class ref on this path, the branch is skipped, and the read falls through to a generic lookup that finds nothing. ReadingH1.prototypethrough the named binding still takes the class-ref path, which is why the two disagree.What #9315 changes is upstream of that. Its narrowing of
generic_computed_member_keymoves a non-generator[Symbol.iterator]()into the generic computed-member path, which setsseen_generic_computed_member; from that point on, every subsequent member also getslower_noncomputed_class_member_registrationpushed intocomputed_members(class_decl.rs, the threeif seen_generic_computed_member && can_source_order_registersites). Thestatic accessor/static probemethod is one of those members, and the extra registration is what leaves itsthisin a form the.prototypebranch does not accept.Suggested direction for the fix
Not the
is_generatorpredicate — that narrowing is what makes #9226's own-keys enumeration correct, and reverting it loses that. The defect is that the extra source-order registration applied to members after a generic computed member changes the receiver form those members see. Two candidates, in order of preference:[[OwnPropertyKeys]]) without altering how the member is emitted or howthisis bound in it — that is all getOwnPropertyNames(C.prototype) omits accessors and leaks a symbol key as the literal string "@@iterator" #9226 needed..prototypebranch accept the receiver form thatthistakes in these methods, so the two reads cannot disagree.Whichever is chosen, the pair
H1(with the computed member) andH2(without) belongs intest-files/— asserting only the failing case would not show that the computed member is the trigger, and this whole bug is a case of a change to member registration silently altering member dispatch.Fixtures on perrymaster:
/root/claude-9341b/fx/r1.ts(axios-shaped, throws the #9341 message) and/root/claude-9341b/fx/r2.ts(the three-waythisvs named-binding probe above). Both ~2 minutes per arm.proggeramlug commentedon Sep 1, 2026
Correction to my own mechanism, and the real one
My previous comment said the trigger was the ordering gate —
seen_generic_computed_membercausing members declared after the computed one to re-register. That is wrong, and my next measurement contradicted it, so here is the reconciliation before anything else.A static method declared before the computed member fails identically to one declared after:
So it is not about order. It is: any class carrying a generic computed member loses
this.prototypeinside every static method of that class.What #9315 actually did
Sweeping the computed-key kinds across both builds:
83754818e8b2cfe6e7(#9315)[Symbol.iterator]() {}(non-generator)*[Symbol.iterator]() {}(generator)[Symbol.asyncIterator]() {}[Symbol.toPrimitive]() {}[K]() {}whereK = "dyn"+"Key"(plain computed)Read the last row carefully. A plain computed-key member already broke
this.prototypeon the green build. The defect is not new. What #9315 did was route the well-known-symbol computed members onto that same already-broken path:generic_computed_member_keywas narrowed, so non-generator[Symbol.iterator],[Symbol.asyncIterator],[Symbol.toPrimitive],[util.inspect.custom]and friends stopped getting special lowering and became generic computed members;[Symbol.iterator]branch changed frommethods.push(wrapper)tocomputed_members.push(ClassComputedMember { … }), which puts that one on the generic path too — which is why the generator row broke as well even though it keptneeds_special_lowering.So the accurate statement of #9341 is:
This is worth stating plainly because it changes the fix: reverting #9315 restores
cc --helpbut leaves the underlying hole, one plain computed key away from the next occurrence — and it costs the #9226 own-keys fix. Fixing the generic computed-member path's effect on static-methodthisfixes both, and closes the pre-existing plain-computed-key case at the same time.That pre-existing half is filed separately as #9367 so it can be fixed on its own merits whichever way the revert decision goes.
Fixtures
All three are ~2 minutes per arm, no
cccompile:/root/claude-9341b/fx/r1.ts— axios-shaped; throws the literal cc --help parity gate down on main: defineProperty-on-non-object at startup — window pins #9326 (indexed writes with a recorded custom array prototype) #9341 message; green passes./root/claude-9341b/fx/r2.ts— the contradiction in one line:this === H1is true,typeof H1.prototypeisobject,typeof this.prototypeisundefined./root/claude-9341b/fx/r3.ts— the table above; separates the regression from the pre-existing case, and is the one worth committing since it pins which member kinds may take the generic path.I would put
r3intest-files/over the other two: it is the only one that would have caught #9315 and would catch a future change that widens the generic path again.proggeramlug commentedon Sep 1, 2026
Correction to the comment above: the pre-existing half is #9369, not #9367 — I wrote the number before the issue was created and guessed wrong. Everything else in that comment stands.
#9369:
class E { [K]() {} static probe() { return typeof this.prototype; } }with a computed keyK—this.prototypeisundefinedinside every static method whileE.prototypeis an object, on green as well as broken.proggeramlug commentedon Sep 1, 2026
Bundle-level confirmation is in.
cc --helpis GREEN at #9315's immediate parent.Combined with the fixture result, the attribution is closed at both levels:
cc --help83754818e(#9242)d6794cf77(#9313) + #9323015ec5fe1(#9300) + #9323 — parent of #93158b2cfe6e7(#9315) + #9323a03be729c(#9336)0f95fbc9f(tip)015ec5fe1is8b2cfe6e7's immediate parent, and both arms were built with the same recipe and the same cherry-picked #9323 fix. Culprit: #9315 (8b2cfe6e7).Restating the finding one last time, because the fix decision turns on it: #9315 did not introduce the defect. A class with a generic computed member has always lost
this.prototypeinside its static methods (#9369, reproduces on green). #9315 routed the well-known-symbol computed members —[Symbol.iterator]in both generator and non-generator form,[Symbol.asyncIterator],[Symbol.toPrimitive],[util.inspect.custom]— onto that path. axios'sAxiosHeadersis a class with a non-generator[Symbol.iterator]()and astatic accessor()that opens withlet z = this.prototype, and its first accessor isgetContentType— the exact key the diagnostic reported.So: reverting #9315 will restore
cc --helpand will lose #9226's own-keys fix while leaving #9369 live. Fixing #9369 restorescc --help, keeps #9226, and closes the plain-computed-key case too. I would fix #9369.Independent of either: #9297's content should be re-landed — the #9326 revert was collateral from a wrong attribution and bought nothing.
proggeramlug commentedon Sep 1, 2026
Culprit: #9315 (
8b2cfe6e7) — but the defect it exposes is older, and the right fix is #9369, not a revertEstablished with green-parent/broken-at evidence at both levels. Every arm built with the identical recipe and the identical cherry-picked #9323 longjmp fix — that cherry-pick is essential, because every point below #9323 segfaults on
cc --helpfor an unrelated reason, so a step down there without it answers a different question:cc --help83754818e(#9242)d6794cf77(#9313) + #9323015ec5fe1(#9300) — #9315's immediate parent8b2cfe6e7(#9315)a03be729c,0f95fbc9f(tip)Root cause
The failing call is
Object.defineProperty(undefined, "getContentType", …)— receiver is plainundefined(tag0x7FFC), exactly one occurrence in the run. It is axios'sAxiosHeaders.accessor, wherelet z = this.prototypeevaluates toundefined.#9315 did not introduce the defect. A class with a generic computed member has always lost
this.prototypeinside its static methods — that is #9369, and it reproduces on green with a plain computed key. What #9315 did was narrowgeneric_computed_member_keyand move the generator-[Symbol.iterator]branch frommethods.pushtocomputed_members.push, routing all well-known-symbol computed members onto the already-broken path.AxiosHeadershas a non-generator[Symbol.iterator]()plus astatic accessor()opening withthis.prototype.The sharpest symptom, inside that one static method:
this === H1istrueandtypeof H1.prototypeis"object", buttypeof this.prototypeis"undefined"— same property, two answers, one function.Recommendation: fix #9369, do not revert #9315
A revert restores
cc --helpbut loses #9226's own-keys fix and leaves the hole one plain computed key away from the next occurrence. #9369 is the real bug and it is independently reachable.Minimized fixtures — ~2 min per arm, no cc compile
/root/claude-9341b/fx/{r1,r2,r3}.ts;r3.tsis the one fortest-files/— the only one that separates this regression from the pre-existing case and pins which member kinds take the generic path (Symbol.iterator,Symbol.asyncIterator,Symbol.toPrimitive, generator form, and a plain computed key).Corrections to things in circulation
/root/claude-keep/app_Dis NOT a reproducer — it segfaults (rc=139), which is the pre-fix(runtime): no Rust frame is ever a longjmp target — cc --help segfault fixed, parity gate back online (#9305) #9323 bug. Anyone differentially debugging against it has been chasing the wrong failure. My earlier pointer to it was wrong; disregard it.Five pre-existing
.prototype-adjacent bugs filed along the way#9362 (
util.inheritswith a class constructor), #9364 (SIGSEGV on chainedclass D extends <param>factory), #9365 (o.prototype = vsilently dropped after a computed-key write), #9366 (exported class expression reads.prototypeasundefinedcross-module), and #9369.Handover with the method, the eliminations, and ~90 non-differentiating shapes is at
/root/claude-9341b/README.mdso none of that ground is walked twice.fix(codegen): PerryTS#9369 — a static body's `this` is the class, not…
fix(codegen): #9369 — a static body's `this` is the class, not an ins…
proggeramlug commentedon Sep 2, 2026
Closing: the culprit was #9369, fixed in #9386. cc --help gate reported PASS, byte-identical to node, 3/3.