[GH-ISSUE #1518] [Bug] Ghidra boundary verifier fails on duplicate output schema ids since #1433 #519

Closed
opened 2026-10-15 18:25:47 +00:00 by bsakaguchi · 1 comment
Owner

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

TL;DR: Since #1433, npm run verify:ghidra:cross-format fails on main before Ghidra does any real work. Tools with the same output schema now share one $id, and the verifier compiles every schema into one Ajv instance, which refuses a second schema with the same id. A one-line change in the verifier makes the lane pass again.

Before submitting

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

Area

Native analysis or decompilation

Observed behavior

On main 983b070d4, the lane exits 1 after about 28 s:

Error: schema with key or id "urn:rea:tool-output:sha256:8d99d0ac…" already exists
    at scripts/lib/real-ghidra-boundaries.mjs:43:35

It dies inside the first fixture, debug, an x86-64 ELF. Only one Ghidra session starts, so no AArch64, PE or Mach-O import happens.

Reproduction

  1. Install Ghidra 12.1.4 and a JDK 21, and set GHIDRA_INSTALL_DIR and JAVA_HOME.
  2. On main, run npm ci, npm run build, then npm run verify:ghidra:cross-format.

Expected behavior

The lane runs all seven fixtures. With the change below, it does: all seven pass in 373 s, with cleanup: complete.

Impact and scope

#1433 gives each self-contained output schema a $id made from a hash of its content, in src/contracts/toolSchemaMetadata.ts. So tools with identical output schemas share one id. Of 139 tools, 118 ids are distinct, and 8 groups share one. One group is address_name, comment, inline_comment and procedure_pseudo_code.

scripts/lib/real-ghidra-boundaries.mjs then calls ajv.compile(tool.outputSchema) for every tool on one Ajv instance. Ajv 8 throws on the second schema with an id it already holds.

git log -S'urn:rea:tool-output' finds only #1433 (dc5af6d68). My run on 08ec2bf8, before #1433, got past this check.

Since the id is a hash of the whole schema, a shared id means an identical schema. So the verifier can reuse the compiled check:

validators.set(
  tool.name,
  (tool.outputSchema?.$id && ajv.getSchema(tool.outputSchema.$id)) ||
    ajv.compile(tool.outputSchema),
);

scripts/lib/real-hopper-boundaries.mjs compiles schemas the same way, so the Hopper lane likely fails too. I could not run Hopper here.

Interface

Other (the verify:ghidra:* scripts)

Earliest failure stage

Request parsing or validation

Not run

Edit: both lanes and the Windows question ran, in the #1527 check. All three lanes fail on main and pass with the fix. Still not run: Hopper, macOS and Windows.

  • verify:ghidra and verify:ghidra:aarch64-jump-table on main. Both run the debug fixture first, so I expect the same error, but I did not run them. Both fail on main with the same error.
  • The Hopper lane. Hopper is not on this box.
  • Whether the Windows Ghidra workflow uses this check. It does not.
  • macOS and Windows.

Found while checking #1382 after it merged.

Test setup

🤖 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/1518 **TL;DR:** Since #1433, `npm run verify:ghidra:cross-format` fails on main before Ghidra does any real work. Tools with the same output schema now share one `$id`, and the verifier compiles every schema into one Ajv instance, which refuses a second schema with the same id. A one-line change in the verifier makes the lane pass again. ### 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 Native analysis or decompilation ### Observed behavior On main `983b070d4`, the lane exits 1 after about 28 s: ``` Error: schema with key or id "urn:rea:tool-output:sha256:8d99d0ac…" already exists at scripts/lib/real-ghidra-boundaries.mjs:43:35 ``` It dies inside the first fixture, `debug`, an x86-64 ELF. Only one Ghidra session starts, so no AArch64, PE or Mach-O import happens. ### Reproduction 1. Install Ghidra 12.1.4 and a JDK 21, and set `GHIDRA_INSTALL_DIR` and `JAVA_HOME`. 2. On main, run `npm ci`, `npm run build`, then `npm run verify:ghidra:cross-format`. ### Expected behavior The lane runs all seven fixtures. With the change below, it does: all seven pass in 373 s, with `cleanup: complete`. ### Impact and scope #1433 gives each self-contained output schema a `$id` made from a hash of its content, in `src/contracts/toolSchemaMetadata.ts`. So tools with identical output schemas share one id. Of 139 tools, 118 ids are distinct, and 8 groups share one. One group is `address_name`, `comment`, `inline_comment` and `procedure_pseudo_code`. `scripts/lib/real-ghidra-boundaries.mjs` then calls `ajv.compile(tool.outputSchema)` for every tool on one Ajv instance. Ajv 8 throws on the second schema with an id it already holds. `git log -S'urn:rea:tool-output'` finds only #1433 (`dc5af6d68`). My run on `08ec2bf8`, before #1433, got past this check. Since the id is a hash of the whole schema, a shared id means an identical schema. So the verifier can reuse the compiled check: ```js validators.set( tool.name, (tool.outputSchema?.$id && ajv.getSchema(tool.outputSchema.$id)) || ajv.compile(tool.outputSchema), ); ``` `scripts/lib/real-hopper-boundaries.mjs` compiles schemas the same way, so the Hopper lane likely fails too. I could not run Hopper here. ### Interface Other (the `verify:ghidra:*` scripts) ### Earliest failure stage Request parsing or validation ### Not run Edit: both lanes and the Windows question ran, in [the #1527 check](https://github.com/morluto/rea/pull/1527#issuecomment-6099255452). All three lanes fail on main and pass with the fix. Still not run: Hopper, macOS and Windows. - ~~`verify:ghidra` and `verify:ghidra:aarch64-jump-table` on main. Both run the `debug` fixture first, so I expect the same error, but I did not run them.~~ Both fail on main with the same error. - The Hopper lane. Hopper is not on this box. - ~~Whether the Windows Ghidra workflow uses this check.~~ It does not. - macOS and Windows. Found while checking #1382 after it merged. <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 - [Ghidra](https://github.com/NationalSecurityAgency/ghidra): 12.1.4 ([Apache-2.0](https://github.com/NationalSecurityAgency/ghidra/blob/master/LICENSE)) - JDK: [Temurin 21.0.12.1](https://adoptium.net/) ([GPL-2.0 with Classpath Exception](https://github.com/adoptium/jdk21u/blob/master/LICENSE)) - Main: `983b070d4` </details> 🤖 Generated with Claude Code (Opus 5.5) 🦊
bsakaguchi 2026-10-15 18:25:47 +00:00
Author
Owner

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

Preserving the real-lane outcomes from the now-closed #1543 alongside the fix that landed in #1527, since they complement the registration check recorded there. All runs on Linux (Kali, node 24.19) with the reuse fix applied to the three compile sites:

  • main before the fix reproduced the schema with key or id "urn:rea:tool-output:sha256:8d99d0ac\u2026" already exists throw at real-ghidra-boundaries.mjs:43 before any fixture ran.
  • verify:ghidra:cross-format with the fix: full pass, ok: true, all seven fixtures, catalog_tools: 139, successful_calls: 286, rejected_calls: 52, cli_mcp_parity: true, cleanup: complete.
  • verify:hopper:linux with the fix: the boundary-contract phase compiled the catalog and ran its tool-call verification; the lane then stopped at the pinned demo's long-literal string-typing boundary documented on #341, unrelated to this defect.
  • verify:electron (Electron 34.3.0 under xvfb) with the fix: input and output schemas compiled without the id collision and all four tool-contract probes returned schema-validated results; the lane afterwards hit a WSL-specific process-tree cleanup assertion, unrelated to schemas.
  • Current main past c73cf0ab: the advertised catalog carries no $id on any input schema, and the three output compile sites reuse registered validators, so no duplicate registration remains.
<!-- gh-comment-id:6097403755 --> @BaaRai commented on GitHub (Oct 10, 2026): Preserving the real-lane outcomes from the now-closed #1543 alongside the fix that landed in #1527, since they complement the registration check recorded there. All runs on Linux (Kali, node 24.19) with the reuse fix applied to the three compile sites: - main before the fix reproduced the `schema with key or id "urn:rea:tool-output:sha256:8d99d0ac\u2026" already exists` throw at `real-ghidra-boundaries.mjs:43` before any fixture ran. - `verify:ghidra:cross-format` with the fix: full pass, `ok: true`, all seven fixtures, `catalog_tools: 139`, `successful_calls: 286`, `rejected_calls: 52`, `cli_mcp_parity: true`, `cleanup: complete`. - `verify:hopper:linux` with the fix: the boundary-contract phase compiled the catalog and ran its tool-call verification; the lane then stopped at the pinned demo's long-literal string-typing boundary documented on #341, unrelated to this defect. - `verify:electron` (Electron 34.3.0 under xvfb) with the fix: input and output schemas compiled without the id collision and all four tool-contract probes returned schema-validated results; the lane afterwards hit a WSL-specific process-tree cleanup assertion, unrelated to schemas. - Current main past c73cf0ab: the advertised catalog carries no $id on any input schema, and the three output compile sites reuse registered validators, so no duplicate registration remains.
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#519
No description provided.