[GH-ISSUE #4630] [Bug] Array-element setter writes leave stale exported return literals #2410

Closed
opened 2026-10-17 21:04:17 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @coygeek on GitHub (Oct 17, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/4630

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

analyze-javascript-application reports the exact return literal 1 after an assignment invokes an own setter on an array element and that setter changes the sibling property subsequently returned. Independently executing the supplied benign fixture returns 2. This reproduces on both tested builds. The CLI exits successfully and retains the stale literal in the exported return projection.

Reproduction

Create the following complete ES module in a readable directory and analyze it with the public CLI. For the published-package run, the package was installed locally with npm install --prefix <scratch>/release --ignore-scripts --no-audit --no-fund rea-agents@6.5.0 and its scripts/rea.mjs launcher was run with Node. For the source run, npm ci and npm run build:cached prepared the checkout, then its scripts/rea.mjs launcher was used. Both builds and every analysis ran under a scratch HOME with client configuration overrides cleared; the Node directory was placed first on PATH. No native provider was required.

work="$(mktemp -d)"
mkdir -p "$work/input" "$work/home" "$work/tmp"
cat > "$work/input/app.mjs" <<'JS'
export function observe() {
  const state = { list: [{ value: 1, set update(next) { this.value = next; } }] };
  state.list[0].update = 2;
  return state.list[0].value;
}
JS

From the prepared source checkout, run the following commands. To use the local package installation instead, set rea_cli to that installation's node_modules/rea-agents/scripts/rea.mjs launcher.

rea_cli="$PWD/scripts/rea.mjs"
node_bin="$(dirname "$(node -p process.execPath)")"
env -i PATH="$node_bin:$PATH" HOME="$work/home" TMPDIR="$work/tmp" \
  npm_config_prefix="$work/home/.npm-global" REA_LOG_LEVEL=silent \
  node "$rea_cli" analyze-javascript-application "$work/input" --json \
  > "$work/result.json" 2> "$work/stderr.txt"
node --input-type=module - "$work" <<'JS'
import { readFileSync } from 'node:fs';
import { pathToFileURL } from 'node:url';
const root = process.argv[2];
const evidence = JSON.parse(readFileSync(`${root}/result.json`, 'utf8'));
for (const node of evidence.normalized_result.graph.nodes) {
  for (const { properties: p } of node.observations) {
    if (p.semantic_role === 'export-return-shapes' && p.exported_name === 'observe') {
      console.log('shapes', JSON.stringify(p.static_return_shapes));
      console.log('coverage', JSON.stringify(p.return_shape_coverage));
    }
  }
}
const fixture = await import(pathToFileURL(`${root}/input/app.mjs`).href);
console.log('runtime', fixture.observe());
JS

The primary fixture was analyzed twice per build. A second array receiver form, const arr = [{ value: 1, set update(next) { this.value = next; } }]; arr[0].update = 2; return { value: arr[0].value };, also retained the stale /value literal in both attempts per build and independently returned {"value":2}.

Three controls were each analyzed twice per build: replacing the primary setter with the ordinary data property update: 0; omitting the primary assignment; and replacing state.list[0] with state.nested, initialized as { nested: { value: 1, set update(next) { this.value = next; } } }. Results are below. REA analyzed source statically; the separate Node import executed only these supplied benign fixtures.

Expected behavior

An assignment that can invoke a local setter must not leave an earlier sibling literal exact when that setter can change it. Conservative unknown is sufficient; REA does not need to execute the setter or infer the new value 2. Existing local and nested-setter regression tests require this uncertainty, and the workflow documentation describes conservative accessor uncertainty. The ordinary-property and no-write controls should remain precise.

Impact and scope

The observed failure is a wrong exact literal in exported static Evidence for an own setter on an array-element receiver, including an array nested in an object. Each of the two array forms failed in both attempts on both builds. No export comparison was run, and no severe downstream incident or security boundary crossing was established. Other accessor forms were not assessed.

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: Valid bounded inputs produce a wrong exact return value through the public static-analysis workflow, despite an existing conservative unknown representation.
  • Workaround status: unverified
  • Workaround basis: No practical REA workflow preserving unchanged input artifacts was verified to recover this uncertainty. Executing the benign fixture establishes its oracle but is not a general substitute for static analysis.

REA version

Published npm package rea-agents@6.5.0 (registry latest); current main source commit 8451d283920e86936576fdf67615807d1b4ca4a9. Both launchers report 6.5.0.

Runtime and platform

Node.js v24.18.0; npm 11.16.0; macOS 27.0.1 arm64. The source build used the repository's pinned Node.js and npm versions.

Target and artifact context

Readable directories containing the supplied JavaScript ES modules. No external application or native provider was used.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

  • Expected source: src/domain/javascript/javascriptSemanticAnalysis.accessor_writes.test.ts:11-67 requires unknown after local and nested setter writes while preserving ordinary-property siblings; docs/javascript-application-workflows.md:341-354 describes conservative mutation and accessor uncertainty in the source build.
  • Failure source: Executed public CLI results from both builds, selecting normalized_result.graph.nodes[].observations[].properties where semantic_role is export-return-shapes and exported_name is observe.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete

All twenty analysis invocations exited 0. The meaningful return fields and independent runtime results were:

Primary array setter, both builds, 2/2 each:
{"path":"","presence":"present","state":"literal","value":1,"reason":null}
Independent runtime: 2

Direct array setter with wrapper return, both builds, 2/2 each:
{"path":"/value","presence":"present","state":"literal","value":1,"reason":null}
Independent runtime: {"value":2}

Ordinary-property and no-write controls, both builds, 2/2 each:
state=literal, value=1; independent runtime=1

Nested-object setter control, main build, 2/2:
state=unknown, value=null, reason="Own property value is unknown-coverage."
Nested-object setter control, published package, 2/2:
state=literal, value=1
Independent runtime for the nested-object setter: 2

Each return projection reported the following fields. These describe projection completeness; they do not establish complete semantic knowledge.

{"status":"complete","retained_return_sites":1,"omitted_return_sites":0,"omitted_fields":0,"omitted_property_coverage":0,"projection_complete":true}

The source build's existing focused regression command npm run test:local -- src/domain/javascript/javascriptSemanticAnalysis.accessor_writes.test.ts passed: one file, five tests, exit 0. That file covers object-property receiver paths, but not this array-element receiver form.

Typed outcome or recovery expected

Reanalyze the primary and direct-array fixtures. Their exported post-setter return fields must stop publishing the stale literal 1; retain unknown unless the post-write value can be justified. Keep the nested-object setter control unknown on the source build, and retain literal 1 for the ordinary-property and no-write controls. Check the exported CLI return projection as well as the local semantic result.

Analysis or suspected cause

Source-derived reasoning: localSetterReceiver in src/domain/javascript/javascriptSemanticMemberMutations.ts:96-129 traverses object-literal properties and stops at an array literal, so it does not recognize the element's own setter for receiver-wide invalidation. The public output establishes the stale literal independently of this explanation; untested accessor forms may behave differently.

Additional context

Closed #3312 and its merged fix #3831 cover directly declared and nested object-property setter receivers. The nested-object control is now unknown on the source build, while the array receiver forms remain stale. The published package's stale nested-object result overlaps that existing report and is not presented as a separate new defect. Closed #2654 concerns writes through references returned by getters, rather than assignment invoking an own setter. Open and closed issue and pull-request searches found no report demonstrating this residual array-element setter case. No last known good revision was established.

Originally created by @coygeek on GitHub (Oct 17, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4630 ### 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 `analyze-javascript-application` reports the exact return literal `1` after an assignment invokes an own setter on an array element and that setter changes the sibling property subsequently returned. Independently executing the supplied benign fixture returns `2`. This reproduces on both tested builds. The CLI exits successfully and retains the stale literal in the exported return projection. ### Reproduction Create the following complete ES module in a readable directory and analyze it with the public CLI. For the published-package run, the package was installed locally with `npm install --prefix <scratch>/release --ignore-scripts --no-audit --no-fund rea-agents@6.5.0` and its `scripts/rea.mjs` launcher was run with Node. For the source run, `npm ci` and `npm run build:cached` prepared the checkout, then its `scripts/rea.mjs` launcher was used. Both builds and every analysis ran under a scratch HOME with client configuration overrides cleared; the Node directory was placed first on PATH. No native provider was required. ```sh work="$(mktemp -d)" mkdir -p "$work/input" "$work/home" "$work/tmp" cat > "$work/input/app.mjs" <<'JS' export function observe() { const state = { list: [{ value: 1, set update(next) { this.value = next; } }] }; state.list[0].update = 2; return state.list[0].value; } JS ``` From the prepared source checkout, run the following commands. To use the local package installation instead, set `rea_cli` to that installation's `node_modules/rea-agents/scripts/rea.mjs` launcher. ```sh rea_cli="$PWD/scripts/rea.mjs" node_bin="$(dirname "$(node -p process.execPath)")" env -i PATH="$node_bin:$PATH" HOME="$work/home" TMPDIR="$work/tmp" \ npm_config_prefix="$work/home/.npm-global" REA_LOG_LEVEL=silent \ node "$rea_cli" analyze-javascript-application "$work/input" --json \ > "$work/result.json" 2> "$work/stderr.txt" node --input-type=module - "$work" <<'JS' import { readFileSync } from 'node:fs'; import { pathToFileURL } from 'node:url'; const root = process.argv[2]; const evidence = JSON.parse(readFileSync(`${root}/result.json`, 'utf8')); for (const node of evidence.normalized_result.graph.nodes) { for (const { properties: p } of node.observations) { if (p.semantic_role === 'export-return-shapes' && p.exported_name === 'observe') { console.log('shapes', JSON.stringify(p.static_return_shapes)); console.log('coverage', JSON.stringify(p.return_shape_coverage)); } } } const fixture = await import(pathToFileURL(`${root}/input/app.mjs`).href); console.log('runtime', fixture.observe()); JS ``` The primary fixture was analyzed twice per build. A second array receiver form, `const arr = [{ value: 1, set update(next) { this.value = next; } }]; arr[0].update = 2; return { value: arr[0].value };`, also retained the stale `/value` literal in both attempts per build and independently returned `{"value":2}`. Three controls were each analyzed twice per build: replacing the primary setter with the ordinary data property `update: 0`; omitting the primary assignment; and replacing `state.list[0]` with `state.nested`, initialized as `{ nested: { value: 1, set update(next) { this.value = next; } } }`. Results are below. REA analyzed source statically; the separate Node import executed only these supplied benign fixtures. ### Expected behavior An assignment that can invoke a local setter must not leave an earlier sibling literal exact when that setter can change it. Conservative unknown is sufficient; REA does not need to execute the setter or infer the new value `2`. Existing local and nested-setter regression tests require this uncertainty, and the workflow documentation describes conservative accessor uncertainty. The ordinary-property and no-write controls should remain precise. ### Impact and scope The observed failure is a wrong exact literal in exported static Evidence for an own setter on an array-element receiver, including an array nested in an object. Each of the two array forms failed in both attempts on both builds. No export comparison was run, and no severe downstream incident or security boundary crossing was established. Other accessor forms were not assessed. - Impact level: P2 - Assessment status: supported - Impact basis: Valid bounded inputs produce a wrong exact return value through the public static-analysis workflow, despite an existing conservative unknown representation. - Workaround status: unverified - Workaround basis: No practical REA workflow preserving unchanged input artifacts was verified to recover this uncertainty. Executing the benign fixture establishes its oracle but is not a general substitute for static analysis. ### REA version Published npm package `rea-agents@6.5.0` (registry `latest`); current `main` source commit `8451d283920e86936576fdf67615807d1b4ca4a9`. Both launchers report `6.5.0`. ### Runtime and platform Node.js v24.18.0; npm 11.16.0; macOS 27.0.1 arm64. The source build used the repository's pinned Node.js and npm versions. ### Target and artifact context Readable directories containing the supplied JavaScript ES modules. No external application or native provider was used. ### Interface CLI ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence - Expected source: `src/domain/javascript/javascriptSemanticAnalysis.accessor_writes.test.ts:11-67` requires unknown after local and nested setter writes while preserving ordinary-property siblings; `docs/javascript-application-workflows.md:341-354` describes conservative mutation and accessor uncertainty in the source build. - Failure source: Executed public CLI results from both builds, selecting `normalized_result.graph.nodes[].observations[].properties` where `semantic_role` is `export-return-shapes` and `exported_name` is `observe`. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete All twenty analysis invocations exited `0`. The meaningful return fields and independent runtime results were: ```text Primary array setter, both builds, 2/2 each: {"path":"","presence":"present","state":"literal","value":1,"reason":null} Independent runtime: 2 Direct array setter with wrapper return, both builds, 2/2 each: {"path":"/value","presence":"present","state":"literal","value":1,"reason":null} Independent runtime: {"value":2} Ordinary-property and no-write controls, both builds, 2/2 each: state=literal, value=1; independent runtime=1 Nested-object setter control, main build, 2/2: state=unknown, value=null, reason="Own property value is unknown-coverage." Nested-object setter control, published package, 2/2: state=literal, value=1 Independent runtime for the nested-object setter: 2 ``` Each return projection reported the following fields. These describe projection completeness; they do not establish complete semantic knowledge. ```json {"status":"complete","retained_return_sites":1,"omitted_return_sites":0,"omitted_fields":0,"omitted_property_coverage":0,"projection_complete":true} ``` The source build's existing focused regression command `npm run test:local -- src/domain/javascript/javascriptSemanticAnalysis.accessor_writes.test.ts` passed: one file, five tests, exit `0`. That file covers object-property receiver paths, but not this array-element receiver form. ### Typed outcome or recovery expected Reanalyze the primary and direct-array fixtures. Their exported post-setter return fields must stop publishing the stale literal `1`; retain unknown unless the post-write value can be justified. Keep the nested-object setter control unknown on the source build, and retain literal `1` for the ordinary-property and no-write controls. Check the exported CLI return projection as well as the local semantic result. ### Analysis or suspected cause Source-derived reasoning: `localSetterReceiver` in `src/domain/javascript/javascriptSemanticMemberMutations.ts:96-129` traverses object-literal properties and stops at an array literal, so it does not recognize the element's own setter for receiver-wide invalidation. The public output establishes the stale literal independently of this explanation; untested accessor forms may behave differently. ### Additional context Closed [#3312](https://github.com/morluto/rea/issues/3312) and its merged fix [#3831](https://github.com/morluto/rea/pull/3831) cover directly declared and nested object-property setter receivers. The nested-object control is now unknown on the source build, while the array receiver forms remain stale. The published package's stale nested-object result overlaps that existing report and is not presented as a separate new defect. Closed [#2654](https://github.com/morluto/rea/issues/2654) concerns writes through references returned by getters, rather than assignment invoking an own setter. Open and closed issue and pull-request searches found no report demonstrating this residual array-element setter case. No last known good revision was established.
Author
Owner

@xu91102 commented on GitHub (Oct 17, 2026):

Independently reproduced on current main 150e996a86f2bec4982ba87f37bf502a8d5e8214 with Node 24.18.0 on macOS arm64, before changing source.

I ran the supplied benign array-setter fixture through the actual compiled package CLI and through analyze_javascript_application over a real SDK stdio MCP connection. Both publish the stale exact return literal 1; independently importing and executing only that supplied fixture with Node returns 2.

The same four-case matrix ran through both adapters:

Case Node result REA CLI / stdio MCP return field
Own setter on state.list[0], then assignment 2 literal 1 (incorrect)
Ordinary data property instead of setter 1 literal 1
Setter present, no assignment 1 literal 1
Existing nested-object setter control 2 unknown (expected conservative result)

Each CLI invocation exited 0. All four MCP calls completed without tool errors on one connection. These are small constructed inputs; no production application, native provider, or frequency claim is involved. REA analyzed the source statically; the separate Node control executed only the explicitly supplied benign function.

I also read the actual related PR file patches, including #3831 and #2041: the remaining localSetterReceiver traversal still does not descend an array-element receiver. The other selected mutation/lookup patches (#2891, #4331, #4336, #4363, #4364, #2702, #3836 and #4355) do not supply that traversal. The original object-setter fix remains useful and should be preserved.

I am preparing a focused owner-level fix and public CLI/MCP regressions, keeping ordinary/no-write controls precise and representing the setter result conservatively as unknown. No new upstream PR or CI run exists yet.

Prepared fork fix (2026-10-17)

The owner-level fix is available as normal fork commit 01aa4094. It reuses the existing array-reference selection and setter definition-order owners, preserving ordinary/no-write/delete/replaced-accessor and unrelated-element precision. A fresh compiled CLI replay on main cc2e18a265198492dff1587a343bfb802d8c591b still reproduced the original stale literal with the controls correct.

Final frozen code passed a fresh34-file/967-test run, including an actual compiled CLI and official SDK stdio regression. Separate immutable four-case CLI/SDK replay, final check/guards, docs plus5README checks, discovery of all1,461 files and independent Standards/Spec review passed. Review found and resolved a candidate enqueue argument-capacity regression; the original failure and its meaningful RED/GREEN case are preserved. This was a source-analysis API capacity defect; the smaller compiled CLI control did not fail. No Windows/Linux host, native or external IDE claim is made.

This remains a prepared fork branch: there is no new upstream PR or upstream CI yet. Ordinary upstream creation is still denied for an unknown policy; no unchanged retry or bypass was attempted.

<!-- gh-comment-id:6229023322 --> @xu91102 commented on GitHub (Oct 17, 2026): Independently reproduced on current main `150e996a86f2bec4982ba87f37bf502a8d5e8214` with Node 24.18.0 on macOS arm64, before changing source. I ran the supplied benign array-setter fixture through the actual compiled package CLI and through `analyze_javascript_application` over a real SDK stdio MCP connection. Both publish the stale exact return literal `1`; independently importing and executing only that supplied fixture with Node returns `2`. The same four-case matrix ran through both adapters: | Case | Node result | REA CLI / stdio MCP return field | | --- | --- | --- | | Own setter on `state.list[0]`, then assignment | `2` | literal `1` (incorrect) | | Ordinary data property instead of setter | `1` | literal `1` | | Setter present, no assignment | `1` | literal `1` | | Existing nested-object setter control | `2` | unknown (expected conservative result) | Each CLI invocation exited 0. All four MCP calls completed without tool errors on one connection. These are small constructed inputs; no production application, native provider, or frequency claim is involved. REA analyzed the source statically; the separate Node control executed only the explicitly supplied benign function. I also read the actual related PR file patches, including #3831 and #2041: the remaining `localSetterReceiver` traversal still does not descend an array-element receiver. The other selected mutation/lookup patches (#2891, #4331, #4336, #4363, #4364, #2702, #3836 and #4355) do not supply that traversal. The original object-setter fix remains useful and should be preserved. I am preparing a focused owner-level fix and public CLI/MCP regressions, keeping ordinary/no-write controls precise and representing the setter result conservatively as unknown. No new upstream PR or CI run exists yet. ## Prepared fork fix (2026-10-17) The owner-level fix is available as normal fork commit [01aa4094](https://github.com/xu91102/rea/commit/01aa40946c344b4dc0bade4bee0b363610503f05). It reuses the existing array-reference selection and setter definition-order owners, preserving ordinary/no-write/delete/replaced-accessor and unrelated-element precision. A fresh compiled CLI replay on main `cc2e18a265198492dff1587a343bfb802d8c591b` still reproduced the original stale literal with the controls correct. Final frozen code passed a fresh34-file/967-test run, including an actual compiled CLI and official SDK stdio regression. Separate immutable four-case CLI/SDK replay, final check/guards, docs plus5README checks, discovery of all1,461 files and independent Standards/Spec review passed. Review found and resolved a candidate enqueue argument-capacity regression; the original failure and its meaningful RED/GREEN case are preserved. This was a source-analysis API capacity defect; the smaller compiled CLI control did not fail. No Windows/Linux host, native or external IDE claim is made. This remains a prepared fork branch: there is no new upstream PR or upstream CI yet. Ordinary upstream creation is still denied for an unknown policy; no unchanged retry or bypass was attempted.
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

Checked the reported fix on main at d87e73f2db.

localAccessorMethods now descends array elements through semanticArrayPropertyReferences, and setter writes invalidate the receiver. The exact reported state.list[0] fixture now returns unknown rather than stale literal 1; ordinary-data-property control still returns literal 1. Existing accessor_writes tests cover related object/nested setter semantics but no exact array-setter CLI/MCP regression was located.

Implementation: javascriptSemanticMemberMutations.ts, javascriptSemanticMemberMutations.ts.

The relevant regression checks in javascriptSemanticAnalysis.accessor_writes.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380764646 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. localAccessorMethods now descends array elements through semanticArrayPropertyReferences, and setter writes invalidate the receiver. The exact reported state.list[0] fixture now returns unknown rather than stale literal 1; ordinary-data-property control still returns literal 1. Existing accessor_writes tests cover related object/nested setter semantics but no exact array-setter CLI/MCP regression was located. Implementation: [javascriptSemanticMemberMutations.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/javascriptSemanticMemberMutations.ts#L186), [javascriptSemanticMemberMutations.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/javascriptSemanticMemberMutations.ts#L984). The relevant regression checks in [javascriptSemanticAnalysis.accessor_writes.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/domain/javascript/javascriptSemanticAnalysis.accessor_writes.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.
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#2410
No description provided.