[GH-ISSUE #4206] [Bug] Subtraction assignment prunes the wrong contextual IPC branch #1652

Closed
opened 2026-10-16 21:08:30 +00:00 by bsakaguchi · 0 comments
Owner

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

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 emits the wrong contextual IPC path for an exposed bridge member whose conditional test uses flag -= 1. With flag initialized to 1, the CLI keeps only channel-then and attaches no branch condition, while the subtraction yields zero and the else branch contains channel-else. Both raw IPC syntax observations survive; the failure is in the bridge member's recovered call paths. The output still labels the path as a static candidate, so this report does not claim that REA reports actual runtime execution.

Reproduction

Use the built source checkout at the baseline below, with the pinned Node.js runtime on PATH. Run this block from the repository root. It creates inert extracted applications and isolated home/config/cache/data directories, invokes the supported CLI sequentially, and prints the terminal call paths for desktop.action. It never executes the fixture or loads Electron.

python3 - <<'PY'
import json
import os
from pathlib import Path
import subprocess
from tempfile import TemporaryDirectory

cases = {"compound": "flag -= 1", "plain": "flag = 0", "unknown": "flag -= step"}
with TemporaryDirectory(prefix="rea-symbolic-assignment-") as temporary:
    root = Path(temporary)
    environment = dict(os.environ)
    for key in ("HOME", "XDG_CONFIG_HOME", "XDG_CACHE_HOME", "XDG_DATA_HOME"):
        folder = root / key.lower()
        folder.mkdir()
        environment[key] = str(folder)
    for name, test in cases.items():
        app = root / name
        app.mkdir()
        source = (
            'const {contextBridge, ipcRenderer} = require("electron");\n'
            'function action(step) {\n'
            '  let flag = 1;\n'
            '  if ((' + test + ')) ipcRenderer.send("channel-then");\n'
            '  else ipcRenderer.send("channel-else");\n'
            '}\n'
            'contextBridge.exposeInMainWorld("desktop", {action});\n'
        )
        (app / "main.cjs").write_text(source)
        result = subprocess.run(
            ["node", "scripts/rea.mjs", "analyze-javascript-application",
             str(app), "--format", "json", "--full-output"],
            env=environment, text=True, capture_output=True, timeout=60,
        )
        envelope = json.loads(result.stdout)
        assert result.returncode == 0 and envelope["ok"], envelope
        analysis = envelope["data"]["normalized_result"]
        assert analysis["statistics"]["parsed_javascript_files"] == 1
        assert analysis["statistics"]["parse_failures"] == 0
        paths = {}
        raw = set()
        for node in analysis["graph"]["nodes"]:
            for observation in node["observations"]:
                properties = observation["properties"]
                if node["kind"] == "ipc-channel":
                    raw.add(properties["channel"])
                if (properties.get("entry") == "desktop.action"
                        and properties.get("step_kind") == "terminal"):
                    paths[properties["path_id"]] = {
                        "channel": properties["channel"],
                        "conditions": [condition["expression"]
                                       for condition in properties["conditions"]],
                        "resolution": properties["resolution"],
                    }
        print(name, "exit", result.returncode, "raw", json.dumps(sorted(raw)),
              "paths", json.dumps(sorted(paths.values(), key=lambda item: item["channel"])))
PY

compound tests the subtraction assignment. plain supplies a known-zero assignment control. unknown supplies a caller-dependent subtraction operand through the exposed function's step parameter and checks that ordinary uncertainty can retain both guarded paths.

Expected behavior

Recovered Electron paths preserve branch conditions as static candidates, and unsupported or unresolved syntax must not select an arbitrary branch. For compound, preserve the else path and do not report only an unguarded then path. A conservative analysis may keep both candidates with their predicates; an analysis that models the subtraction correctly may select only else. The plain-assignment control should keep only else, and the unknown-operand control should keep both guarded alternatives. Raw observations of both written IPC calls should remain available in all cases.

This contract concerns static reconstruction of an exposed bridge member. It neither requires executing the application nor establishes that a particular caller invokes the exposed member.

Impact and scope

A valid exposed bridge member produces the wrong machine-readable contextual candidate-path graph. Its else path disappears and its then path loses the predicate. Both raw IPC observations and their source evidence survive. This is a bounded P2 analysis-correctness defect in the authored fixture; no severe downstream failure was demonstrated. The same three-case failure and controls were reproduced in separate CLI runs. NOT_ENOUGH_INFO: prevalence in real applications and consequences for downstream tracing were not measured.

Only the known subtraction case demonstrates wrong branch pruning here. This report does not claim that every compound assignment operator fails. No supported unchanged-input workflow was verified to restore the contextual graph. Replacing the expression with a plain assignment is a control that changes the input, not a demonstrated workaround.

REA version

Tested source revision 62bf09809ef7bb224028916424f8f07652a20f4f, built with npm run build:cached; the CLI reports 6.5.0. Current main 3406dc4d85bf29414319b33b98bd1421d573f663 was source-inspected for applicability. The latest published rea-agents-6.5.0 tag is behind the tested source revision. NOT_ENOUGH_INFO: packaged-release behavior was not tested; the package version string does not establish it.

Runtime and platform

Node.js v24.18.0 and npm 11.16.0 on macOS arm64. No external analysis provider was used. No MCP transport, target-source execution, Electron launch or native-addon loading was exercised.

Target and artifact context

An inert extracted JavaScript application directory containing only main.cjs, with an ordinary require("electron"), a named function exposed as desktop.action, and two literal IPC channels. The fixture is included inline in the reproduction.

Interface

CLI, through node scripts/rea.mjs analyze-javascript-application with JSON and full output.

Earliest failure stage

Native analysis or reconstruction. The static collector constructs the wrong contextual candidate paths before graph projection; request admission and parsing succeed.

Relevant output or evidence

The output below comes from the public CLI running the inert source fixtures above. Each invocation succeeded, parsed one source, and reported zero parse failures.

compound exit 0 raw ["channel-else", "channel-then"] paths [{"channel": "channel-then", "conditions": [], "resolution": "candidate"}]
plain exit 0 raw ["channel-else", "channel-then"] paths [{"channel": "channel-else", "conditions": [], "resolution": "candidate"}]
unknown exit 0 raw ["channel-else", "channel-then"] paths [{"channel": "channel-else", "conditions": ["!(flag -= step)"], "resolution": "candidate"}, {"channel": "channel-then", "conditions": ["flag -= step"], "resolution": "candidate"}]

The numeric result of subtracting one from one and the producing-code explanation are source-derived reasoning. The fixture was not executed to obtain a runtime oracle. The two existing focused source suites passed all 90 tests during independent verification; their current cases do not reject this CLI result.

The same standalone three-case reproduction was rerun for this submission and returned the exact output shown above.

Analysis or suspected cause

Source-derived explanation: the symbolic evaluator evaluates every assignment expression as its right-hand side without distinguishing its operator. The branch walker uses that result's truthiness to select a branch and omit its condition. Static analysis invokes this collector, and graph projection preserves the returned path properties.

The expected contract comes from the reconstruction guide, the candidate-path type, and existing bridge guard tests plus false-branch and early-exit tests. These distinguish syntax observations from inferred paths, preserve branch conditions, and exclude known-false branches. The ordinary-source bridge tests establish that this contract applies beyond module-table syntax.

Current-main applicability was inspected in a separate checkout. Every cited source, contract and test file is unchanged from the tested baseline, including the assignment-expression rule and branch walker. The reproduction was executed on the baseline, not on current main.

Typed outcome or recovery expected

Run the same public reproduction. The compound case must retain channel-else and must no longer contain only an unguarded channel-then path. Either guarded alternatives or a correctly evaluated else-only result can satisfy this check. Preserve the plain-assignment control's else-only result, the unknown-operand control's two guarded candidates, and both raw IPC observations. Keep all results explicitly static candidates. These checks leave the implementation free to model the operation or retain uncertainty.

Additional context

Related reports have independent closure signals. #4168 and #3881 concern export return-shape values; #3536 concerns BrowserWindow configuration recovery; #3963 concerns guarded module-factory completion; #4202 concerns native-binding re-export classification. This fixture has no export-return shape, BrowserWindow configuration, module factory, early return, or native addon. Correcting those behaviors does not correct the assignment-test truthiness shown here.

NOT_ENOUGH_INFO: the first affected revision and last known good revision are unknown. MCP parity, published-package behavior, other platforms, real-application frequency and downstream tracing were not tested.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A valid exposed bridge member produces a wrong machine-readable candidate-path graph and omits its else path. This is a consequential analysis error rather than wording or cleanup. The observed failure is bounded to the authored fixture, and both raw IPC observations and source evidence survive; prevalence in real applications and downstream tracing consequences were not tested.
  • Workaround status: unverified
  • Workaround basis: The retained raw IPC observations and source ranges permit manual inspection, but no supported option or unchanged-input workflow was verified to return the corrected contextual call paths. Replacing the subtraction with a plain assignment is a control that changes the selected source, not a demonstrated workaround for the original artifact.
Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4206 ## 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` emits the wrong contextual IPC path for an exposed bridge member whose conditional test uses `flag -= 1`. With `flag` initialized to `1`, the CLI keeps only `channel-then` and attaches no branch condition, while the subtraction yields zero and the else branch contains `channel-else`. Both raw IPC syntax observations survive; the failure is in the bridge member's recovered call paths. The output still labels the path as a static candidate, so this report does not claim that REA reports actual runtime execution. ## Reproduction Use the built source checkout at the baseline below, with the pinned Node.js runtime on PATH. Run this block from the repository root. It creates inert extracted applications and isolated home/config/cache/data directories, invokes the supported CLI sequentially, and prints the terminal call paths for `desktop.action`. It never executes the fixture or loads Electron. ```sh python3 - <<'PY' import json import os from pathlib import Path import subprocess from tempfile import TemporaryDirectory cases = {"compound": "flag -= 1", "plain": "flag = 0", "unknown": "flag -= step"} with TemporaryDirectory(prefix="rea-symbolic-assignment-") as temporary: root = Path(temporary) environment = dict(os.environ) for key in ("HOME", "XDG_CONFIG_HOME", "XDG_CACHE_HOME", "XDG_DATA_HOME"): folder = root / key.lower() folder.mkdir() environment[key] = str(folder) for name, test in cases.items(): app = root / name app.mkdir() source = ( 'const {contextBridge, ipcRenderer} = require("electron");\n' 'function action(step) {\n' ' let flag = 1;\n' ' if ((' + test + ')) ipcRenderer.send("channel-then");\n' ' else ipcRenderer.send("channel-else");\n' '}\n' 'contextBridge.exposeInMainWorld("desktop", {action});\n' ) (app / "main.cjs").write_text(source) result = subprocess.run( ["node", "scripts/rea.mjs", "analyze-javascript-application", str(app), "--format", "json", "--full-output"], env=environment, text=True, capture_output=True, timeout=60, ) envelope = json.loads(result.stdout) assert result.returncode == 0 and envelope["ok"], envelope analysis = envelope["data"]["normalized_result"] assert analysis["statistics"]["parsed_javascript_files"] == 1 assert analysis["statistics"]["parse_failures"] == 0 paths = {} raw = set() for node in analysis["graph"]["nodes"]: for observation in node["observations"]: properties = observation["properties"] if node["kind"] == "ipc-channel": raw.add(properties["channel"]) if (properties.get("entry") == "desktop.action" and properties.get("step_kind") == "terminal"): paths[properties["path_id"]] = { "channel": properties["channel"], "conditions": [condition["expression"] for condition in properties["conditions"]], "resolution": properties["resolution"], } print(name, "exit", result.returncode, "raw", json.dumps(sorted(raw)), "paths", json.dumps(sorted(paths.values(), key=lambda item: item["channel"]))) PY ``` `compound` tests the subtraction assignment. `plain` supplies a known-zero assignment control. `unknown` supplies a caller-dependent subtraction operand through the exposed function's `step` parameter and checks that ordinary uncertainty can retain both guarded paths. ## Expected behavior Recovered Electron paths preserve branch conditions as static candidates, and unsupported or unresolved syntax must not select an arbitrary branch. For `compound`, preserve the else path and do not report only an unguarded then path. A conservative analysis may keep both candidates with their predicates; an analysis that models the subtraction correctly may select only else. The plain-assignment control should keep only else, and the unknown-operand control should keep both guarded alternatives. Raw observations of both written IPC calls should remain available in all cases. This contract concerns static reconstruction of an exposed bridge member. It neither requires executing the application nor establishes that a particular caller invokes the exposed member. ## Impact and scope A valid exposed bridge member produces the wrong machine-readable contextual candidate-path graph. Its else path disappears and its then path loses the predicate. Both raw IPC observations and their source evidence survive. This is a bounded P2 analysis-correctness defect in the authored fixture; no severe downstream failure was demonstrated. The same three-case failure and controls were reproduced in separate CLI runs. NOT_ENOUGH_INFO: prevalence in real applications and consequences for downstream tracing were not measured. Only the known subtraction case demonstrates wrong branch pruning here. This report does not claim that every compound assignment operator fails. No supported unchanged-input workflow was verified to restore the contextual graph. Replacing the expression with a plain assignment is a control that changes the input, not a demonstrated workaround. ## REA version Tested source revision [62bf09809ef7bb224028916424f8f07652a20f4f](https://github.com/morluto/rea/commit/62bf09809ef7bb224028916424f8f07652a20f4f), built with `npm run build:cached`; the CLI reports `6.5.0`. Current main [3406dc4d85bf29414319b33b98bd1421d573f663](https://github.com/morluto/rea/commit/3406dc4d85bf29414319b33b98bd1421d573f663) was source-inspected for applicability. The latest published `rea-agents-6.5.0` tag is behind the tested source revision. NOT_ENOUGH_INFO: packaged-release behavior was not tested; the package version string does not establish it. ## Runtime and platform Node.js v24.18.0 and npm 11.16.0 on macOS arm64. No external analysis provider was used. No MCP transport, target-source execution, Electron launch or native-addon loading was exercised. ## Target and artifact context An inert extracted JavaScript application directory containing only `main.cjs`, with an ordinary `require("electron")`, a named function exposed as `desktop.action`, and two literal IPC channels. The fixture is included inline in the reproduction. ## Interface CLI, through `node scripts/rea.mjs analyze-javascript-application` with JSON and full output. ## Earliest failure stage Native analysis or reconstruction. The static collector constructs the wrong contextual candidate paths before graph projection; request admission and parsing succeed. ## Relevant output or evidence The output below comes from the public CLI running the inert source fixtures above. Each invocation succeeded, parsed one source, and reported zero parse failures. ```text compound exit 0 raw ["channel-else", "channel-then"] paths [{"channel": "channel-then", "conditions": [], "resolution": "candidate"}] plain exit 0 raw ["channel-else", "channel-then"] paths [{"channel": "channel-else", "conditions": [], "resolution": "candidate"}] unknown exit 0 raw ["channel-else", "channel-then"] paths [{"channel": "channel-else", "conditions": ["!(flag -= step)"], "resolution": "candidate"}, {"channel": "channel-then", "conditions": ["flag -= step"], "resolution": "candidate"}] ``` The numeric result of subtracting one from one and the producing-code explanation are source-derived reasoning. The fixture was not executed to obtain a runtime oracle. The two existing focused source suites passed all 90 tests during independent verification; their current cases do not reject this CLI result. The same standalone three-case reproduction was rerun for this submission and returned the exact output shown above. ## Analysis or suspected cause Source-derived explanation: [the symbolic evaluator](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/src/domain/javascript/javascriptSymbolicBindings.ts#L440-L462) evaluates every assignment expression as its right-hand side without distinguishing its operator. [The branch walker](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/src/domain/javascript/electronWrappedCallPaths.ts#L604-L618) uses that result's truthiness to select a branch and omit its condition. [Static analysis](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/src/domain/javascript/javascriptStaticAnalysis.ts#L104-L111) invokes this collector, and [graph projection](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/src/application/javascript/ElectronBoundaryGraphCallPaths.ts#L31-L78) preserves the returned path properties. The expected contract comes from [the reconstruction guide](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/docs/javascript-artifact-reconstruction.md#L313-L339), [the candidate-path type](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/src/domain/javascript/electronStaticAnalysisTypes.ts#L218-L231), and [existing bridge guard tests](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/src/domain/javascript/electronWrappedCallPaths.test.ts#L534-L563) plus [false-branch and early-exit tests](https://github.com/morluto/rea/blob/62bf09809ef7bb224028916424f8f07652a20f4f/src/domain/javascript/electronWrappedCallPaths.test.ts#L627-L660). These distinguish syntax observations from inferred paths, preserve branch conditions, and exclude known-false branches. The ordinary-source bridge tests establish that this contract applies beyond module-table syntax. Current-main applicability was inspected in a separate checkout. Every cited source, contract and test file is unchanged from the tested baseline, including the assignment-expression rule and branch walker. The reproduction was executed on the baseline, not on current main. ## Typed outcome or recovery expected Run the same public reproduction. The compound case must retain `channel-else` and must no longer contain only an unguarded `channel-then` path. Either guarded alternatives or a correctly evaluated else-only result can satisfy this check. Preserve the plain-assignment control's else-only result, the unknown-operand control's two guarded candidates, and both raw IPC observations. Keep all results explicitly static candidates. These checks leave the implementation free to model the operation or retain uncertainty. ## Additional context Related reports have independent closure signals. #4168 and #3881 concern export return-shape values; #3536 concerns BrowserWindow configuration recovery; #3963 concerns guarded module-factory completion; #4202 concerns native-binding re-export classification. This fixture has no export-return shape, BrowserWindow configuration, module factory, early return, or native addon. Correcting those behaviors does not correct the assignment-test truthiness shown here. NOT_ENOUGH_INFO: the first affected revision and last known good revision are unknown. MCP parity, published-package behavior, other platforms, real-application frequency and downstream tracing were not tested. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A valid exposed bridge member produces a wrong machine-readable candidate-path graph and omits its else path. This is a consequential analysis error rather than wording or cleanup. The observed failure is bounded to the authored fixture, and both raw IPC observations and source evidence survive; prevalence in real applications and downstream tracing consequences were not tested. - Workaround status: unverified - Workaround basis: The retained raw IPC observations and source ranges permit manual inspection, but no supported option or unchanged-input workflow was verified to return the corrected contextual call paths. Replacing the subtraction with a plain assignment is a control that changes the selected source, not a demonstrated workaround for the original 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#1652
No description provided.