Skip to content

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

Description

@proggeramlug

Symptom

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:

build SHA cc --help
app_r5 (Aug 31) 83754818e (#9242) PASS — byte-identical to node, rc=0
app_r6 (tonight) 9943dd95c (#9328) FAIL — TypeError above, rc=1
fresh tip build (tonight) a03be729c4 (#9336) FAIL — same TypeError

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).

Repro

ssh perrymaster
export HOME=/tmp/cc_home
/root/claude-shapedesc/app_base --help    # tip a03be729c4 — fails
/root/claude-arch/app_r6 --help           # 9943dd95c — fails
/root/claude-arch/app_r5 --help           # 83754818e — passes, byte-identical

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.

Activity

  1. proggeramlug commented on Sep 1, 2026

    @proggeramlug
    ContributorAuthor

    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.

  2. proggeramlug commented on Sep 1, 2026

    @proggeramlug
    ContributorAuthor

    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:

    known-bad (#9326 present) post-revert main
    Parity pass 604 604
    Parity fail 5 5
    failing tests same 5 same 5
    verdict Gap snapshot OK Gap snapshot OK
    exit 0 0

    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.

  3. proggeramlug commented on Sep 1, 2026

    @proggeramlug
    ContributorAuthor

    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:

    1. 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.
    2. 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.
    3. 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.

  4. proggeramlug commented on Sep 1, 2026

    @proggeramlug
    ContributorAuthor

    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 · #9300 GC map v5 · #9315 own-keys enumeration · #9316 strided fill kernel · #9318 affine index bound · #9317 tombstone 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.

  5. proggeramlug commented on Sep 1, 2026

    @proggeramlug
    ContributorAuthor

    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_D is not a reproducer of this bug

    It is worth stating plainly, because it has been passed around as "the known-bad binary":

    $ HOME=/tmp/cc_home /root/claude-keep/app_D --help
    Segmentation fault (core dumped)   rc=139
    

    app_D segfaults. 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:

    binary SHA result
    /root/claude-shapedesc/app_base a03be729c (#9336) rc=1, Object.defineProperty called on non-object
    /root/claude-arch/app_r7 (later tip) rc=1, same
    /root/claude-shapedesc/app_base3 83754818e (#9242) rc=0, 9175 bytes, byte-identical to node
    /root/claude-keep/app_A — rc=0, 9175 bytes

    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:

    configuration result
    default TypeError
    PERRY_GC_MOVING_LOOP_POLLS=0 TypeError
    PERRY_GC_HEAP_LIMIT=4096 TypeError
    PERRY_LAZY_STACK_MAPS=0 TypeError
    PERRY_STACKMAP_WALKER=unwind TypeError
    PERRY_OBJECT_TOMBSTONES=0 TypeError
    PERRY_LAZY_STACK_MAPS=0 + PERRY_GC_MOVING_LOOP_POLLS=0 TypeError

    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:

    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

    crates/perry-runtime/src/object/object_ops/define_property.rs:429
    crates/perry-runtime/src/util_inherits.rs:134   (set_super_property)
    

    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:

    const util = require("util");
    class Base2 {}
    class Derived2 {}
    util.inherits(Derived2, Base2);
    console.log(typeof Derived2.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.

    That matters for where to look. The guard is

    let target_is_class_ref = super::super::class_ref_id(obj_value).is_some();
    if !target_is_class_ref && !value_is_object_like(obj_value) { … throw … }

    and class_ref_id returns Some only 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.

  6. proggeramlug commented on Sep 1, 2026

    @proggeramlug
    ContributorAuthor

    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
    

    State of the attribution

    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 --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.

  7. proggeramlug commented on Sep 1, 2026

    @proggeramlug
    ContributorAuthor

    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:

    fixture green 83754818e broken a03be729c verdict
    util.inherits(SomeClass, X) → TypeError fails fails pre-existing → #9362
    factory class D extends <param> chained 2 levels → SIGSEGV fails fails pre-existing → #9364

    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:

    let target_is_class_ref = class_ref_id(obj_value).is_some();
    if !target_is_class_ref && !value_is_object_like(obj_value) { … is_small_handle fallback … throw }

    value_is_object_like = typed_array_addr_from_value ∨ is_valid_obj_ptr ∨ is_closure_ptr; class_ref_id = tag 0x7FFE ∧ is_class_id_registered.

    Asking git which of the 52 commits touch any file implementing those predicates gives exactly two:

    $ git log --oneline 83754818e..a03be729c -- <the predicate files>
    6d08afc29  #9272  closure/dynamic_props.rs      (is_closure_ptr: CLOSURE_MAGIC tag read hoisted above the arena check)
    8b2cfe6e7  #9315  class_registry/registration.rs (additions only; is_class_id_registered itself unchanged)
    

    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.

  8. proggeramlug commented on Sep 1, 2026

    @proggeramlug
    ContributorAuthor

    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:

    static accessor(q){
      let _=(this[lb7]=this[lb7]={accessors:{}}).accessors,
          z=this.prototype;                                   // <-- z is undefined
      function Y(A){let O=VU6(A);if(!_[O])xp5(z,A),_[O]=!0}
      return H1.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.

    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

  9. proggeramlug commented on Sep 1, 2026

    @proggeramlug
    ContributorAuthor

    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.

  10. proggeramlug commented on Sep 1, 2026

    @proggeramlug
    ContributorAuthor

    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.

  11. proggeramlug commented on Sep 1, 2026

    @proggeramlug
    ContributorAuthor

    The minimized fixture — green passes, broken throws the exact #9341 error

    class H1 {
      set(k, v) { this[k] = v; return this; }
      toJSON() { return { a: 1 }; }
      [Symbol.iterator]() { return Object.entries(this.toJSON())[Symbol.iterator](); }
      static accessor(list) {
        const z = this.prototype;
        list.forEach((h) => {
          Object.defineProperty(z, "get" + h, { value: function () { return h; }, configurable: true });
        });
        return this;
      }
    }
    H1.accessor(["ContentType"]);
    console.log(typeof H1.prototype.getContentType);
    node                       : function
    perry 83754818e (#9242)    : function          <- GREEN
    perry a03be729c (#9336)    : TypeError: Object.defineProperty called on non-object   <- BROKEN
    

    Same message, same site, no bundle required. ~2 minutes per arm instead of a 25-minute cc compile.

    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:

    class H2 {                                  // no computed member
      set(k, v) { this[k] = v; return this; }
      toJSON() { return { a: 1 }; }
      static accessor(list) { const z = this.prototype; /* …same… */ }
    }
    H2.accessor(["ContentType"]);               // broken build: "function"  ✔

    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]() { return Object.entries(this.toJSON())[Symbol.iterator](); }   // non-generator, computed
      static accessor(q){ … let z = 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.

    Attribution so far

    point fixture
    83754818e (#9242) passes
    015ec5fe1 (#9300) — immediate parent of #9315 — + #9323's longjmp fix passes
    a03be729c (#9336) fails

    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:

    let needs_special_lowering =
        (well_known == Some("iterator") && method.function.is_generator && !method.is_static)
            || (well_known == Some("hasInstance") && method.is_static && …)
            || (well_known == Some("toStringTag") && !method.is_static && …);

    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.

  12. proggeramlug commented on Sep 1, 2026

    @proggeramlug
    ContributorAuthor

    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.

    Full evidence chain

    point what was measured result
    83754818e (#9242) cc --help GREEN, 9175 bytes byte-identical
    83754818e (#9242) fixture passes
    d6794cf77 (#9313) + #9323 cc --help GREEN 3/3 byte-identical
    015ec5fe1 (#9300) + #9323 fixture passes
    8b2cfe6e7 (#9315) + #9323 fixture FAILS — Object.defineProperty called on non-object
    a03be729c (#9336) cc --help + fixture fails both
    0f95fbc9f (tip) cc --help fails

    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:

    if is_symbol_iterator_key(&computed.expr)
        || is_inspect_custom_key(ctx, &computed.expr)
        || symbol_well_known_key(&computed.expr).is_some()
    { return None; }

    After, only three shapes do:

    let needs_special_lowering =
        (well_known == Some("iterator") && method.function.is_generator && !method.is_static)
            || (well_known == Some("hasInstance") && method.is_static && matches!(method.kind, MethodKind::Method))
            || (well_known == Some("toStringTag") && !method.is_static && matches!(method.kind, MethodKind::Getter));

    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.

  13. proggeramlug commented on Sep 1, 2026

    @proggeramlug
    ContributorAuthor

    Sharper root cause — and a smaller fixture that shows the contradiction directly

    class H1 {
      toJSON() { return { a: 1 }; }
      [Symbol.iterator]() { return Object.entries(this.toJSON())[Symbol.iterator](); }
      static probe() {
        return "this===H1:" + (this === H1) +
               " | typeof this.prototype=" + typeof this.prototype +
               " | typeof H1.prototype="   + typeof H1.prototype;
      }
    }
    console.log(H1.probe());
    node                    : this===H1:true | typeof this.prototype=object    | typeof H1.prototype=object
    perry 83754818e (GREEN) : this===H1:true | typeof this.prototype=object    | typeof H1.prototype=object
    perry 8b2cfe6e7 (#9315) : this===H1:true | typeof this.prototype=undefined | typeof H1.prototype=object
    

    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.

    Why that produces #9341

    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 {
            return class_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:

    1. Make the extra registration purely additive metadata (declaration order for [[OwnPropertyKeys]]) without altering how the member is emitted or how this is bound in it — that is all getOwnPropertyNames(C.prototype) omits accessors and leaks a symbol key as the literal string "@@iterator" #9226 needed.
    2. 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.

  14. proggeramlug commented on Sep 1, 2026

    @proggeramlug
    ContributorAuthor

    Correction to my own mechanism, and the real one

    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.

    What #9315 actually did

    Sweeping the computed-key kinds across both builds:

    class member green 83754818e broken 8b2cfe6e7 (#9315)
    [Symbol.iterator]() {} (non-generator) object undefined
    *[Symbol.iterator]() {} (generator) object undefined
    [Symbol.asyncIterator]() {} object undefined
    [Symbol.toPrimitive]() {} object undefined
    [K]() {} where K = "dyn"+"Key" (plain computed) undefined undefined

    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.

    So the accurate statement of #9341 is:

    #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.

    Fixtures

    All three are ~2 minutes per arm, no cc compile:

    I would put r3 in test-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.

  15. proggeramlug commented on Sep 1, 2026

    @proggeramlug
    ContributorAuthor

    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.

  16. proggeramlug commented on Sep 1, 2026

    @proggeramlug
    ContributorAuthor

    Bundle-level confirmation is in. cc --help is GREEN at #9315's immediate parent.

    p9313  d6794cf77 (#9313) + #9323 fix   CC --help  GREEN (3/3 byte-identical)
    p9300  015ec5fe1 (#9300) + #9323 fix   CC --help  GREEN (3/3 byte-identical)   <- #9315's parent
    

    Combined with the fixture result, the attribution is closed at both levels:

    point cc --help fixture
    83754818e (#9242) GREEN passes
    d6794cf77 (#9313) + #9323 GREEN 3/3 —
    015ec5fe1 (#9300) + #9323 — parent of #9315 GREEN 3/3 passes
    8b2cfe6e7 (#9315) + #9323 — FAILS with the #9341 message
    a03be729c (#9336) BROKEN fails
    0f95fbc9f (tip) 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: #9315 did 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.

  17. proggeramlug commented on Sep 1, 2026

    @proggeramlug
    ContributorAuthor

    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:

    point cc --help fixture
    83754818e (#9242) GREEN, 9,175 B passes
    d6794cf77 (#9313) + #9323 GREEN 3/3 —
    015ec5fe1 (#9300) — #9315's immediate parent GREEN 3/3 passes
    8b2cfe6e7 (#9315) — FAILS with the #9341 message
    a03be729c, 0f95fbc9f (tip) BROKEN fails

    Root cause

    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.

    Recommendation: fix #9369, do not revert #9315

    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).

    Corrections to things in circulation

    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.

  18. added 2 commits that reference this issue on Sep 1, 2026
  19. proggeramlug commented on Sep 2, 2026

    @proggeramlug
    ContributorAuthor

    Closing: the culprit was #9369, fixed in #9386. cc --help gate reported PASS, byte-identical to node, 3/3.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions