Object.freeze(arr); arr[0] = 9 in non-strict code must be a silent no-op (ES §6.2.5.7 / OrdinarySetWithOwnDescriptor — a failed [[Set]] throws only in strict mode). Perry now raises an uncaught TypeError, so ordinary sloppy CJS code crashes where node continues.
Reproduced independently on 757beace0 (current main):
const a1 = [1]; Object.freeze(a1); a1[0] = 9; // node: silent, a1[0]===1
const a2 = [1]; Object.freeze(a2); a2[5] = 9; // node: silent
const a3 = [1]; Object.defineProperty(a3, 0, {writable:false}); a3[0] = 9; // node: silent
const a4 = [1]; Object.preventExtensions(a4); a4[5] = 9; // node: silent
const o1 = {x:1}; Object.freeze(o1); o1.x = 9; // node: silent
node perry (757beace0)
frozen-inbounds silent !! Cannot assign to read only property '0' of object '[object Array]'
frozen-oob silent !! Cannot add property 5, object is not extensible
nonwritable silent !! Cannot assign to read only property '0' of object '[object Array]'
preventext silent !! Cannot add property 5, object is not extensible
obj-frozen silent => silent <-- plain objects are CORRECT
Array element stores specifically. Plain-object stores still behave correctly, which localises this to the array [[Set]] path.
Attribution
Introduced by #9326 (the merge of #9297, "indexed writes honour a custom array prototype"). Verified against a pre-#9326 build at #9300, which matches node on every case above. Note #9326 was reverted (#9345) and then re-landed (#9370), so this is live on main today.
The change is correct about what it set out to fix — an inherited index accessor now runs, and a non-writable inherited index now rejects the write. It just rejects it by throwing unconditionally rather than throwing only in strict mode.
Why the fix's own tests could not see it
This is the third instance this week of the same pattern: its 64-check differential and 205-line gap fixture are all green, because they exercise the write through paths that are strict, and never assert that sloppy mode stays silent. The failure needs (a) a frozen/non-extensible/non-writable array element and (b) non-strict code — and cc's bundle is sloppy-mode CJS with 9 Object.freeze sites on arrays.
Suggested fix
The rejection path needs the strictness flag that the ordinary-object [[Set]] path already honours: throw when strict, return false silently otherwise. The regression test should assert both arms — that sloppy is silent and that strict still throws — since asserting only the throw is what let this through.
Found during a differential stress-test of claude-code under perry.
Object.freeze(arr); arr[0] = 9in non-strict code must be a silent no-op (ES §6.2.5.7 / OrdinarySetWithOwnDescriptor — a failed[[Set]]throws only in strict mode). Perry now raises an uncaughtTypeError, so ordinary sloppy CJS code crashes where node continues.Reproduced independently on
757beace0(current main):Array element stores specifically. Plain-object stores still behave correctly, which localises this to the array
[[Set]]path.Attribution
Introduced by #9326 (the merge of #9297, "indexed writes honour a custom array prototype"). Verified against a pre-#9326 build at #9300, which matches node on every case above. Note #9326 was reverted (#9345) and then re-landed (#9370), so this is live on main today.
The change is correct about what it set out to fix — an inherited index accessor now runs, and a non-writable inherited index now rejects the write. It just rejects it by throwing unconditionally rather than throwing only in strict mode.
Why the fix's own tests could not see it
This is the third instance this week of the same pattern: its 64-check differential and 205-line gap fixture are all green, because they exercise the write through paths that are strict, and never assert that sloppy mode stays silent. The failure needs (a) a frozen/non-extensible/non-writable array element and (b) non-strict code — and cc's bundle is sloppy-mode CJS with 9
Object.freezesites on arrays.Suggested fix
The rejection path needs the strictness flag that the ordinary-object
[[Set]]path already honours: throw when strict, returnfalsesilently otherwise. The regression test should assert both arms — that sloppy is silent and that strict still throws — since asserting only the throw is what let this through.Found during a differential stress-test of claude-code under perry.