[PR #6748] [MERGED] fix(ghidra): report kernel OOM kills as a memory constraint #7405

Closed
opened 2026-11-01 21:47:38 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/6748
Author: @akshatgg
Created: 10/23/2026
Status: ✅ Merged
Merged: 10/23/2026
Merged by: @morluto

Base: main ← Head: fix/ghidra-sigkill-oom-kill


📝 Commits (2)

  • 0899f08 fix(ghidra): report kernel OOM kills as a memory constraint
  • cbefe13 fix(ghidra): scope OOM-kill attribution to what the cgroup proves

📊 Changes

14 files changed (+781 additions, -9 deletions)

View changed files

📝 docs/binary-diagnostics.md (+25 -0)
📝 src/ghidra/GhidraClient.ts (+49 -7)
📝 src/ghidra/GhidraClientTypes.ts (+3 -0)
📝 src/ghidra/GhidraDiagnostics.test.ts (+27 -0)
📝 src/ghidra/GhidraDiagnostics.ts (+3 -0)
📝 src/ghidra/GhidraProvider.behavior.test.ts (+108 -0)
📝 src/ghidra/GhidraProviderClient.ts (+73 -0)
➕ src/process/LinuxCgroupMemory.test.ts (+146 -0)
➕ src/process/LinuxCgroupMemory.ts (+198 -0)
📝 src/process/ProviderProcess.ts (+8 -0)
📝 src/process/ProviderProcessOutput.test.ts (+40 -0)
📝 tests/boundary/providers/ghidra/ghidraClient.fixture.ts (+2 -0)
📝 tests/boundary/providers/ghidra/ghidraOomRetention.test.ts (+89 -2)
📝 tests/fixtures/fakeGhidra.mjs (+10 -0)

📄 Description

Summary

Fixes #5859. When the kernel or a container OOM killer ends Ghidra with SIGKILL, Ghidra prints no java.lang.OutOfMemoryError, so REA fell through to the generic execution_failure ("Run rea doctor … repeating the unchanged request is not expected to succeed"). Doctor finds nothing, and the obvious next knob (GHIDRA_HEADLESS_MAXMEM) does not help, because the limit also covers Ghidra's native decompiler processes.

Problem and expected behavior

  • Trigger: a process or protocol failure where Ghidra exited with exit_signal: "SIGKILL" before REA began stopping it, with no OOME line in the operation's output.
  • Before: execution_failure with rea doctor advice, although the diagnostics already held exit_signal: "SIGKILL".
  • After:
    • Linux with cgroup v2, and the oom_kill count of REA's cgroup rose during the run → resource_constraint, resource: "memory", reported_limits.cgroup_memory_max (REA's group memory.max: bytes, "max", or null when unread) plus configured_jvm_max_heap. The message says the kernel OOM killer ended a process in REA's cgroup or one of its child groups; the advice covers the whole process tree instead of a larger Java heap.
    • Otherwise (no cgroup v2 memory group, macOS, or count unchanged) → still execution_failure, but the message names the SIGKILL as a possible out-of-memory kill and says how to check, instead of pointing at rea doctor.
    • When the session had already completed an operation, both keep the existing "Call open_binary for the same target…" reopen guidance.

Change and scope

  • src/process/ProviderProcess.ts: the supervisor records stopRequested when stop() begins, before any signal it sends (including group cleanup), and exposes it in snapshot(). Ghidra diagnostics carry it as stop_requested: true. A SIGKILL with that flag came from REA and is never projected as an outside kill.
  • src/process/LinuxCgroupMemory.ts (new): parsers for /proc/self/cgroup (the 0:: row), cgroup2 rows of /proc/self/mountinfo, memory.events (oom_kill) and memory.max. The group directory is joined from the root of the cgroup2 mount that covers it, so subtree mounts resolve correctly. Kernel files are read to EOF within fixed budgets (16 KiB; 4 MiB for mountinfo). Returns undefined off Linux, under cgroup v1, or when files are unreadable.
  • GhidraClient: reads REA's own cgroup once before launch; after a process/protocol failure (after the output drain) or a startup failure, if exit_signal is SIGKILL and no stop was requested, reads again and attaches memory_cgroup: { path, oom_kill_before, oom_kill_after, memory_max }. The reader is injectable through GhidraClientOptions.readMemoryCgroup for tests.
  • GhidraProviderClient: projectExternalSigkill runs after the existing OOME projection (an observed OOME stays primary). Only process and protocol failures qualify; start (launcher) failures can include REA's own rollback, and timeouts, cancellation and remote failures are unchanged. It composes the existing exitedProviderMessage reopen guidance and, for the unattributed case, the existing incomplete-output sentence.
  • Attribution limit, stated in the message and docs: oom_kill is hierarchical by cgroup (the kernel charges the victim's memcg and every ancestor), so a rise proves an OOM kill in REA's cgroup or a child group, not in REA's process tree. If REA shares its cgroup with unrelated processes, a coincident kill of one of them plus an unrelated SIGKILL of Ghidra would still be reported as a memory constraint. Whether a rise in a shared group should stay a confirmed resource_constraint is a maintainer decision; this PR keeps it and words it exactly.
  • Not included: a knob for Ghidra decompiler parallelism (the issue suggests a separate feature request); reading limits on parent groups (documented).

Follow the contribution scope.
Unresolved scope, authority, side effects, or required verification block
integration. Link the required maintainer scope decision for new providers or
capabilities involving target execution, injection, mutation, or protection
recovery; ordinary fixes within existing boundaries may state "not applicable".
Draft PRs may leave the scope decision pending and the readiness and merge checklists
unchecked. Maintainers can help resolve open questions.

Contract and boundary impact

  • Semantic owner and earliest changed stage: process supervision (ProviderProcessSupervisor.snapshot().stopRequested), then Ghidra diagnostics (GhidraClient/GhidraDiagnostics), then Ghidra failure projection (GhidraProviderClient).
  • Contribution scope fit and linked maintainer decision: bug fix within the existing Ghidra provider; not applicable. Open question for maintainers: shared-cgroup attribution (see Change and scope).
  • CLI and MCP/tool-catalog contract: no schema change. The error code changes from execution_failure to the existing resource_constraint only when the cgroup count rose; new optional diagnostics.memory_cgroup, diagnostics.stop_requested and reported_limits.cgroup_memory_max values.
  • Provider, bridge, target-format, or platform compatibility: attribution on Linux cgroup v2 only; other platforms get the improved SIGKILL message only.
  • Evidence, artifact, provenance, or reconstruction contract: none.
  • Target selection, authority, upstream behavior, and cleanup details (if applicable): reads /proc/self/cgroup, /proc/self/mountinfo, and memory.events and memory.max of REA's own group; no writes. Cleanup is unchanged; the supervisor only records that a stop began.
  • Generated metadata (docs/public/product-catalog.json), package, or installation impact (CI generates and validates ignored catalogs and skills/; edit authored instructions in .agents/skills/reverse-engineer-anything/ and never commit build outputs): none.

Expected effects (check known effects; use documentation-only on its own).
Unchecked items do not establish absence; leave uncertainties visible.

  • Read existing files or captured data.
  • Launch or attach to a process.
  • Inject instrumentation or run scripts/code.
  • Change process memory, registers, files, or configuration.
  • Contact network services.
  • Recover protected code or work around protections.
  • Documentation or usability only; no target interaction.
  • Not sure yet; investigation or maintainer guidance needed.

Evidence and regression coverage

  • Tests added or updated:

    • src/process/LinuxCgroupMemory.test.ts: parsers (v1 rows ignored, duplicate/unsafe values rejected, oom/oom_group_kill not confused with oom_kill, escaped mountinfo paths), directory resolution for full and subtree mounts, and the reader against fixture trees (subtree mount, "max" vs unreadable limit, uncovered path, v1, non-Linux).
    • src/process/ProviderProcessOutput.test.ts: an external SIGKILL has no stopRequested; the owner's stop sets it before its cleanup sends SIGKILL.
    • src/ghidra/GhidraDiagnostics.test.ts: stop_requested is carried into exit diagnostics only when set.
    • src/ghidra/GhidraProvider.behavior.test.ts → "Ghidra SIGKILL projection": process/protocol OOM kill → resource_constraint with cgroup wording; "max" and null limits reported as observed; no cgroup evidence or unchanged count → execution_failure naming SIGKILL; stop_requested → not attributed; observed OOME stays primary; timeout/cancelled/remote unchanged.
    • tests/boundary/providers/ghidra/ghidraOomRetention.test.ts with new sigkill_tools and sigkill_after_first_tool fixture modes: real GhidraClient, process supervision and projection with a fake JVM that SIGKILLs itself, on a first call and on an established session (reopen guidance kept).
  • Base reproduction or other evidence:

    • On the base revision the new projection tests and the first-call boundary tests fail with the issue's exact message (Analysis could not complete. Run \rea doctor` …`).

    • Real kernel check of the reader at cbefe13 (Docker on Colima, cgroup v2, a child allocating until killed): under --memory 256m → {"before":{"path":"/","oomKills":0,"memoryMax":268435456},"exit":{"code":null,"signal":"SIGKILL"},"after":{"path":"/","oomKills":1,"memoryMax":268435456}}; with no container limit (VM-wide OOM) → memoryMax: "max", oomKills 0 → 1. The container's cgroup2 mountinfo row: … / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw,nsdelegate,memory_recursiveprot.

    • End-to-end with real Ghidra on the issue's public target (cargo-auditable.exe v0.7.7, sha256 fc35804634fc76ddec6bd2d7dde2bdd2c0b18e7fe2e7bc96d223751f6a72cefc): Colima (4 CPUs, cgroup v2), Ubuntu 24.04, Temurin 21, Node 24.22.1, Ghidra 12.1.4 with natives built by support/gradle/gradlew buildNatives for linux_arm_64. Container --memory 1500m --memory-swap 1500m, GHIDRA_HEADLESS_MAXMEM=1G, rea analyze /targets/cargo-auditable.exe --format json --full-output, using npm pack tarballs of the base revision and of this branch:

      Build oom_kill before → after Result
      base (3c5c887) 0 → 1 execution_failure, "Run `rea doctor` …", exit_signal: "SIGKILL", failure kind process ("Ghidra exited before bridge startup")
      this PR (cbefe13) 0 → 1 resource_constraint, resource: "memory", reported_limits: {cgroup_memory_max: 1572864000, configured_jvm_max_heap: "1G"}, no stop_requested

      In one earlier run of the first commit (0899f08), the kernel chose REA's own Node process as the victim (exit 137), so no REA result was produced; victim selection is the kernel's and outside this change. The run at cbefe13 killed Ghidra on the first attempt.

    • Control at 0899f08 (same image, no memory limit): rea analyze completes (exit 0, Evidence for sha256 fc358046…, 433 s).

  • User-visible CLI/MCP output (if applicable): from the end-to-end run at cbefe13 (trimmed):

    {"code":"resource_constraint",
     "message":"Ghidra was killed by SIGKILL while the oom_kill count of REA's cgroup / rose from 0 to 1, so the kernel OOM killer ended a process in that cgroup or one of its child groups. The Java heap need not have been full.",
     "remediation":{"action":"A memory limit covers Ghidra's native decompiler processes as well as its Java heap, and their number follows the available CPUs, so a larger GHIDRA_HEADLESS_MAXMEM does not help. Raise the container or host memory limit, or allow fewer CPUs with a longer REA_GHIDRA_STARTUP_TIMEOUT_MS. Then retry."},
     "details":{"reported_limits":{"cgroup_memory_max":1572864000,"configured_jvm_max_heap":"1G"},
       "diagnostics":{"exit_signal":"SIGKILL","failure_kind":"process","memory_cgroup":{"path":"/","oom_kill_before":0,"oom_kill_after":1,"memory_max":1572864000}}}}
    
  • Remaining proof gaps: not run on Windows or on cgroup v1 hosts (attribution is unavailable there by design and the SIGKILL message still applies); macOS jetsam kills only get the unattributed message. A real subtree cgroup2 mount was not exercised (fixture-tested only). The concurrent close/cancel race that motivated stop_requested is covered by supervisor and projection tests, not reproduced with real Ghidra. The "fewer CPUs with a longer startup deadline" advice follows the issue's runs E and F and was not measured here. The no-limit control was not repeated at cbefe13.

For evidence-bearing changes:

  • Observed, derived, and inferred claims remain distinguishable.
  • Artifact identity, source provenance, and failed attempts remain preserved.
  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

All at head cbefe13 unless noted, Node 24.22.1:

  • npx vitest run src/ghidra src/process tests/boundary/providers/ghidra (after npm run build) — 91 files passed; 1681 tests passed, 8 skipped.
  • npm run typecheck — passed.
  • npm run lint — 0 errors; warning count unchanged from base (2327).
  • npm run format:check — passed.
  • npm run docs:check — passed.
  • npm run verify:test-discovery — passed (2131 test files).
  • npm run verify:module-boundaries and npm run verify:architecture-guards — passed.
  • npm run metadata:check — passed.
  • npm run knip (at 0899f08) — only the two #…-analysis-worker unresolved imports that also appear on an unmodified checkout.

Compatibility, safety, and release

  • Breaking changes or migration steps: none. Callers matching on execution_failure for this case see resource_constraint only when the cgroup count rose.
  • Real Hopper/Ghidra, browser, or OS coverage: real Ghidra 12.1.4 in a Linux arm64 container (above). Not run on Windows or macOS hosts.
  • Package or release metadata impact: none.
  • Security, privacy, process, or containment review: read-only access to the process's own cgroup and mount tables; the reported path is REA's cgroup path, already visible to the process. The supervisor change only adds a flag; no signal or cleanup behavior changes.

Author readiness

Human owner: @akshatgg

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/6748 **Author:** [@akshatgg](https://github.com/akshatgg) **Created:** 10/23/2026 **Status:** ✅ Merged **Merged:** 10/23/2026 **Merged by:** [@morluto](https://github.com/morluto) **Base:** `main` ← **Head:** `fix/ghidra-sigkill-oom-kill` --- ### 📝 Commits (2) - [`0899f08`](https://github.com/morluto/rea/commit/0899f086d8a7010404f8718f2e2bf77edd13f87f) fix(ghidra): report kernel OOM kills as a memory constraint - [`cbefe13`](https://github.com/morluto/rea/commit/cbefe13866e19a0658475d13286459573d340096) fix(ghidra): scope OOM-kill attribution to what the cgroup proves ### 📊 Changes **14 files changed** (+781 additions, -9 deletions) <details> <summary>View changed files</summary> 📝 `docs/binary-diagnostics.md` (+25 -0) 📝 `src/ghidra/GhidraClient.ts` (+49 -7) 📝 `src/ghidra/GhidraClientTypes.ts` (+3 -0) 📝 `src/ghidra/GhidraDiagnostics.test.ts` (+27 -0) 📝 `src/ghidra/GhidraDiagnostics.ts` (+3 -0) 📝 `src/ghidra/GhidraProvider.behavior.test.ts` (+108 -0) 📝 `src/ghidra/GhidraProviderClient.ts` (+73 -0) ➕ `src/process/LinuxCgroupMemory.test.ts` (+146 -0) ➕ `src/process/LinuxCgroupMemory.ts` (+198 -0) 📝 `src/process/ProviderProcess.ts` (+8 -0) 📝 `src/process/ProviderProcessOutput.test.ts` (+40 -0) 📝 `tests/boundary/providers/ghidra/ghidraClient.fixture.ts` (+2 -0) 📝 `tests/boundary/providers/ghidra/ghidraOomRetention.test.ts` (+89 -2) 📝 `tests/fixtures/fakeGhidra.mjs` (+10 -0) </details> ### 📄 Description ## Summary Fixes #5859. When the kernel or a container OOM killer ends Ghidra with `SIGKILL`, Ghidra prints no `java.lang.OutOfMemoryError`, so REA fell through to the generic `execution_failure` ("Run `rea doctor` … repeating the unchanged request is not expected to succeed"). Doctor finds nothing, and the obvious next knob (`GHIDRA_HEADLESS_MAXMEM`) does not help, because the limit also covers Ghidra's native decompiler processes. ## Problem and expected behavior - Trigger: a process or protocol failure where Ghidra exited with `exit_signal: "SIGKILL"` before REA began stopping it, with no OOME line in the operation's output. - Before: `execution_failure` with `rea doctor` advice, although the diagnostics already held `exit_signal: "SIGKILL"`. - After: - Linux with cgroup v2, and the `oom_kill` count of REA's cgroup rose during the run → `resource_constraint`, `resource: "memory"`, `reported_limits.cgroup_memory_max` (REA's group `memory.max`: bytes, `"max"`, or `null` when unread) plus `configured_jvm_max_heap`. The message says the kernel OOM killer ended a process in REA's cgroup or one of its child groups; the advice covers the whole process tree instead of a larger Java heap. - Otherwise (no cgroup v2 memory group, macOS, or count unchanged) → still `execution_failure`, but the message names the SIGKILL as a possible out-of-memory kill and says how to check, instead of pointing at `rea doctor`. - When the session had already completed an operation, both keep the existing "Call open_binary for the same target…" reopen guidance. ## Change and scope - `src/process/ProviderProcess.ts`: the supervisor records `stopRequested` when `stop()` begins, before any signal it sends (including group cleanup), and exposes it in `snapshot()`. Ghidra diagnostics carry it as `stop_requested: true`. A SIGKILL with that flag came from REA and is never projected as an outside kill. - `src/process/LinuxCgroupMemory.ts` (new): parsers for `/proc/self/cgroup` (the `0::` row), cgroup2 rows of `/proc/self/mountinfo`, `memory.events` (`oom_kill`) and `memory.max`. The group directory is joined from the root of the cgroup2 mount that covers it, so subtree mounts resolve correctly. Kernel files are read to EOF within fixed budgets (16 KiB; 4 MiB for mountinfo). Returns `undefined` off Linux, under cgroup v1, or when files are unreadable. - `GhidraClient`: reads REA's own cgroup once before launch; after a process/protocol failure (after the output drain) or a startup failure, if `exit_signal` is `SIGKILL` and no stop was requested, reads again and attaches `memory_cgroup: { path, oom_kill_before, oom_kill_after, memory_max }`. The reader is injectable through `GhidraClientOptions.readMemoryCgroup` for tests. - `GhidraProviderClient`: `projectExternalSigkill` runs after the existing OOME projection (an observed OOME stays primary). Only `process` and `protocol` failures qualify; `start` (launcher) failures can include REA's own rollback, and timeouts, cancellation and remote failures are unchanged. It composes the existing `exitedProviderMessage` reopen guidance and, for the unattributed case, the existing incomplete-output sentence. - Attribution limit, stated in the message and docs: `oom_kill` is hierarchical by cgroup (the kernel charges the victim's memcg and every ancestor), so a rise proves an OOM kill in REA's cgroup or a child group, not in REA's process tree. If REA shares its cgroup with unrelated processes, a coincident kill of one of them plus an unrelated SIGKILL of Ghidra would still be reported as a memory constraint. Whether a rise in a shared group should stay a confirmed `resource_constraint` is a maintainer decision; this PR keeps it and words it exactly. - Not included: a knob for Ghidra decompiler parallelism (the issue suggests a separate feature request); reading limits on parent groups (documented). Follow the [contribution scope](https://github.com/morluto/rea/blob/main/CONTRIBUTING.md#contribution-scope). Unresolved scope, authority, side effects, or required verification block integration. Link the required maintainer scope decision for new providers or capabilities involving target execution, injection, mutation, or protection recovery; ordinary fixes within existing boundaries may state "not applicable". Draft PRs may leave the scope decision pending and the readiness and merge checklists unchecked. Maintainers can help resolve open questions. ## Contract and boundary impact - Semantic owner and earliest changed stage: process supervision (`ProviderProcessSupervisor.snapshot().stopRequested`), then Ghidra diagnostics (`GhidraClient`/`GhidraDiagnostics`), then Ghidra failure projection (`GhidraProviderClient`). - Contribution scope fit and linked maintainer decision: bug fix within the existing Ghidra provider; not applicable. Open question for maintainers: shared-cgroup attribution (see Change and scope). - CLI and MCP/tool-catalog contract: no schema change. The error code changes from `execution_failure` to the existing `resource_constraint` only when the cgroup count rose; new optional `diagnostics.memory_cgroup`, `diagnostics.stop_requested` and `reported_limits.cgroup_memory_max` values. - Provider, bridge, target-format, or platform compatibility: attribution on Linux cgroup v2 only; other platforms get the improved SIGKILL message only. - Evidence, artifact, provenance, or reconstruction contract: none. - Target selection, authority, upstream behavior, and cleanup details (if applicable): reads `/proc/self/cgroup`, `/proc/self/mountinfo`, and `memory.events` and `memory.max` of REA's own group; no writes. Cleanup is unchanged; the supervisor only records that a stop began. - Generated metadata (`docs/public/product-catalog.json`), package, or installation impact (CI generates and validates ignored catalogs and `skills/`; edit authored instructions in `.agents/skills/reverse-engineer-anything/` and never commit build outputs): none. Expected effects (check known effects; use documentation-only on its own). Unchecked items do not establish absence; leave uncertainties visible. - [x] Read existing files or captured data. - [ ] Launch or attach to a process. - [ ] Inject instrumentation or run scripts/code. - [ ] Change process memory, registers, files, or configuration. - [ ] Contact network services. - [ ] Recover protected code or work around protections. - [ ] Documentation or usability only; no target interaction. - [ ] Not sure yet; investigation or maintainer guidance needed. ## Evidence and regression coverage - Tests added or updated: - `src/process/LinuxCgroupMemory.test.ts`: parsers (v1 rows ignored, duplicate/unsafe values rejected, `oom`/`oom_group_kill` not confused with `oom_kill`, escaped mountinfo paths), directory resolution for full and subtree mounts, and the reader against fixture trees (subtree mount, `"max"` vs unreadable limit, uncovered path, v1, non-Linux). - `src/process/ProviderProcessOutput.test.ts`: an external SIGKILL has no `stopRequested`; the owner's stop sets it before its cleanup sends SIGKILL. - `src/ghidra/GhidraDiagnostics.test.ts`: `stop_requested` is carried into exit diagnostics only when set. - `src/ghidra/GhidraProvider.behavior.test.ts` → "Ghidra SIGKILL projection": process/protocol OOM kill → `resource_constraint` with cgroup wording; `"max"` and `null` limits reported as observed; no cgroup evidence or unchanged count → `execution_failure` naming SIGKILL; `stop_requested` → not attributed; observed OOME stays primary; timeout/cancelled/remote unchanged. - `tests/boundary/providers/ghidra/ghidraOomRetention.test.ts` with new `sigkill_tools` and `sigkill_after_first_tool` fixture modes: real `GhidraClient`, process supervision and projection with a fake JVM that SIGKILLs itself, on a first call and on an established session (reopen guidance kept). - Base reproduction or other evidence: - On the base revision the new projection tests and the first-call boundary tests fail with the issue's exact message (`Analysis could not complete. Run \`rea doctor\` …`). - Real kernel check of the reader at cbefe13 (Docker on Colima, cgroup v2, a child allocating until killed): under `--memory 256m` → `{"before":{"path":"/","oomKills":0,"memoryMax":268435456},"exit":{"code":null,"signal":"SIGKILL"},"after":{"path":"/","oomKills":1,"memoryMax":268435456}}`; with no container limit (VM-wide OOM) → `memoryMax: "max"`, `oomKills` 0 → 1. The container's cgroup2 mountinfo row: `… / /sys/fs/cgroup ro,nosuid,nodev,noexec,relatime - cgroup2 cgroup rw,nsdelegate,memory_recursiveprot`. - End-to-end with real Ghidra on the issue's public target (`cargo-auditable.exe` v0.7.7, sha256 `fc35804634fc76ddec6bd2d7dde2bdd2c0b18e7fe2e7bc96d223751f6a72cefc`): Colima (4 CPUs, cgroup v2), Ubuntu 24.04, Temurin 21, Node 24.22.1, Ghidra 12.1.4 with natives built by `support/gradle/gradlew buildNatives` for linux_arm_64. Container `--memory 1500m --memory-swap 1500m`, `GHIDRA_HEADLESS_MAXMEM=1G`, `rea analyze /targets/cargo-auditable.exe --format json --full-output`, using `npm pack` tarballs of the base revision and of this branch: | Build | `oom_kill` before → after | Result | |---|---|---| | base (3c5c887) | 0 → 1 | `execution_failure`, "Run \`rea doctor\` …", `exit_signal: "SIGKILL"`, failure kind `process` ("Ghidra exited before bridge startup") | | this PR (cbefe13) | 0 → 1 | `resource_constraint`, `resource: "memory"`, `reported_limits: {cgroup_memory_max: 1572864000, configured_jvm_max_heap: "1G"}`, no `stop_requested` | In one earlier run of the first commit (0899f08), the kernel chose REA's own Node process as the victim (exit 137), so no REA result was produced; victim selection is the kernel's and outside this change. The run at cbefe13 killed Ghidra on the first attempt. - Control at 0899f08 (same image, no memory limit): `rea analyze` completes (exit 0, Evidence for sha256 `fc358046…`, 433 s). - User-visible CLI/MCP output (if applicable): from the end-to-end run at cbefe13 (trimmed): ```json {"code":"resource_constraint", "message":"Ghidra was killed by SIGKILL while the oom_kill count of REA's cgroup / rose from 0 to 1, so the kernel OOM killer ended a process in that cgroup or one of its child groups. The Java heap need not have been full.", "remediation":{"action":"A memory limit covers Ghidra's native decompiler processes as well as its Java heap, and their number follows the available CPUs, so a larger GHIDRA_HEADLESS_MAXMEM does not help. Raise the container or host memory limit, or allow fewer CPUs with a longer REA_GHIDRA_STARTUP_TIMEOUT_MS. Then retry."}, "details":{"reported_limits":{"cgroup_memory_max":1572864000,"configured_jvm_max_heap":"1G"}, "diagnostics":{"exit_signal":"SIGKILL","failure_kind":"process","memory_cgroup":{"path":"/","oom_kill_before":0,"oom_kill_after":1,"memory_max":1572864000}}}} ``` - Remaining proof gaps: not run on Windows or on cgroup v1 hosts (attribution is unavailable there by design and the SIGKILL message still applies); macOS jetsam kills only get the unattributed message. A real subtree cgroup2 mount was not exercised (fixture-tested only). The concurrent close/cancel race that motivated `stop_requested` is covered by supervisor and projection tests, not reproduced with real Ghidra. The "fewer CPUs with a longer startup deadline" advice follows the issue's runs E and F and was not measured here. The no-limit control was not repeated at cbefe13. For evidence-bearing changes: - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed All at head cbefe13 unless noted, Node 24.22.1: - `npx vitest run src/ghidra src/process tests/boundary/providers/ghidra` (after `npm run build`) — 91 files passed; 1681 tests passed, 8 skipped. - `npm run typecheck` — passed. - `npm run lint` — 0 errors; warning count unchanged from base (2327). - `npm run format:check` — passed. - `npm run docs:check` — passed. - `npm run verify:test-discovery` — passed (2131 test files). - `npm run verify:module-boundaries` and `npm run verify:architecture-guards` — passed. - `npm run metadata:check` — passed. - `npm run knip` (at 0899f08) — only the two `#…-analysis-worker` unresolved imports that also appear on an unmodified checkout. ## Compatibility, safety, and release - Breaking changes or migration steps: none. Callers matching on `execution_failure` for this case see `resource_constraint` only when the cgroup count rose. - Real Hopper/Ghidra, browser, or OS coverage: real Ghidra 12.1.4 in a Linux arm64 container (above). Not run on Windows or macOS hosts. - Package or release metadata impact: none. - Security, privacy, process, or containment review: read-only access to the process's own cgroup and mount tables; the reported path is REA's cgroup path, already visible to the process. The supervisor change only adds a flag; no signal or cleanup behavior changes. ## Author readiness Human owner: @akshatgg - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [ ] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [x] User-visible CLI/MCP changes include representative output. - [ ] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-01 21:47:38 +00:00
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#7405
No description provided.