[GH-ISSUE #1964] [Bug] trace_call_path reports truncated: true on every IDA trace, and get_call_graph nodes carry provider-wide limitations #652

Closed
opened 2026-10-15 18:26:59 +00:00 by bsakaguchi · 3 comments
Owner

Originally created by @nikolay-dementiev on GitHub (Oct 11, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/1964

I'm working on this and will open a PR.

Before submitting

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

Area

MCP or agent integration

Observed behavior

Since #1906, get_call_graph and trace_call_path copy every limitation of each relationship read into the graph. With a real IDA server every expanded node is therefore reported as limited, because every IDA execution carries the six shared IDA_LIMITATIONS (provider version identity, mutable database, live results, cancellation, worker cleanup, unsupported dossier facets):

  • every get_call_graph node gets those six strings in limitations;
  • trace_call_path sets truncated: true and fills residual_unknowns with entries such as Call relationships for 0x100000470 are limited: Caller cancellation drains the in-flight MCP request before cleanup; it does not prove that IDA engine work was interrupted.

This also happens on x86 targets, where the callee read itself did not change, and on traces that reach their goal over a fully expanded graph. truncated no longer distinguishes a complete trace from an incomplete one.

Reproduction

// hello.c
#include <stdio.h>
int square(int x) { return x * x; }
int main(void) { printf("%d\n", square(7)); return 0; }
clang -O0 -arch x86_64 -o hello_x86 hello.c

MCP calls against the headless IDA profile: open_binary {"path": "<hello_x86>", "provider_id": "ida"}, get_call_graph {"address": "_main", "direction": "forward"}, trace_call_path {"start": "_main", "goal": "_square", "direction": "forward"}.

x86 hello_x86 main before #1906 (c2cf7dcb8) main after (39418ea17)
get_call_graph nodes with limitations 0 of 3 3 of 3, six each
trace_call_path goal_status reached reached
trace_call_path truncated false true
trace_call_path residual_unknowns 0 6

On a small ARM64 program (main calls use_wrapper, puts and printf; use_wrapper calls sum_point) the trace from _main to _sum_point is reached with truncated: true, 14 residual unknowns and seven limitations on each of the five graph nodes (the six shared ones plus the call graph coverage limitation).

Expected behavior

truncated and node limitations reflect limitations of the relationship read itself (call graph coverage, upstream truncation, an unadvertised callgraph), not the limitations every execution of the provider carries. A complete x86 trace that reaches its goal stays truncated: false, as before #1906.

Impact and scope

Every get_call_graph and trace_call_path result through IDA, on every architecture. By reading the code, Hopper (every execution carries its capability limitations, for example Results depend on Hopper's completed static analysis.) and Ghidra (limitationsFor adds a common base to every operation) would be affected the same way; I ran only IDA. main only: rea-agents 6.4.0 does not contain #1906.

REA version

main at 39418ea17.

Runtime and platform

macOS 26.5.2 (arm64), Node 26.3.0; IDA Professional 9.1 (9.1.250226) with idalib; ida-pro-mcp 2.0.0 at c133c3853.

Target and artifact context

The 3-line hello.c above, built with clang -O0 -arch x86_64, and a small ARM64 C program. No private target data.

Interface

MCP

Earliest failure stage

Transport or result projection

Analysis or suspected cause

  • src/ida/IdaSessionClient.ts:213 returns [...IDA_LIMITATIONS, ...runner.limitations] for every execution.
  • EnhancedTools now reads relationships through #observe and attaches all execution.limitations to the get_call_graph node.
  • CallPathTracing.expandAddress adds every execution limitation to residual_unknowns, and projectTrace sets truncated: state.residual.size > 0 (src/application/CallPathTracing.ts:231).
  • The boundary tests cover ARM64 traces, which are expected to be truncated: true, and check node limitations with expect.arrayContaining. No test covers a trace whose relationship reads carry only provider-wide limitations (an x86 target, for example), so the shared limitations pass unnoticed.

Additional context

The non-x86 callee edges from #1906 are correct on real IDA 9.1: _main -> _square, _printf on an ARM64 build of hello.c; _main -> _use_wrapper, _puts, _printf and _use_wrapper -> _sum_point on the ARM64 program above, through callgraph {"max_depth": 0} plus lookup_funcs, both with ida-pro-mcp c133c38 and with the fix proposed in mrexodia/ida-pro-mcp#550. The x86 callee read is unchanged, and npm run verify:ida passes.

Originally created by @nikolay-dementiev on GitHub (Oct 11, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/1964 > I'm working on this and will open a PR. ### Before submitting - [x] I searched the open and closed issues and pull requests for a related report. - [x] I removed credentials, tokens, private paths, and sensitive target data. ### Area MCP or agent integration ### Observed behavior Since #1906, `get_call_graph` and `trace_call_path` copy every limitation of each relationship read into the graph. With a real IDA server every expanded node is therefore reported as limited, because every IDA execution carries the six shared `IDA_LIMITATIONS` (provider version identity, mutable database, live results, cancellation, worker cleanup, unsupported dossier facets): - every `get_call_graph` node gets those six strings in `limitations`; - `trace_call_path` sets `truncated: true` and fills `residual_unknowns` with entries such as `Call relationships for 0x100000470 are limited: Caller cancellation drains the in-flight MCP request before cleanup; it does not prove that IDA engine work was interrupted.` This also happens on x86 targets, where the callee read itself did not change, and on traces that reach their goal over a fully expanded graph. `truncated` no longer distinguishes a complete trace from an incomplete one. ### Reproduction ```c // hello.c #include <stdio.h> int square(int x) { return x * x; } int main(void) { printf("%d\n", square(7)); return 0; } ``` ```sh clang -O0 -arch x86_64 -o hello_x86 hello.c ``` MCP calls against the headless IDA profile: `open_binary {"path": "<hello_x86>", "provider_id": "ida"}`, `get_call_graph {"address": "_main", "direction": "forward"}`, `trace_call_path {"start": "_main", "goal": "_square", "direction": "forward"}`. | x86 `hello_x86` | `main` before #1906 (c2cf7dcb8) | `main` after (39418ea17) | | --- | --- | --- | | `get_call_graph` nodes with `limitations` | 0 of 3 | 3 of 3, six each | | `trace_call_path` `goal_status` | `reached` | `reached` | | `trace_call_path` `truncated` | `false` | `true` | | `trace_call_path` `residual_unknowns` | 0 | 6 | On a small ARM64 program (`main` calls `use_wrapper`, `puts` and `printf`; `use_wrapper` calls `sum_point`) the trace from `_main` to `_sum_point` is reached with `truncated: true`, 14 residual unknowns and seven limitations on each of the five graph nodes (the six shared ones plus the call graph coverage limitation). ### Expected behavior `truncated` and node `limitations` reflect limitations of the relationship read itself (call graph coverage, upstream truncation, an unadvertised `callgraph`), not the limitations every execution of the provider carries. A complete x86 trace that reaches its goal stays `truncated: false`, as before #1906. ### Impact and scope Every `get_call_graph` and `trace_call_path` result through IDA, on every architecture. By reading the code, Hopper (every execution carries its capability limitations, for example `Results depend on Hopper's completed static analysis.`) and Ghidra (`limitationsFor` adds a common base to every operation) would be affected the same way; I ran only IDA. `main` only: rea-agents 6.4.0 does not contain #1906. ### REA version `main` at 39418ea17. ### Runtime and platform macOS 26.5.2 (arm64), Node 26.3.0; IDA Professional 9.1 (9.1.250226) with idalib; ida-pro-mcp 2.0.0 at c133c3853. ### Target and artifact context The 3-line `hello.c` above, built with `clang -O0 -arch x86_64`, and a small ARM64 C program. No private target data. ### Interface MCP ### Earliest failure stage Transport or result projection ### Analysis or suspected cause - `src/ida/IdaSessionClient.ts:213` returns `[...IDA_LIMITATIONS, ...runner.limitations]` for every execution. - `EnhancedTools` now reads relationships through `#observe` and attaches all `execution.limitations` to the `get_call_graph` node. - `CallPathTracing.expandAddress` adds every execution limitation to `residual_unknowns`, and `projectTrace` sets `truncated: state.residual.size > 0` (`src/application/CallPathTracing.ts:231`). - The boundary tests cover ARM64 traces, which are expected to be `truncated: true`, and check node limitations with `expect.arrayContaining`. No test covers a trace whose relationship reads carry only provider-wide limitations (an x86 target, for example), so the shared limitations pass unnoticed. ### Additional context The non-x86 callee edges from #1906 are correct on real IDA 9.1: `_main` -> `_square`, `_printf` on an ARM64 build of `hello.c`; `_main` -> `_use_wrapper`, `_puts`, `_printf` and `_use_wrapper` -> `_sum_point` on the ARM64 program above, through `callgraph {"max_depth": 0}` plus `lookup_funcs`, both with ida-pro-mcp c133c38 and with the fix proposed in mrexodia/ida-pro-mcp#550. The x86 callee read is unchanged, and `npm run verify:ida` passes.
bsakaguchi 2026-10-15 18:26:59 +00:00
Author
Owner

@nikolay-dementiev commented on GitHub (Oct 11, 2026):

The same commit also changed build_call_path (src/domain/callPath.ts): search_scope.exhaustive is now false whenever any expanded analyze_function Evidence carries limitations, and IDA Evidence always carries the shared IDA_LIMITATIONS. On real IDA 9.1 with the x86 hello_x86 from the description, analyze_function Evidence for _main, _square and _printf:

build_call_path main before #1906 (c2cf7dcb8) main after (beb80a07b)
_square -> _main (unreachable) not_found, exhaustive: true, 0 limitations unknown, exhaustive: false, 8 limitations
_main -> _square found, 1 hop, exhaustive: true found, 1 hop, exhaustive: false

So not_found can no longer be established from IDA Evidence. The fix I'm preparing covers get_call_graph and trace_call_path, which read executions directly. build_call_path reads serialized Evidence, where the shared and the read-specific limitations are not distinguishable today, so it is not part of that change.

<!-- gh-comment-id:6114260227 --> @nikolay-dementiev commented on GitHub (Oct 11, 2026): The same commit also changed `build_call_path` (`src/domain/callPath.ts`): `search_scope.exhaustive` is now false whenever any expanded `analyze_function` Evidence carries limitations, and IDA Evidence always carries the shared `IDA_LIMITATIONS`. On real IDA 9.1 with the x86 `hello_x86` from the description, `analyze_function` Evidence for `_main`, `_square` and `_printf`: | `build_call_path` | `main` before #1906 (c2cf7dcb8) | `main` after (beb80a07b) | | --- | --- | --- | | `_square` -> `_main` (unreachable) | `not_found`, `exhaustive: true`, 0 limitations | `unknown`, `exhaustive: false`, 8 limitations | | `_main` -> `_square` | `found`, 1 hop, `exhaustive: true` | `found`, 1 hop, `exhaustive: false` | So `not_found` can no longer be established from IDA Evidence. The fix I'm preparing covers `get_call_graph` and `trace_call_path`, which read executions directly. `build_call_path` reads serialized Evidence, where the shared and the read-specific limitations are not distinguishable today, so it is not part of that change.
Author
Owner

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

Working on the isolated build_call_path follow-up described above, keeping get_call_graph and trace_call_path out of scope. Current main already carries digest-bound relationship_coverage in portable Evidence, so I am checking that boundary rather than inferring completeness from generic limitation strings. The planned regressions use inline Evidence through the public MCP tool; no live IDA verification will be claimed. AI-assisted contribution using Hermes Agent.

<!-- gh-comment-id:6115297328 --> @khanukov commented on GitHub (Oct 11, 2026): Working on the isolated `build_call_path` follow-up described above, keeping `get_call_graph` and `trace_call_path` out of scope. Current main already carries digest-bound `relationship_coverage` in portable Evidence, so I am checking that boundary rather than inferring completeness from generic limitation strings. The planned regressions use inline Evidence through the public MCP tool; no live IDA verification will be claimed. AI-assisted contribution using Hermes Agent.
Author
Owner

@nikolay-dementiev commented on GitHub (Oct 12, 2026):

Verified on real IDA 9.1 with main at c22111eca (includes 9c8511409, 5dfb9d050 and #2001), ida-pro-mcp c133c38, the same binaries as above:

x86 hello_x86 ARM64 program
get_call_graph from _main 3 nodes complete, no node limitations; provider caveats once at the top 5 nodes complete, only the call graph coverage note on each node
trace_call_path to _square / _sum_point reached, truncated: false, no residual unknowns reached, truncated: false, no residual unknowns
build_call_path, unreachable goal not_found, exhaustive: true not_found, exhaustive: true
build_call_path, reachable goal found, 1 hop, exhaustive: true -

The results are the same with ida-pro-mcp including mrexodia/ida-pro-mcp#550. CLI function callees are unchanged, and npm run verify:ida passes.

<!-- gh-comment-id:6121205211 --> @nikolay-dementiev commented on GitHub (Oct 12, 2026): Verified on real IDA 9.1 with `main` at c22111eca (includes 9c8511409, 5dfb9d050 and #2001), ida-pro-mcp c133c38, the same binaries as above: | | x86 `hello_x86` | ARM64 program | | --- | --- | --- | | `get_call_graph` from `_main` | 3 nodes `complete`, no node limitations; provider caveats once at the top | 5 nodes `complete`, only the call graph coverage note on each node | | `trace_call_path` to `_square` / `_sum_point` | `reached`, `truncated: false`, no residual unknowns | `reached`, `truncated: false`, no residual unknowns | | `build_call_path`, unreachable goal | `not_found`, `exhaustive: true` | `not_found`, `exhaustive: true` | | `build_call_path`, reachable goal | `found`, 1 hop, `exhaustive: true` | - | The results are the same with ida-pro-mcp including mrexodia/ida-pro-mcp#550. CLI `function` callees are unchanged, and `npm run verify:ida` passes.
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#652
No description provided.