[GH-ISSUE #3990] [Bug] Headless IDA string search returns success after an embedded provider failure #1587

Closed
opened 2026-10-16 21:07:29 +00:00 by bsakaguchi · 3 comments
Owner

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

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

The headless IDA adapter returns a successful empty search_strings result when a modern string page contains an explicit producer error, an empty match list, and a terminal cursor. The caller receives the failure only inside raw Evidence. This can make a failed scan look like a complete search that found no matching defined strings. The local reproduction exercised the adapter behavior through the public stdio MCP process using a simulated provider, without an IDA engine.

Reproduction

Use the baseline in the REA version and Runtime and platform fields with its pinned Node.js, installed dependencies, and built runtime. From the repository root, save the following script in a disposable directory outside the checkout and run all three scenarios. The script starts only local stdio processes, supplies a minimal ELF fixture, isolates HOME and the provider workspace, and closes each admitted session.

PROBE_DIR="$(mktemp -d)"
cat > "$PROBE_DIR/string-search-probe.mjs" <<'PROBE'
import { spawn } from 'node:child_process';
import { createInterface } from 'node:readline';
import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises';
import { dirname, join, resolve } from 'node:path';
import { fileURLToPath } from 'node:url';
import { tmpdir } from 'node:os';

const self = fileURLToPath(import.meta.url);
const scenario = process.argv[3] ?? 'embedded';
if (process.argv[2] === '--upstream') {
  const names = ['idb_open', 'idb_list', 'idb_close', 'server_health', 'list_funcs', 'lookup_funcs', 'find_regex', 'decompile', 'disasm', 'callees', 'xrefs_to'];
  let active;
  const reply = (id, result) => process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id, result }) + '\n');
  for await (const line of createInterface({ input: process.stdin })) {
    const request = JSON.parse(line);
    if (request.id === undefined) continue;
    if (request.method === 'initialize') {
      reply(request.id, { protocolVersion: request.params.protocolVersion, capabilities: { tools: {} }, serverInfo: { name: 'search-fixture', version: '1' } });
      continue;
    }
    if (request.method === 'tools/list') {
      reply(request.id, { tools: names.map(name => ({ name, inputSchema: { type: 'object' } })) });
      continue;
    }
    const { name, arguments: args } = request.params;
    let value;
    if (name === 'idb_open') {
      active = { session_id: args.preferred_session_id, input_path: args.input_path, owned: true, backend: 'worker', is_active: true };
      value = { success: true, session: active };
    } else if (name === 'idb_list') value = { sessions: active ? [active] : [] };
    else if (name === 'idb_close') {
      value = { success: true, session_id: active.session_id, saved: false, owned: true, backend: 'worker' };
      active = undefined;
    } else if (name === 'server_health') {
      value = { status: 'ok', input_path: active.input_path, idb_path: active.input_path + '.i64', module: 'sample.elf', imagebase: '0x1000', auto_analysis_ready: true };
    } else if (name === 'find_regex') {
      value = { matches: [], cursor: { done: true } };
      if (scenario !== 'clean') value.error = 'Search fixture failed';
    } else throw new Error('Unexpected request: ' + name);
    reply(request.id, { ...(name === 'find_regex' && scenario === 'envelope' ? { isError: true } : {}), content: [{ type: 'text', text: JSON.stringify(value) }] });
  }
} else {
  const repo = resolve(process.argv[2] ?? '.');
  const work = await mkdtemp(join(process.env.PROBE_ROOT ?? tmpdir(), 'search-probe-'));
  await mkdir(join(work, 'home'));
  const target = join(work, 'sample.elf');
  const bytes = Buffer.alloc(64);
  bytes.set([127, 69, 76, 70, 2, 1, 1]);
  bytes.writeUInt16LE(62, 18); bytes.writeUInt32LE(1, 20); bytes.writeUInt16LE(64, 52);
  await writeFile(target, bytes);
  const config = join(work, 'upstream.json');
  await writeFile(config, JSON.stringify({ command: process.execPath, args: [self, '--upstream', scenario], mode: 'headless', timeoutMs: 2000, workspaceRoot: work }));
  const child = spawn(process.execPath, [join(repo, 'scripts/rea.mjs'), 'mcp'], {
    cwd: repo,
    env: { ...process.env, HOME: join(work, 'home'), REA_ANALYSIS_PROVIDER: 'ida', REA_IDA_MCP_CONFIG: config, REA_MCP_INPUT_SCHEMA_PROFILE: 'compact', REA_MCP_OUTPUT_SCHEMA_PROFILE: 'off', REA_LOG_LEVEL: 'silent' },
    stdio: ['pipe', 'pipe', 'pipe']
  });
  let stderr = '';
  child.stderr.on('data', data => { stderr += data; });
  const exited = new Promise(resolve => child.once('exit', (code, signal) => resolve({ code, signal })));
  const pending = new Map();
  let sequence = 0;
  createInterface({ input: child.stdout }).on('line', line => {
    const response = JSON.parse(line);
    pending.get(response.id)?.(response);
    pending.delete(response.id);
  });
  const send = (method, params) => new Promise(resolve => {
    const id = ++sequence;
    pending.set(id, resolve);
    child.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n');
  });
  const call = async (name, args) => (await send('tools/call', { name, arguments: args })).result;
  const deadline = setTimeout(() => { child.kill('SIGTERM'); process.exitCode = 1; }, 20000);
  try {
    await send('initialize', { protocolVersion: '2025-11-25', capabilities: {}, clientInfo: { name: 'search-probe', version: '1' } });
    child.stdin.write(JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' }) + '\n');
    const opened = await call('open_binary', { path: target, provider_id: 'ida' });
    if (opened.isError) throw new Error(JSON.stringify(opened));
    const result = await call('search_strings', { pattern: 'needle' });
    const evidence = result.structuredContent;
    const observation = evidence?.raw_result?.observations?.find(item => item.tool === 'find_regex');
    console.log(JSON.stringify({ scenario, isError: result.isError === true, normalized_result: evidence?.normalized_result, upstream_result: observation?.result, upstream_query: observation ? { ...observation.arguments, database: '<owned-session>' } : undefined, error_content: result.isError ? result.content : undefined }, null, 2));
    const closed = await call('close_binary', {});
    console.log(JSON.stringify({ scenario, cleanup_success: closed.isError !== true }));
  } finally {
    child.stdin.end();
    const status = await exited;
    clearTimeout(deadline);
    console.log(JSON.stringify({ scenario, process_exit: status, stderr_empty: stderr.length === 0 }));
    await rm(work, { recursive: true, force: true });
  }
}
PROBE
PROBE_ROOT="$PROBE_DIR" node "$PROBE_DIR/string-search-probe.mjs" "$PWD" embedded
PROBE_ROOT="$PROBE_DIR" node "$PROBE_DIR/string-search-probe.mjs" "$PWD" clean
PROBE_ROOT="$PROBE_DIR" node "$PROBE_DIR/string-search-probe.mjs" "$PWD" envelope
rm -r "$PROBE_DIR"

The embedded case puts {"matches":[],"cursor":{"done":true},"error":"Search fixture failed"} into the producer's normal MCP JSON text result. The clean case omits only the embedded error property. The envelope case retains the failed page and sets upstream MCP isError: true. All cases call the public search_strings tool with {"pattern":"needle"} after successful open_binary admission.

Expected behavior

An explicit failed string scan must not become a successful complete empty search. REA must preserve the meaningful producer failure and return the documented MCP error response. A page that successfully completes with no defined strings must still return a successful empty result. Completeness applies to provider-defined string objects; it does not establish that the binary contains no other text.

Impact and scope

A bounded provider failure becomes a false complete empty search result. This can mislead callers that use normalized results for string discovery. The demonstrated trigger is a simulated error-bearing page; real-provider frequency and wider effects are unknown.

The observed successful Evidence retains the explicit producer error in raw_result.observations, so callers can inspect it and treat this scan as failed instead of accepting the empty result. That preserves the failure signal but does not recover results from the failed producer scan.

REA version

rea 6.5.0 at source commit 4692bb3330

Runtime and platform

Node 24.18.0; macOS arm64

Target and artifact context

Synthetic x86_64 ELF header for admission; local simulated headless IDA database-supervisor over stdio.

Interface

MCP

Earliest failure stage

Native analysis or reconstruction

Relevant output or evidence

  • Expected source: src/contracts/officialToolContracts.ts:198, the search_strings contract, promises deterministic complete results over provider-defined strings. AGENTS.md, Architecture and Cleanup, requires preserving meaningful failure reasons from unknown boundary values. docs/mcp-contracts.md:72 requires execution failures to return isError: true with a canonical diagnostic. docs/ida-provider.md, Give REA a target for headless analysis, documents the database-supervisor profile used here.
  • Failure source: The executed public MCP reproduction above returns successful structured Evidence despite the explicit failed producer observation. Source inspection of src/ida/IdaOperationRunner.ts:322 and src/ida/IdaProtocolValues.ts:138 shows that the modern page parser consumes the matches and terminal cursor without retaining the embedded error field. src/ida/IdaMcpConnection.ts, decodeIdaToolResult, handles envelope-level errors, which agrees with the passing envelope-error control.

The runtime result is observed. The parser explanation is source-derived. This report establishes adapter handling of the given provider-boundary payload, not compatibility behavior of any particular installed IDA server release.

Each scenario ran once. The embedded failure returned the following public result summary:

{
  "scenario": "embedded",
  "isError": false,
  "normalized_result": [],
  "upstream_result": {
    "matches": [],
    "cursor": { "done": true },
    "error": "Search fixture failed"
  },
  "upstream_query": {
    "pattern": "(?s).*",
    "offset": 0,
    "limit": 100,
    "database": "<owned-session>"
  }
}

The clean control returned isError: false and an empty normalized result. The envelope-error control returned isError: true with canonical error code unreadable_output and the producer failure in its diagnostic, without a successful structured result. In all three cases, close_binary succeeded, the REA process exited with code 0, and stderr was empty. The producer database identifier above is a generic replacement for the generated owned-session value.

Typed outcome or recovery expected

Run the embedded-error scenario and require a failed public MCP response that preserves Search fixture failed, rather than successful empty structured Evidence. Run the clean control and require its successful empty result to remain valid. Run the envelope-error control and require its existing failure behavior to remain intact. Require successful session close and process exit in each case. Real-provider compatibility requires a separate engine run and is outside this reproduction.

Analysis or suspected cause

The source references above explain the observed behavior. Runtime claims are limited to the explicitly described simulation boundaries.

Additional context

Runtime claims above describe the supplied local reproduction at the baseline commit. Real-provider frequency remains unmeasured.

The relevant failure code is unchanged on current origin/main fc712b3750.

Originally created by @coygeek on GitHub (Oct 15, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3990 ### 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 The headless IDA adapter returns a successful empty `search_strings` result when a modern string page contains an explicit producer error, an empty match list, and a terminal cursor. The caller receives the failure only inside raw Evidence. This can make a failed scan look like a complete search that found no matching defined strings. The local reproduction exercised the adapter behavior through the public stdio MCP process using a simulated provider, without an IDA engine. ### Reproduction Use the baseline in the REA version and Runtime and platform fields with its pinned Node.js, installed dependencies, and built runtime. From the repository root, save the following script in a disposable directory outside the checkout and run all three scenarios. The script starts only local stdio processes, supplies a minimal ELF fixture, isolates HOME and the provider workspace, and closes each admitted session. ```sh PROBE_DIR="$(mktemp -d)" cat > "$PROBE_DIR/string-search-probe.mjs" <<'PROBE' import { spawn } from 'node:child_process'; import { createInterface } from 'node:readline'; import { mkdtemp, mkdir, writeFile, rm } from 'node:fs/promises'; import { dirname, join, resolve } from 'node:path'; import { fileURLToPath } from 'node:url'; import { tmpdir } from 'node:os'; const self = fileURLToPath(import.meta.url); const scenario = process.argv[3] ?? 'embedded'; if (process.argv[2] === '--upstream') { const names = ['idb_open', 'idb_list', 'idb_close', 'server_health', 'list_funcs', 'lookup_funcs', 'find_regex', 'decompile', 'disasm', 'callees', 'xrefs_to']; let active; const reply = (id, result) => process.stdout.write(JSON.stringify({ jsonrpc: '2.0', id, result }) + '\n'); for await (const line of createInterface({ input: process.stdin })) { const request = JSON.parse(line); if (request.id === undefined) continue; if (request.method === 'initialize') { reply(request.id, { protocolVersion: request.params.protocolVersion, capabilities: { tools: {} }, serverInfo: { name: 'search-fixture', version: '1' } }); continue; } if (request.method === 'tools/list') { reply(request.id, { tools: names.map(name => ({ name, inputSchema: { type: 'object' } })) }); continue; } const { name, arguments: args } = request.params; let value; if (name === 'idb_open') { active = { session_id: args.preferred_session_id, input_path: args.input_path, owned: true, backend: 'worker', is_active: true }; value = { success: true, session: active }; } else if (name === 'idb_list') value = { sessions: active ? [active] : [] }; else if (name === 'idb_close') { value = { success: true, session_id: active.session_id, saved: false, owned: true, backend: 'worker' }; active = undefined; } else if (name === 'server_health') { value = { status: 'ok', input_path: active.input_path, idb_path: active.input_path + '.i64', module: 'sample.elf', imagebase: '0x1000', auto_analysis_ready: true }; } else if (name === 'find_regex') { value = { matches: [], cursor: { done: true } }; if (scenario !== 'clean') value.error = 'Search fixture failed'; } else throw new Error('Unexpected request: ' + name); reply(request.id, { ...(name === 'find_regex' && scenario === 'envelope' ? { isError: true } : {}), content: [{ type: 'text', text: JSON.stringify(value) }] }); } } else { const repo = resolve(process.argv[2] ?? '.'); const work = await mkdtemp(join(process.env.PROBE_ROOT ?? tmpdir(), 'search-probe-')); await mkdir(join(work, 'home')); const target = join(work, 'sample.elf'); const bytes = Buffer.alloc(64); bytes.set([127, 69, 76, 70, 2, 1, 1]); bytes.writeUInt16LE(62, 18); bytes.writeUInt32LE(1, 20); bytes.writeUInt16LE(64, 52); await writeFile(target, bytes); const config = join(work, 'upstream.json'); await writeFile(config, JSON.stringify({ command: process.execPath, args: [self, '--upstream', scenario], mode: 'headless', timeoutMs: 2000, workspaceRoot: work })); const child = spawn(process.execPath, [join(repo, 'scripts/rea.mjs'), 'mcp'], { cwd: repo, env: { ...process.env, HOME: join(work, 'home'), REA_ANALYSIS_PROVIDER: 'ida', REA_IDA_MCP_CONFIG: config, REA_MCP_INPUT_SCHEMA_PROFILE: 'compact', REA_MCP_OUTPUT_SCHEMA_PROFILE: 'off', REA_LOG_LEVEL: 'silent' }, stdio: ['pipe', 'pipe', 'pipe'] }); let stderr = ''; child.stderr.on('data', data => { stderr += data; }); const exited = new Promise(resolve => child.once('exit', (code, signal) => resolve({ code, signal }))); const pending = new Map(); let sequence = 0; createInterface({ input: child.stdout }).on('line', line => { const response = JSON.parse(line); pending.get(response.id)?.(response); pending.delete(response.id); }); const send = (method, params) => new Promise(resolve => { const id = ++sequence; pending.set(id, resolve); child.stdin.write(JSON.stringify({ jsonrpc: '2.0', id, method, params }) + '\n'); }); const call = async (name, args) => (await send('tools/call', { name, arguments: args })).result; const deadline = setTimeout(() => { child.kill('SIGTERM'); process.exitCode = 1; }, 20000); try { await send('initialize', { protocolVersion: '2025-11-25', capabilities: {}, clientInfo: { name: 'search-probe', version: '1' } }); child.stdin.write(JSON.stringify({ jsonrpc: '2.0', method: 'notifications/initialized' }) + '\n'); const opened = await call('open_binary', { path: target, provider_id: 'ida' }); if (opened.isError) throw new Error(JSON.stringify(opened)); const result = await call('search_strings', { pattern: 'needle' }); const evidence = result.structuredContent; const observation = evidence?.raw_result?.observations?.find(item => item.tool === 'find_regex'); console.log(JSON.stringify({ scenario, isError: result.isError === true, normalized_result: evidence?.normalized_result, upstream_result: observation?.result, upstream_query: observation ? { ...observation.arguments, database: '<owned-session>' } : undefined, error_content: result.isError ? result.content : undefined }, null, 2)); const closed = await call('close_binary', {}); console.log(JSON.stringify({ scenario, cleanup_success: closed.isError !== true })); } finally { child.stdin.end(); const status = await exited; clearTimeout(deadline); console.log(JSON.stringify({ scenario, process_exit: status, stderr_empty: stderr.length === 0 })); await rm(work, { recursive: true, force: true }); } } PROBE PROBE_ROOT="$PROBE_DIR" node "$PROBE_DIR/string-search-probe.mjs" "$PWD" embedded PROBE_ROOT="$PROBE_DIR" node "$PROBE_DIR/string-search-probe.mjs" "$PWD" clean PROBE_ROOT="$PROBE_DIR" node "$PROBE_DIR/string-search-probe.mjs" "$PWD" envelope rm -r "$PROBE_DIR" ``` The `embedded` case puts `{"matches":[],"cursor":{"done":true},"error":"Search fixture failed"}` into the producer's normal MCP JSON text result. The `clean` case omits only the embedded error property. The `envelope` case retains the failed page and sets upstream MCP `isError: true`. All cases call the public `search_strings` tool with `{"pattern":"needle"}` after successful `open_binary` admission. ### Expected behavior An explicit failed string scan must not become a successful complete empty search. REA must preserve the meaningful producer failure and return the documented MCP error response. A page that successfully completes with no defined strings must still return a successful empty result. Completeness applies to provider-defined string objects; it does not establish that the binary contains no other text. ### Impact and scope A bounded provider failure becomes a false complete empty search result. This can mislead callers that use normalized results for string discovery. The demonstrated trigger is a simulated error-bearing page; real-provider frequency and wider effects are unknown. The observed successful Evidence retains the explicit producer error in `raw_result.observations`, so callers can inspect it and treat this scan as failed instead of accepting the empty result. That preserves the failure signal but does not recover results from the failed producer scan. ### REA version rea 6.5.0 at source commit 4692bb33300c7cb77b095b6cc0c33da861c1b860 ### Runtime and platform Node 24.18.0; macOS arm64 ### Target and artifact context Synthetic x86_64 ELF header for admission; local simulated headless IDA database-supervisor over stdio. ### Interface MCP ### Earliest failure stage Native analysis or reconstruction ### Relevant output or evidence - Expected source: `src/contracts/officialToolContracts.ts:198`, the `search_strings` contract, promises deterministic complete results over provider-defined strings. `AGENTS.md`, Architecture and Cleanup, requires preserving meaningful failure reasons from unknown boundary values. `docs/mcp-contracts.md:72` requires execution failures to return `isError: true` with a canonical diagnostic. `docs/ida-provider.md`, Give REA a target for headless analysis, documents the database-supervisor profile used here. - Failure source: The executed public MCP reproduction above returns successful structured Evidence despite the explicit failed producer observation. Source inspection of `src/ida/IdaOperationRunner.ts:322` and `src/ida/IdaProtocolValues.ts:138` shows that the modern page parser consumes the matches and terminal cursor without retaining the embedded error field. `src/ida/IdaMcpConnection.ts`, `decodeIdaToolResult`, handles envelope-level errors, which agrees with the passing envelope-error control. The runtime result is observed. The parser explanation is source-derived. This report establishes adapter handling of the given provider-boundary payload, not compatibility behavior of any particular installed IDA server release. Each scenario ran once. The embedded failure returned the following public result summary: ```json { "scenario": "embedded", "isError": false, "normalized_result": [], "upstream_result": { "matches": [], "cursor": { "done": true }, "error": "Search fixture failed" }, "upstream_query": { "pattern": "(?s).*", "offset": 0, "limit": 100, "database": "<owned-session>" } } ``` The clean control returned `isError: false` and an empty normalized result. The envelope-error control returned `isError: true` with canonical error code `unreadable_output` and the producer failure in its diagnostic, without a successful structured result. In all three cases, `close_binary` succeeded, the REA process exited with code 0, and stderr was empty. The producer database identifier above is a generic replacement for the generated owned-session value. ### Typed outcome or recovery expected Run the embedded-error scenario and require a failed public MCP response that preserves `Search fixture failed`, rather than successful empty structured Evidence. Run the clean control and require its successful empty result to remain valid. Run the envelope-error control and require its existing failure behavior to remain intact. Require successful session close and process exit in each case. Real-provider compatibility requires a separate engine run and is outside this reproduction. ### Analysis or suspected cause The source references above explain the observed behavior. Runtime claims are limited to the explicitly described simulation boundaries. ### Additional context Runtime claims above describe the supplied local reproduction at the baseline commit. Real-provider frequency remains unmeasured. The relevant failure code is unchanged on current `origin/main` fc712b3750cbe16755c4f78c1f90fdf09fbaca31.
Author
Owner

@XinwangTao commented on GitHub (Oct 15, 2026):

Confirmed: an explicit producer error plus terminal cursor is not an empty result set — surfacing it as a successful search_strings makes a failed scan indistinguishable from a genuine no-match. The adapter should propagate the provider error into the call result (or at least an explicit diagnostic), keeping the empty-list meaning reserved for real complete searches.

<!-- gh-comment-id:6186849569 --> @XinwangTao commented on GitHub (Oct 15, 2026): Confirmed: an explicit producer error plus terminal cursor is not an empty result set — surfacing it as a successful search_strings makes a failed scan indistinguishable from a genuine no-match. The adapter should propagate the provider error into the call result (or at least an explicit diagnostic), keeping the empty-list meaning reserved for real complete searches.
Author
Owner

@yankikucuk commented on GitHub (Oct 15, 2026):

Working on this. Reproduced with your stdio probe: the embedded case returned successful empty Evidence. The shared string page schema now reads the optional error and rejects a page that reports one with a protocol error naming the producer failure (headless and native alike; the native schema reuses it). The probe now returns isError: true with unreadable_output and "Search fixture failed" retained in the diagnostic, the clean control stays an empty success, the envelope control is unchanged, and close and exit succeed in all three. Draft PR follows shortly.

<!-- gh-comment-id:6191515655 --> @yankikucuk commented on GitHub (Oct 15, 2026): Working on this. Reproduced with your stdio probe: the embedded case returned successful empty Evidence. The shared string page schema now reads the optional `error` and rejects a page that reports one with a protocol error naming the producer failure (headless and native alike; the native schema reuses it). The probe now returns `isError: true` with `unreadable_output` and "Search fixture failed" retained in the diagnostic, the clean control stays an empty success, the envelope control is unchanged, and close and exit succeed in all three. Draft PR follows shortly.
Author
Owner

@morluto commented on GitHub (Oct 15, 2026):

Fixed on main in bfacfbac3. Explicit IDA string-page errors are retained by the shared contract and rejected once by the runner before successful matches can be returned. Raw replies remain available as partial Evidence.

Public stdio MCP replay passed embedded-error, clean-empty and error-envelope cases, with successful cleanup and zero exit status. The 61 client tests also passed, including a failed later page with matches and prior observations retained. These are simulated-producer checks; no real IDA engine reproduction is claimed. Closing the adapter false-success defect.

Implementation qualification passed: CI at d9aa06d93. This verifies the implementation revision; the later PCAP-only batch has its own checks.

<!-- gh-comment-id:6201321400 --> @morluto commented on GitHub (Oct 15, 2026): Fixed on main in bfacfbac3. Explicit IDA string-page errors are retained by the shared contract and rejected once by the runner before successful matches can be returned. Raw replies remain available as partial Evidence. Public stdio MCP replay passed embedded-error, clean-empty and error-envelope cases, with successful cleanup and zero exit status. The 61 client tests also passed, including a failed later page with matches and prior observations retained. These are simulated-producer checks; no real IDA engine reproduction is claimed. Closing the adapter false-success defect. Implementation qualification passed: [CI at d9aa06d93](https://github.com/morluto/rea/actions/runs/38797211865). This verifies the implementation revision; the later PCAP-only batch has its own checks.
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#1587
No description provided.