[GH-ISSUE #6729] [Bug] Deleted CommonJS exports remain selectable for definite return-shape changes #4093

Closed
opened 2026-10-31 00:46:46 +00:00 by bsakaguchi · 0 comments
Owner

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

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

After exports.read = () => ({type: "result", value: 1}); delete exports.read;, analyze-javascript-application retains a read export and its complete literal return shape. Comparing two such modules differing only in the deleted function's value reports a definite /value change from 1 to 2, with both selectors selected, zero unknown changes, and complete-within-inputs coverage.

Node's actual CommonJS loader returns an empty export object for both self-authored fixtures. An intact-export control correctly exposes a callable and reports its real return-shape change. The paired reproduction and control were observed in two independent temporary-directory rounds. Every analysis, comparison, and Node loader command exited 0.

Reproduction

Use the prepared source checkout at the revision below, with its locked dependencies and compiled runtime. From its root, run the following equivalent sequence of the executed probe. It creates only disposable fixtures and isolated configuration under /tmp; Node executes only the generated fixtures. REA performs static analysis.

python3 - <<'PY'
import json, os, pathlib, subprocess, tempfile

repo = pathlib.Path.cwd()
root = pathlib.Path(tempfile.mkdtemp(prefix="agent-work.", dir="/tmp"))
for name in ("home", "config", "cache", "temp"):
    (root / name).mkdir()
env = {"PATH": os.environ["PATH"], "HOME": str(root / "home"),
       "XDG_CONFIG_HOME": str(root / "config"),
       "XDG_CACHE_HOME": str(root / "cache"), "TMPDIR": str(root / "temp")}

def run(*args):
    return subprocess.run(["node", *map(str, args)], cwd=repo, env=env,
                          check=True, capture_output=True, text=True,
                          timeout=60).stdout

for mode in ("deleted", "intact"):
    evidence, runtime = [], []
    for value in (1, 2):
        app = root / f"{mode}-{value}"
        app.mkdir()
        (app / "main.cjs").write_text(
            'exports.read = () => ({type: "result", value: ' + str(value) + '});\n'
            + ('delete exports.read;\n' if mode == "deleted" else ""))
        evidence.append(json.loads(run(repo / "scripts/rea.mjs",
            "analyze-javascript-application", app,
            "--artifact-format", "directory", "--json")))
        runtime.append(json.loads(run("-e",
            'const m=require(process.argv[1]);console.log(JSON.stringify('
            '{keys:Object.keys(m),type:typeof m.read}));', app / "main.cjs")))
    request = root / f"{mode}-request.json"
    request.write_text(json.dumps(dict(left=evidence[0], right=evidence[1],
        left_module_path="main.cjs", right_module_path="main.cjs",
        left_export_name="read", right_export_name="read")))
    result = json.loads(run(repo / "scripts/rea.mjs",
        "compare-javascript-export-shapes", request, "--json"))["normalized_result"]
    print(json.dumps(dict(case=mode, runtime=runtime,
        selectors=[result["left"]["status"], result["right"]["status"]],
        summary=result["summary"], coverage=result["coverage"]["status"])))
PY

The deleted case still selects read on both sides and reports one changed field. The intact case supplies the positive control. Full Evidence objects, rather than Evidence identifiers alone, are passed to comparison.

Expected behavior

A visible unconditional deletion must prevent the earlier function assignment from establishing a currently available public export and trusted callable. Returning explicit uncertainty is sufficient if the final export surface cannot be established; REA must not present a definite return-field difference for this missing callable. Preserve the intact-export control.

The export-shape contract requires exact export/callable relationships and an unknown result for missing selectors. The mutation contract includes explicit deletion. The owned Node loader controls establish that neither deleted fixture retains this property. This expectation requires conservative static evidence, not execution of arbitrary analyzed code.

Impact and scope

The observed consequence is a false API return-shape difference for an export absent from both modules. The defect is bounded to the tested ordinary CommonJS property assignment followed by unconditional deletion. Real-application prevalence, other deletion forms, other platforms, MCP transport, and the published release runtime were not measured. No workaround preserving the original artifact and correcting the retained Evidence was verified; inspecting the source remains possible.

REA version

Source build at 3c5c887a2cc7371a94fe0fd8b6c478dbf2af60db. Its manifest says 6.7.0; this main revision includes changes after the latest published rea-agents 6.7.0 release. The published package was not executed for this finding.

Runtime and platform

Node.js v24.22.1; Linux x64 (Omarchy). Locked checkout dependencies and the prepared compiled runtime. No native analysis provider was involved.

Target and artifact context

Two self-authored directory artifacts, each containing one main.cjs assigning and then deleting exports.read. The intact control pair omits only the deletion. No package, process, device, network target, getters, custom iterators, or deferred callbacks are needed.

Interface

CLI

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

Observed reduced output from each paired run:

{"case":"deleted","node":[{"keys":[],"type":"undefined"},{"keys":[],"type":"undefined"}],"selectors":["selected","selected"],"summary":{"added":0,"removed":0,"changed":1,"unknown":0},"coverage":"complete-within-inputs"}
{"case":"intact","node":[{"keys":["read"],"type":"function"},{"keys":["read"],"type":"function"}],"selectors":["selected","selected"],"summary":{"added":0,"removed":0,"changed":1,"unknown":0},"coverage":"complete-within-inputs"}

The deleted comparison's sole change was:

{"status":"changed","path":"/value","discriminant":{"path":"/type","value":"result"},"presence":{"left":"present","right":"present"},"left":{"availability":"literal","value":1},"right":{"availability":"literal","value":2},"limitations":[]}

The deleted fixture's analysis also retained an export-binding observation for read with authority: "ast-static-analysis", state: "observed", confidence: "exact", and complete coverage, plus an export-return-shapes projection with projection_complete: true.

Evidence provenance: observed. Local verification: reproduced. Reproduction completeness: complete. REA itself did not execute the source. Its general static-analysis limitations remain present; they do not specifically invalidate this stale export selection.

Typed outcome or recovery expected

The deleted pair must not produce the definite /value change through the former read callable. A missing or unavailable selector with explicit uncertainty is acceptable. The intact pair must continue selecting the genuine export and reporting its change. These are proposed restoration checks; no repair was made.

Analysis or suspected cause

Source-derived explanation: the CommonJS export producer records the assignment's function as a module link. The receiver lifetime pass follows the public object and wrapper identities; deleting exports.read changes a property rather than either identity. Callable finalization preserves the direct anonymous callable link, and the export selector trusts the resulting unique export and shape. The executed CLI comparison, rather than this explanation alone, establishes the false selection.

Additional context

Related reports were inspected: #3541 covers detached CommonJS receiver objects; these fixtures retain the same connected public object. #3315 covers a deleted or replaced Webpack interop helper and false Electron attribution; this fixture has no bundle or Electron usage. #564 concerns the separate replay sandbox. #6620 concerns variable call-spread export names. No duplicate for this ordinary property-deletion and export-shape-selection signal was found in the inspected open/closed searches.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A small valid CommonJS fixture creates a trusted missing export and a definite false API change in retained comparison Evidence. The intact control works. Severe downstream harm and real-world prevalence were not established.
  • Workaround status: unverified
  • Workaround basis: No public option was demonstrated to correct the retained export facts while preserving the original input; removing the deletion is a diagnostic control that changes the artifact.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6729 ## 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 After `exports.read = () => ({type: "result", value: 1}); delete exports.read;`, `analyze-javascript-application` retains a `read` export and its complete literal return shape. Comparing two such modules differing only in the deleted function's `value` reports a definite `/value` change from 1 to 2, with both selectors `selected`, zero unknown changes, and `complete-within-inputs` coverage. Node's actual CommonJS loader returns an empty export object for both self-authored fixtures. An intact-export control correctly exposes a callable and reports its real return-shape change. The paired reproduction and control were observed in two independent temporary-directory rounds. Every analysis, comparison, and Node loader command exited 0. ## Reproduction Use the prepared source checkout at the revision below, with its locked dependencies and compiled runtime. From its root, run the following equivalent sequence of the executed probe. It creates only disposable fixtures and isolated configuration under `/tmp`; Node executes only the generated fixtures. REA performs static analysis. ```sh python3 - <<'PY' import json, os, pathlib, subprocess, tempfile repo = pathlib.Path.cwd() root = pathlib.Path(tempfile.mkdtemp(prefix="agent-work.", dir="/tmp")) for name in ("home", "config", "cache", "temp"): (root / name).mkdir() env = {"PATH": os.environ["PATH"], "HOME": str(root / "home"), "XDG_CONFIG_HOME": str(root / "config"), "XDG_CACHE_HOME": str(root / "cache"), "TMPDIR": str(root / "temp")} def run(*args): return subprocess.run(["node", *map(str, args)], cwd=repo, env=env, check=True, capture_output=True, text=True, timeout=60).stdout for mode in ("deleted", "intact"): evidence, runtime = [], [] for value in (1, 2): app = root / f"{mode}-{value}" app.mkdir() (app / "main.cjs").write_text( 'exports.read = () => ({type: "result", value: ' + str(value) + '});\n' + ('delete exports.read;\n' if mode == "deleted" else "")) evidence.append(json.loads(run(repo / "scripts/rea.mjs", "analyze-javascript-application", app, "--artifact-format", "directory", "--json"))) runtime.append(json.loads(run("-e", 'const m=require(process.argv[1]);console.log(JSON.stringify(' '{keys:Object.keys(m),type:typeof m.read}));', app / "main.cjs"))) request = root / f"{mode}-request.json" request.write_text(json.dumps(dict(left=evidence[0], right=evidence[1], left_module_path="main.cjs", right_module_path="main.cjs", left_export_name="read", right_export_name="read"))) result = json.loads(run(repo / "scripts/rea.mjs", "compare-javascript-export-shapes", request, "--json"))["normalized_result"] print(json.dumps(dict(case=mode, runtime=runtime, selectors=[result["left"]["status"], result["right"]["status"]], summary=result["summary"], coverage=result["coverage"]["status"]))) PY ``` The deleted case still selects `read` on both sides and reports one changed field. The intact case supplies the positive control. Full Evidence objects, rather than Evidence identifiers alone, are passed to comparison. ## Expected behavior A visible unconditional deletion must prevent the earlier function assignment from establishing a currently available public export and trusted callable. Returning explicit uncertainty is sufficient if the final export surface cannot be established; REA must not present a definite return-field difference for this missing callable. Preserve the intact-export control. The [export-shape contract](https://github.com/morluto/rea/blob/3c5c887a2cc7371a94fe0fd8b6c478dbf2af60db/docs/javascript-application-workflows.md#L359-L373) requires exact export/callable relationships and an unknown result for missing selectors. The [mutation contract](https://github.com/morluto/rea/blob/3c5c887a2cc7371a94fe0fd8b6c478dbf2af60db/docs/javascript-application-workflows.md#L214-L221) includes explicit deletion. The owned Node loader controls establish that neither deleted fixture retains this property. This expectation requires conservative static evidence, not execution of arbitrary analyzed code. ## Impact and scope The observed consequence is a false API return-shape difference for an export absent from both modules. The defect is bounded to the tested ordinary CommonJS property assignment followed by unconditional deletion. Real-application prevalence, other deletion forms, other platforms, MCP transport, and the published release runtime were not measured. No workaround preserving the original artifact and correcting the retained Evidence was verified; inspecting the source remains possible. ## REA version Source build at `3c5c887a2cc7371a94fe0fd8b6c478dbf2af60db`. Its manifest says 6.7.0; this main revision includes changes after the latest published rea-agents 6.7.0 release. The published package was not executed for this finding. ## Runtime and platform Node.js v24.22.1; Linux x64 (Omarchy). Locked checkout dependencies and the prepared compiled runtime. No native analysis provider was involved. ## Target and artifact context Two self-authored directory artifacts, each containing one `main.cjs` assigning and then deleting `exports.read`. The intact control pair omits only the deletion. No package, process, device, network target, getters, custom iterators, or deferred callbacks are needed. ## Interface CLI ## Earliest failure stage Native analysis or reconstruction ## Relevant output or evidence Observed reduced output from each paired run: ```json {"case":"deleted","node":[{"keys":[],"type":"undefined"},{"keys":[],"type":"undefined"}],"selectors":["selected","selected"],"summary":{"added":0,"removed":0,"changed":1,"unknown":0},"coverage":"complete-within-inputs"} {"case":"intact","node":[{"keys":["read"],"type":"function"},{"keys":["read"],"type":"function"}],"selectors":["selected","selected"],"summary":{"added":0,"removed":0,"changed":1,"unknown":0},"coverage":"complete-within-inputs"} ``` The deleted comparison's sole change was: ```json {"status":"changed","path":"/value","discriminant":{"path":"/type","value":"result"},"presence":{"left":"present","right":"present"},"left":{"availability":"literal","value":1},"right":{"availability":"literal","value":2},"limitations":[]} ``` The deleted fixture's analysis also retained an `export-binding` observation for `read` with `authority: "ast-static-analysis"`, `state: "observed"`, `confidence: "exact"`, and complete coverage, plus an `export-return-shapes` projection with `projection_complete: true`. Evidence provenance: observed. Local verification: reproduced. Reproduction completeness: complete. REA itself did not execute the source. Its general static-analysis limitations remain present; they do not specifically invalidate this stale export selection. ## Typed outcome or recovery expected The deleted pair must not produce the definite `/value` change through the former `read` callable. A missing or unavailable selector with explicit uncertainty is acceptable. The intact pair must continue selecting the genuine export and reporting its change. These are proposed restoration checks; no repair was made. ## Analysis or suspected cause Source-derived explanation: the [CommonJS export producer](https://github.com/morluto/rea/blob/3c5c887a2cc7371a94fe0fd8b6c478dbf2af60db/src/domain/javascript/javascriptSemanticProjection.ts#L473-L535) records the assignment's function as a module link. The [receiver lifetime pass](https://github.com/morluto/rea/blob/3c5c887a2cc7371a94fe0fd8b6c478dbf2af60db/src/domain/javascript/javascriptCommonJsExportReceivers.ts#L388-L409) follows the public object and wrapper identities; deleting `exports.read` changes a property rather than either identity. [Callable finalization](https://github.com/morluto/rea/blob/3c5c887a2cc7371a94fe0fd8b6c478dbf2af60db/src/domain/javascript/javascriptSemanticReturns.ts#L109-L124) preserves the direct anonymous callable link, and the [export selector](https://github.com/morluto/rea/blob/3c5c887a2cc7371a94fe0fd8b6c478dbf2af60db/src/domain/javascript/javascriptExportShapeSelection.ts#L65-L122) trusts the resulting unique export and shape. The executed CLI comparison, rather than this explanation alone, establishes the false selection. ## Additional context Related reports were inspected: [#3541](https://github.com/morluto/rea/issues/3541) covers detached CommonJS receiver objects; these fixtures retain the same connected public object. [#3315](https://github.com/morluto/rea/issues/3315) covers a deleted or replaced Webpack interop helper and false Electron attribution; this fixture has no bundle or Electron usage. [#564](https://github.com/morluto/rea/issues/564) concerns the separate replay sandbox. [#6620](https://github.com/morluto/rea/issues/6620) concerns variable call-spread export names. No duplicate for this ordinary property-deletion and export-shape-selection signal was found in the inspected open/closed searches. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A small valid CommonJS fixture creates a trusted missing export and a definite false API change in retained comparison Evidence. The intact control works. Severe downstream harm and real-world prevalence were not established. - Workaround status: unverified - Workaround basis: No public option was demonstrated to correct the retained export facts while preserving the original input; removing the deletion is a diagnostic control that changes the artifact.
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#4093
No description provided.