[GH-ISSUE #1494] [Bug] Writing through a getter's returned object reports a stale literal #508

Closed
opened 2026-10-15 18:25:42 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @CassieFox23 on GitHub (Oct 10, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/1494

TL;DR: A write through a getter's object is missed. The getter hands back a shared object, code writes to it, and REA still reports the old value with complete coverage. #1471 fixed this for calls, but a getter read is not a call.

Before submitting

  • I searched the open and closed issues for a related report.
  • I removed credentials, tokens, private paths, and sensitive target data.

Area

Evidence, artifacts, or reconstruction

Observed behavior

app.js:

export function result() {
  const shared = { x: 1 };
  const box = { get v() { return shared; } };
  box.v.x = 2;
  return shared.x;
}

Real JS returns 2. rea analyze-javascript-application <dir> --artifact-format directory --json reports result as literal 1, with return_shape_coverage complete.

Four getter shapes fail the same way on main 1f692c83e:

Getter shape Real JS REA
Object getter, box.v.x = 2 2 literal 1
Class getter, new B().v.x = 2 2 literal 1
Static getter, B.v.x = 2 2 literal 1
Getter copied first, const t = box.v; t.x = 2 2 literal 1

On #1471's head, two other getter forms gave unknown: a getter stored through a slot (o.a = box.v), and one made with Object.defineProperty.

Reproduction

  1. Save the file above as app.js in an empty folder.
  2. Run rea analyze-javascript-application <folder> --artifact-format directory --json.
  3. Read static_return_shapes and return_shape_coverage for the result export.

Expected behavior

unknown, like the call forms after #1471. Or the right value, 2.

Impact and scope

The wrong value comes with complete coverage, so an agent has no reason to doubt it. Getters that hand back a cached or shared object are common in real code.

#1471 hooks the call, optional-call and new branch in javascriptSemanticMemberMutations.ts. A plain getter read is a member read, not a call, so it never reaches that hook. I read that in the source but did not test it another way.

Interface

CLI. MCP gave the same answers for the call forms in #1471. I did not run MCP on these four.

Earliest failure stage

Native analysis or reconstruction

Not run

Edit: MCP on the four shapes, the class instance getter, and Node 22 and 26 ran on current main, see the follow-up. Still not run: Windows and macOS.

  • MCP on these four shapes. Ran on all six shapes, on Node 22, 24 and 26.
  • Getters on a class instance stored in a module variable.
  • Node 22 and 26 on main 1f692c83e. On #1471's head they gave the same cells as Node 24. Ran on main cca7bf002 instead.
  • Windows and macOS.

Found while testing #1471 for #1449.

Test setup
  • Box: Nobara 44 (Fedora 44 base), Linux 7.2.9
  • CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads
  • RAM: 32 GB DDR4-3600
  • Node: 24.18.0 on main 1f692c83e; 22.23.1, 24.18.0 and 26.11.1 on #1471's head 8414ee49
  • REA: built from source with npm ci and npm run build

🤖 Generated with Claude Code (Opus 5.5) 🦊

Originally created by @CassieFox23 on GitHub (Oct 10, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/1494 **TL;DR:** A write through a getter's object is missed. The getter hands back a shared object, code writes to it, and REA still reports the old value with `complete` coverage. #1471 fixed this for calls, but a getter read is not a call. ### Before submitting - [x] I searched the open and closed issues for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area Evidence, artifacts, or reconstruction ### Observed behavior `app.js`: ```js export function result() { const shared = { x: 1 }; const box = { get v() { return shared; } }; box.v.x = 2; return shared.x; } ``` Real JS returns 2. `rea analyze-javascript-application <dir> --artifact-format directory --json` reports `result` as `literal 1`, with `return_shape_coverage` `complete`. Four getter shapes fail the same way on main `1f692c83e`: | Getter shape | Real JS | REA | |---|---|---| | Object getter, `box.v.x = 2` | 2 | `literal 1` | | Class getter, `new B().v.x = 2` | 2 | `literal 1` | | Static getter, `B.v.x = 2` | 2 | `literal 1` | | Getter copied first, `const t = box.v; t.x = 2` | 2 | `literal 1` | On #1471's head, two other getter forms gave `unknown`: a getter stored through a slot (`o.a = box.v`), and one made with `Object.defineProperty`. ### Reproduction 1. Save the file above as `app.js` in an empty folder. 2. Run `rea analyze-javascript-application <folder> --artifact-format directory --json`. 3. Read `static_return_shapes` and `return_shape_coverage` for the `result` export. ### Expected behavior `unknown`, like the call forms after #1471. Or the right value, 2. ### Impact and scope The wrong value comes with `complete` coverage, so an agent has no reason to doubt it. Getters that hand back a cached or shared object are common in real code. #1471 hooks the call, optional-call and `new` branch in `javascriptSemanticMemberMutations.ts`. A plain getter read is a member read, not a call, so it never reaches that hook. I read that in the source but did not test it another way. ### Interface CLI. MCP gave the same answers for the call forms in #1471. I did not run MCP on these four. ### Earliest failure stage Native analysis or reconstruction ### Not run Edit: MCP on the four shapes, the class instance getter, and Node 22 and 26 ran on current main, see [the follow-up](https://github.com/morluto/rea/issues/1494#issuecomment-6099256228). Still not run: Windows and macOS. - ~~MCP on these four shapes.~~ Ran on all six shapes, on Node 22, 24 and 26. - ~~Getters on a class instance stored in a module variable.~~ - ~~Node 22 and 26 on main `1f692c83e`. On #1471's head they gave the same cells as Node 24.~~ Ran on main `cca7bf002` instead. - Windows and macOS. Found while testing #1471 for #1449. <details> <summary>Test setup</summary> - Box: Nobara 44 (Fedora 44 base), Linux 7.2.9 - CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads - RAM: 32 GB DDR4-3600 - Node: 24.18.0 on main `1f692c83e`; 22.23.1, 24.18.0 and 26.11.1 on #1471's head `8414ee49` - REA: built from source with `npm ci` and `npm run build` </details> 🤖 Generated with Claude Code (Opus 5.5) 🦊
bsakaguchi 2026-10-15 18:25:42 +00:00
Author
Owner

@CassieFox23 commented on GitHub (Oct 10, 2026):

TL;DR: Not fixed on today's main cca7bf002. REA still says literal 1 for a write through a getter's object, with complete coverage, but real JavaScript gives 2. All six shapes fail the same way on Node 22, 24 and 26, over the CLI and over MCP.

Results

Each cell shows what REA reported over the CLI, then over MCP. All cells had complete coverage. Real JavaScript gave the number in the second column on all three Node versions.

Getter shape Real JS Node 22.23.1 Node 24.18.0 Node 26.11.1
Object getter, box.v.x = 2 2 literal 1 / literal 1 literal 1 / literal 1 literal 1 / literal 1
Class getter, new B().v.x = 2 2 literal 1 / literal 1 literal 1 / literal 1 literal 1 / literal 1
Static getter, B.v.x = 2 2 literal 1 / literal 1 literal 1 / literal 1 literal 1 / literal 1
Getter copied first, const t = box.v; t.x = 2 2 literal 1 / literal 1 literal 1 / literal 1 literal 1 / literal 1
Class instance in a module variable, inst.v.x = 2 2 literal 1 / literal 1 literal 1 / literal 1 literal 1 / literal 1
Same instance, copied first, const t = inst.v; t.x = 2 2 literal 1 / literal 1 literal 1 / literal 1 literal 1 / literal 1

These cells match my last run on ca7ef69c0. Eight JavaScript commits landed since then, and none changed a cell. Two touch member writes or returned objects, 47947eb01 and 5d6eeb22f. The first is a speed-up. The second lets the pass pause. 8c47a9ce4 reshapes a nearby pass.

Still not run

  • Windows and macOS.
  • Getters in real-world code. All my shapes are small.
Controls, to check that the test can see a difference
Shape Real JS REA on cca7bf002, CLI on Node 22, 24 and 26 REA, MCP on Node 22, 24 and 26
Method call, box.m().x = 2 2 unknown unknown
Getter read, no write 1 literal 1 (correct) literal 1 (correct)
Getter stored in a slot, o.a = box.v; o.a.x = 2 2 unknown unknown
Getter made with Object.defineProperty 2 unknown unknown

These are the same as on ca7ef69c0. The method call gives unknown, as #1471 intended.

The eight commits since ca7ef69c0
  • 47947eb01: faster lookup of returned objects (Geekwolf)
  • b973b1b1e: a test for many returned objects (Geekwolf)
  • 9ffc6210a: removes a repeated test (N0zoM1z0)
  • 5d6eeb22f: the write pass pauses for other work, #1545 (Kaoru0822)
  • 8c47a9ce4: more passes pause for other work, #1557 (Ysopwnable)
  • 0e19db9fc: package path lookup, #1424 (AWUQAQ)
  • 649661dc8: media tags no longer read as Electron scripts
  • 4d8d76b25: Electron app.asar pages, #1400

I read three diffs in full (47947eb01, b973b1b1e, 9ffc6210a) and the member-write parts of two more. For the other three I read only file lists or a few lines.

Commands
node --input-type=module -e "$(cat app.js); console.log(result())"   # real JS, each Node
rea analyze-javascript-application <folder> --artifact-format directory --json   # CLI, each Node
# MCP: stdio client from the built tree's node_modules,
# tool analyze_javascript_application { input_path, format: "directory" }, server started by the same Node
Test setup
  • Box: Nobara 44 (Fedora 44 base), Linux 7.2.9
  • CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads
  • RAM: 32 GB DDR4-3600
  • Node: 22.23.1, 24.18.0, 26.11.1
  • Commits: main cca7bf002, previous run ca7ef69c0
  • REA: built from source with npm ci and npm run build, on Node 24.18.0

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

<!-- gh-comment-id:6099256228 --> @CassieFox23 commented on GitHub (Oct 10, 2026): **TL;DR:** Not fixed on today's main `cca7bf002`. REA still says `literal 1` for a write through a getter's object, with `complete` coverage, but real JavaScript gives 2. All six shapes fail the same way on Node 22, 24 and 26, over the CLI and over MCP. ### Results Each cell shows what REA reported over the CLI, then over MCP. All cells had `complete` coverage. Real JavaScript gave the number in the second column on all three Node versions. | Getter shape | Real JS | Node 22.23.1 | Node 24.18.0 | Node 26.11.1 | |---|---|---|---|---| | Object getter, `box.v.x = 2` | 2 | `literal 1` / `literal 1` | `literal 1` / `literal 1` | `literal 1` / `literal 1` | | Class getter, `new B().v.x = 2` | 2 | `literal 1` / `literal 1` | `literal 1` / `literal 1` | `literal 1` / `literal 1` | | Static getter, `B.v.x = 2` | 2 | `literal 1` / `literal 1` | `literal 1` / `literal 1` | `literal 1` / `literal 1` | | Getter copied first, `const t = box.v; t.x = 2` | 2 | `literal 1` / `literal 1` | `literal 1` / `literal 1` | `literal 1` / `literal 1` | | Class instance in a module variable, `inst.v.x = 2` | 2 | `literal 1` / `literal 1` | `literal 1` / `literal 1` | `literal 1` / `literal 1` | | Same instance, copied first, `const t = inst.v; t.x = 2` | 2 | `literal 1` / `literal 1` | `literal 1` / `literal 1` | `literal 1` / `literal 1` | These cells match my last run on `ca7ef69c0`. Eight JavaScript commits landed since then, and none changed a cell. Two touch member writes or returned objects, `47947eb01` and `5d6eeb22f`. The first is a speed-up. The second lets the pass pause. `8c47a9ce4` reshapes a nearby pass. ### Still not run - Windows and macOS. - Getters in real-world code. All my shapes are small. <details> <summary>Controls, to check that the test can see a difference</summary> | Shape | Real JS | REA on `cca7bf002`, CLI on Node 22, 24 and 26 | REA, MCP on Node 22, 24 and 26 | |---|---|---|---| | Method call, `box.m().x = 2` | 2 | `unknown` | `unknown` | | Getter read, no write | 1 | `literal 1` (correct) | `literal 1` (correct) | | Getter stored in a slot, `o.a = box.v; o.a.x = 2` | 2 | `unknown` | `unknown` | | Getter made with `Object.defineProperty` | 2 | `unknown` | `unknown` | These are the same as on `ca7ef69c0`. The method call gives `unknown`, as #1471 intended. </details> <details> <summary>The eight commits since <code>ca7ef69c0</code></summary> - `47947eb01`: faster lookup of returned objects (Geekwolf) - `b973b1b1e`: a test for many returned objects (Geekwolf) - `9ffc6210a`: removes a repeated test (N0zoM1z0) - `5d6eeb22f`: the write pass pauses for other work, #1545 (Kaoru0822) - `8c47a9ce4`: more passes pause for other work, #1557 (Ysopwnable) - `0e19db9fc`: package path lookup, #1424 (AWUQAQ) - `649661dc8`: media tags no longer read as Electron scripts - `4d8d76b25`: Electron app.asar pages, #1400 I read three diffs in full (`47947eb01`, `b973b1b1e`, `9ffc6210a`) and the member-write parts of two more. For the other three I read only file lists or a few lines. </details> <details> <summary>Commands</summary> ```sh node --input-type=module -e "$(cat app.js); console.log(result())" # real JS, each Node rea analyze-javascript-application <folder> --artifact-format directory --json # CLI, each Node # MCP: stdio client from the built tree's node_modules, # tool analyze_javascript_application { input_path, format: "directory" }, server started by the same Node ``` </details> <details> <summary>Test setup</summary> - Box: Nobara 44 (Fedora 44 base), Linux 7.2.9 - CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads - RAM: 32 GB DDR4-3600 - Node: 22.23.1, 24.18.0, 26.11.1 - Commits: main `cca7bf002`, previous run `ca7ef69c0` - REA: built from source with `npm ci` and `npm run build`, on Node 24.18.0 </details> 🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊
Author
Owner

@CassieFox23 commented on GitHub (Oct 11, 2026):

TL;DR: Confirmed fixed. On today's main 2612e474b, all six getter shapes now say unknown instead of a wrong literal 1. That holds over the CLI and over MCP, on Node 22, 24 and 26.

Results

Real JavaScript gives 2 for every shape below. REA now says unknown, which is the honest answer this issue asked for. It does not recover the value 2. The return shape coverage still reads complete, but the field itself is marked unknown. All three Node versions gave the same cell in each row.

Getter shape Real JS Main CLI Main MCP
Object getter, box.v.x = 2 2 unknown unknown
Class getter, new B().v.x = 2 2 unknown unknown
Static getter, B.v.x = 2 2 unknown unknown
Getter copied first, const t = box.v; t.x = 2 2 unknown unknown
Class instance in a module variable, inst.v.x = 2 2 unknown unknown
Same instance, copied first, const t = inst.v; t.x = 2 2 unknown unknown

The two test files from the commit also pass on main 2612e474b: 187 tests, on Node 24.18.0. Main has since moved to 66c344e28, which changes no files under src.

Not run

  • Windows and macOS.
  • Getters in real-world code. All my shapes are small.
  • The build before the fix (5485483f7) on MCP, Node 22 and Node 26. It ran on the CLI and Node 24 only.

Thanks, morluto, for the fix in bc7846db9.

Controls, to check that the test can see a difference
Shape Real JS Before the fix, 5485483f7, CLI on Node 24 Main 2612e474b, CLI and MCP
Method call, box.m().x = 2 2 unknown unknown
Getter read, no write 1 literal 1 literal 1 (correct)
Getter stored in a slot, o.a = box.v; o.a.x = 2 2 unknown unknown
Getter made with Object.defineProperty 2 unknown unknown
The six shapes above 2 literal 1 in all six unknown in all six

Main 2612e474b and bc7846db9 gave the same cells as a11fbc0df, the main I tested first.

Commands
node --input-type=module -e "import('./app.js').then(m => console.log(m.result()))"   # real JS, each Node
rea analyze-javascript-application <folder> --artifact-format directory --json   # CLI, each Node
# MCP: stdio client from the built tree's node_modules,
# tool analyze_javascript_application { input_path, format: "directory" }, server started by the same Node
npx vitest run src/domain/javascript/javascriptSemanticAnalysis.member_mutations.test.ts tests/acceptance/applications/javascriptReturnedReferences.test.ts

I rebuilt the ten small test files from this issue and my earlier comment, so they are not the original files.

Test setup
  • Box: Nobara 44 (Fedora 44 base), Linux 7.2.9
  • CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads
  • RAM: 32 GB DDR4-3600
  • Node: 22.23.1, 24.18.0, 26.11.1
  • Main: 2612e474b
  • Fix commit: bc7846db9
  • Main, tested first: a11fbc0df
  • Before the fix: 5485483f7
  • REA: built from source with npm ci and npm run build, on Node 24.18.0

🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊

<!-- gh-comment-id:6103810732 --> @CassieFox23 commented on GitHub (Oct 11, 2026): **TL;DR:** Confirmed fixed. On today's main 2612e474b, all six getter shapes now say `unknown` instead of a wrong `literal 1`. That holds over the CLI and over MCP, on Node 22, 24 and 26. ### Results Real JavaScript gives 2 for every shape below. REA now says `unknown`, which is the honest answer this issue asked for. It does not recover the value 2. The return shape coverage still reads `complete`, but the field itself is marked unknown. All three Node versions gave the same cell in each row. | Getter shape | Real JS | Main CLI | Main MCP | |---|---|---|---| | Object getter, `box.v.x = 2` | 2 | `unknown` | `unknown` | | Class getter, `new B().v.x = 2` | 2 | `unknown` | `unknown` | | Static getter, `B.v.x = 2` | 2 | `unknown` | `unknown` | | Getter copied first, `const t = box.v; t.x = 2` | 2 | `unknown` | `unknown` | | Class instance in a module variable, `inst.v.x = 2` | 2 | `unknown` | `unknown` | | Same instance, copied first, `const t = inst.v; t.x = 2` | 2 | `unknown` | `unknown` | The two test files from the commit also pass on main 2612e474b: 187 tests, on Node 24.18.0. Main has since moved to 66c344e28, which changes no files under `src`. ### Not run - Windows and macOS. - Getters in real-world code. All my shapes are small. - The build before the fix (5485483f7) on MCP, Node 22 and Node 26. It ran on the CLI and Node 24 only. Thanks, morluto, for the fix in bc7846db9. <details> <summary>Controls, to check that the test can see a difference</summary> | Shape | Real JS | Before the fix, 5485483f7, CLI on Node 24 | Main 2612e474b, CLI and MCP | |---|---|---|---| | Method call, `box.m().x = 2` | 2 | `unknown` | `unknown` | | Getter read, no write | 1 | `literal 1` | `literal 1` (correct) | | Getter stored in a slot, `o.a = box.v; o.a.x = 2` | 2 | `unknown` | `unknown` | | Getter made with `Object.defineProperty` | 2 | `unknown` | `unknown` | | The six shapes above | 2 | `literal 1` in all six | `unknown` in all six | Main 2612e474b and bc7846db9 gave the same cells as a11fbc0df, the main I tested first. </details> <details> <summary>Commands</summary> ```sh node --input-type=module -e "import('./app.js').then(m => console.log(m.result()))" # real JS, each Node rea analyze-javascript-application <folder> --artifact-format directory --json # CLI, each Node # MCP: stdio client from the built tree's node_modules, # tool analyze_javascript_application { input_path, format: "directory" }, server started by the same Node npx vitest run src/domain/javascript/javascriptSemanticAnalysis.member_mutations.test.ts tests/acceptance/applications/javascriptReturnedReferences.test.ts ``` I rebuilt the ten small test files from this issue and my earlier comment, so they are not the original files. </details> <details> <summary>Test setup</summary> - Box: Nobara 44 (Fedora 44 base), Linux 7.2.9 - CPU: AMD Ryzen 9 5900X, 12 cores / 24 threads - RAM: 32 GB DDR4-3600 - Node: 22.23.1, 24.18.0, 26.11.1 - Main: 2612e474b - Fix commit: bc7846db9 - Main, tested first: a11fbc0df - Before the fix: 5485483f7 - REA: built from source with `npm ci` and `npm run build`, on Node 24.18.0 </details> 🤖 Generated with Claude Code (Sonnet 5.5, verified by Opus 5.5) 🦊
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
morluto/rea#508
No description provided.