[GH-ISSUE #5848] [Bug] EVM reply-budget refusals become unreadable_output with doctor guidance #3546

Closed
opened 2026-10-31 00:41:42 +00:00 by bsakaguchi · 2 comments
Owner

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

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

An executed service/provider boundary test supplied the exact valid JSON refusal emitted by the owned EVM worker when its complete reply exceeds 16 MiB:

{"ok":false,"reason":"output-limit","message":"Complete interface evidence exceeds the 16 MiB reply budget."}

EvmInterfaceService.inspect returned an error that projected to code: "unreadable_output", category: "execution_failure", and retryable: false. Both the top-level message and remediation described an unreadable result and recommended rea doctor. The specific budget explanation survived in details.reason, but there was no details.reported_limits.

This was an injected worker-reply reproduction on macOS, with Linux admission facts emulated using the repository's existing EVM test pattern. It did not execute EVMole or generate a naturally oversized reply. A malformed-JSON control returned the same error code. Both cases completed owned cleanup.

Reproduction

From the source revision below with dependencies installed, add the following temporary test beside the EVM provider as src/evm/repro.test.ts, then run:

npm run test:local -- src/evm/repro.test.ts

The filename is normalized here; this is the executed probe body. Use an isolated writable TMPDIR, HOME, and XDG_CONFIG_HOME for the run. The test writes a small selected hex carrier, admits the provider using simulated Linux host/limit facts, writes the worker refusal through an injected launcher, and supervises a real inert Node child after restoring the actual host identity. It asserts the current incorrect classification; it does not assert the proposed correction.

import { readFile, writeFile, access } from "node:fs/promises";
import { expect, it, vi } from "vitest";
import { EvmoleInterfaceProvider } from "./EvmoleInterfaceProvider.js";
import { EvmInterfaceService } from "../application/evm/EvmInterfaceService.js";
import { projectAnalysisError } from "../domain/analysisErrorProjection.js";
import { spawnOwnedProviderProcess } from "../process/ProviderProcess.js";
import { createTestWorkspace, removeTestWorkspace } from "../../tests/support/workspace/workspaceFixture.js";

vi.mock("node:fs/promises", async (importOriginal) => {
  const fs = await importOriginal<typeof import("node:fs/promises")>();
  return {
    ...fs,
    readFile: async (...args: Parameters<typeof fs.readFile>) =>
      args[0] === "/proc/self/limits"
        ? [
            "Max address space  unlimited  unlimited  bytes",
            "Max cpu time  unlimited  unlimited  seconds",
            "Max file size  unlimited  unlimited  bytes",
          ].join("\n")
        : fs.readFile(...args),
  };
});

it.each(["output-limit", "malformed"] as const)(
  "projects the worker reply: %s",
  async (scenario) => {
    const platform = Object.getOwnPropertyDescriptor(process, "platform")!;
    const arch = Object.getOwnPropertyDescriptor(process, "arch")!;
    const restoreHost = () => {
      Object.defineProperty(process, "platform", platform);
      Object.defineProperty(process, "arch", arch);
    };
    const workspace = await createTestWorkspace("rea-verify-evm-reply-");
    const path = await workspace.write("selected.hex", "0x60006000f3\n");
    let runtimePath = "";
    const provider = new EvmoleInterfaceProvider(
      { ...workspace.environment, REA_EVM_PRLIMIT_COMMAND: process.execPath },
      async (spawn) => {
        restoreHost(); // Existing EVM cleanup fixture pattern: use real process identity.
        runtimePath = spawn.cwd!;
        const request = JSON.parse(await readFile(spawn.arguments.at(-1)!, "utf8"));
        await writeFile(request.reply_path, scenario === "malformed" ? "invalid JSON" : JSON.stringify({
          ok: false,
          reason: "output-limit",
          message: "Complete interface evidence exceeds the 16 MiB reply budget.",
        }));
        return spawnOwnedProviderProcess({
          ...spawn,
          command: process.execPath,
          arguments: ["-e", "process.exitCode = 0"],
        });
      },
    );
    try {
      Object.defineProperty(process, "platform", { ...platform, value: "linux" });
      Object.defineProperty(process, "arch", { ...arch, value: "x64" });
      const result = await new EvmInterfaceService(provider).inspect({ path, encoding: "hex" });
      if (result.ok) throw new Error("Expected worker refusal");
      const projected = projectAnalysisError(result.error);
      expect(projected).toMatchObject({
        code: "unreadable_output",
        category: "execution_failure",
        retryable: false,
      });
      expect(projected.remediation.action).toContain("rea doctor");
      expect(projected.details?.["reported_limits"]).toBeUndefined();
      if (scenario === "output-limit") {
        expect(projected.details?.["reason"]).toBe("Complete interface evidence exceeds the 16 MiB reply budget.");
        console.log("EXPLICIT_REFUSAL", JSON.stringify(projected));
      }
      expect(result.error.cleanupIncomplete).toBe(false);
      await expect(access(runtimePath)).rejects.toMatchObject({ code: "ENOENT" });
    } finally {
      restoreHost();
      await provider.close();
      await removeTestWorkspace(workspace.root);
    }
  },
);

Remove the temporary test afterward. The executed run passed both cases. Public CLI/MCP calls and real Linux resource enforcement were not run.

Expected behavior

Treat the explicit complete-reply allowance refusal as a resource constraint, report the known 16777216-byte allowance in limit details, and explain that the fixed worker allowance cannot be raised by running doctor or changing the delivery channel. Preserve the existing non-retryable outcome and the specific reason.

The EVM resource contract documents separate complete-reply and diagnostic-output budgets. The error algebra defines resource errors to include bounded admission refusals; output errors describe failed output parsing. This worker reply is valid structured refusal data.

Impact and scope

P2: the refusal loses its machine-readable resource classification and numerical allowance, and recommends a diagnostic action that cannot change this fixed limit. The textual reason remains available for callers that inspect details. The injected test observed no partial success, retained cleanup, or evidence corruption.

Source tracing shows the same provider serves CLI and MCP. The service returns provider failures unchanged, and both the CLI command and MCP handler call that service. Production admission requires Linux x64. Actual frequency, a real carrier exceeding the budget within the other worker limits, and downstream agent behavior are unknown. No workaround preserving the same complete analysis was verified.

REA version

rea-agents 6.7.0; source baseline a5f982de8d; locked dependencies and prebuilt runtime supplied, source test executed.

Runtime and platform

Node v24.22.0, npm 11.19.1; Darwin 27.0.0 arm64; Linux x64 admission and inherited-limit facts simulated, real host restored for Node process supervision.

Target and artifact context

Small local flat EVM hex carrier containing 0x60006000f3; worker reply injected, no real oversized artifact or chain interaction.

Interface

Other

Earliest failure stage

Transport or result projection

Relevant output or evidence

The focused source test printed this actual projection:

{"code":"unreadable_output","category":"execution_failure","message":"Analysis returned an unreadable result. Run `rea doctor` and review the reported details; repeating the unchanged request is not expected to succeed.","retryable":false,"remediation":{"action":"Analysis returned an unreadable result. Run `rea doctor` and review the reported details; repeating the unchanged request is not expected to succeed."},"details":{"operation":"inspect_evm_interface","reason":"Complete interface evidence exceeds the 16 MiB reply budget.","captured_output":{"stdout":"","stderr":"","truncated":false}}}

Trimmed runner output:

Test Files  1 passed (1)
     Tests  2 passed (2)

The worker's budget branch produces the tested protocol reply. The provider's reply schema accepts it, then the reason-specific branch constructs AnalysisOutputError. The code mapping and generic presentation explain the observed outcome.

Typed outcome or recovery expected

resource_constraint, non-retryable, with the numerical complete-reply allowance retained and fixed-allowance guidance. This refusal occurs before a successful Evidence result exists; summary delivery or retained views do not provide a completed oversized result to retrieve.

Analysis or suspected cause

Source-derived: the provider successfully parses the worker's explicit refusal and then selects the generic output-parsing error class. That class supplies the wrong stable code and generic recovery. Classify this reason at the provider boundary while preserving captured output and existing cleanup ownership. The independent reply-file read guard and diagnostic-capture overflow are outside this report's reproduced scope.

Additional context

Read-only searches of open and closed issues and PRs using EVM/output-limit, reply-budget, unreadable_output, and the exact refusal phrase found no matching report or fix. #5808 proposes a broader audit of residual fixed-allowance recovery without reporting this EVM refusal. No known-good version or regression range was established. The existing EVM cleanup tests supplied the host-admission simulation pattern; this report makes no real EVMole or util-linux verification claim.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/5848 ### 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 An executed service/provider boundary test supplied the exact valid JSON refusal emitted by the owned EVM worker when its complete reply exceeds 16 MiB: ```json {"ok":false,"reason":"output-limit","message":"Complete interface evidence exceeds the 16 MiB reply budget."} ``` `EvmInterfaceService.inspect` returned an error that projected to `code: "unreadable_output"`, `category: "execution_failure"`, and `retryable: false`. Both the top-level message and remediation described an unreadable result and recommended `rea doctor`. The specific budget explanation survived in `details.reason`, but there was no `details.reported_limits`. This was an injected worker-reply reproduction on macOS, with Linux admission facts emulated using the repository's existing EVM test pattern. It did not execute EVMole or generate a naturally oversized reply. A malformed-JSON control returned the same error code. Both cases completed owned cleanup. ### Reproduction From the source revision below with dependencies installed, add the following temporary test beside the EVM provider as `src/evm/repro.test.ts`, then run: ```sh npm run test:local -- src/evm/repro.test.ts ``` The filename is normalized here; this is the executed probe body. Use an isolated writable `TMPDIR`, `HOME`, and `XDG_CONFIG_HOME` for the run. The test writes a small selected hex carrier, admits the provider using simulated Linux host/limit facts, writes the worker refusal through an injected launcher, and supervises a real inert Node child after restoring the actual host identity. It asserts the current incorrect classification; it does not assert the proposed correction. ```ts import { readFile, writeFile, access } from "node:fs/promises"; import { expect, it, vi } from "vitest"; import { EvmoleInterfaceProvider } from "./EvmoleInterfaceProvider.js"; import { EvmInterfaceService } from "../application/evm/EvmInterfaceService.js"; import { projectAnalysisError } from "../domain/analysisErrorProjection.js"; import { spawnOwnedProviderProcess } from "../process/ProviderProcess.js"; import { createTestWorkspace, removeTestWorkspace } from "../../tests/support/workspace/workspaceFixture.js"; vi.mock("node:fs/promises", async (importOriginal) => { const fs = await importOriginal<typeof import("node:fs/promises")>(); return { ...fs, readFile: async (...args: Parameters<typeof fs.readFile>) => args[0] === "/proc/self/limits" ? [ "Max address space unlimited unlimited bytes", "Max cpu time unlimited unlimited seconds", "Max file size unlimited unlimited bytes", ].join("\n") : fs.readFile(...args), }; }); it.each(["output-limit", "malformed"] as const)( "projects the worker reply: %s", async (scenario) => { const platform = Object.getOwnPropertyDescriptor(process, "platform")!; const arch = Object.getOwnPropertyDescriptor(process, "arch")!; const restoreHost = () => { Object.defineProperty(process, "platform", platform); Object.defineProperty(process, "arch", arch); }; const workspace = await createTestWorkspace("rea-verify-evm-reply-"); const path = await workspace.write("selected.hex", "0x60006000f3\n"); let runtimePath = ""; const provider = new EvmoleInterfaceProvider( { ...workspace.environment, REA_EVM_PRLIMIT_COMMAND: process.execPath }, async (spawn) => { restoreHost(); // Existing EVM cleanup fixture pattern: use real process identity. runtimePath = spawn.cwd!; const request = JSON.parse(await readFile(spawn.arguments.at(-1)!, "utf8")); await writeFile(request.reply_path, scenario === "malformed" ? "invalid JSON" : JSON.stringify({ ok: false, reason: "output-limit", message: "Complete interface evidence exceeds the 16 MiB reply budget.", })); return spawnOwnedProviderProcess({ ...spawn, command: process.execPath, arguments: ["-e", "process.exitCode = 0"], }); }, ); try { Object.defineProperty(process, "platform", { ...platform, value: "linux" }); Object.defineProperty(process, "arch", { ...arch, value: "x64" }); const result = await new EvmInterfaceService(provider).inspect({ path, encoding: "hex" }); if (result.ok) throw new Error("Expected worker refusal"); const projected = projectAnalysisError(result.error); expect(projected).toMatchObject({ code: "unreadable_output", category: "execution_failure", retryable: false, }); expect(projected.remediation.action).toContain("rea doctor"); expect(projected.details?.["reported_limits"]).toBeUndefined(); if (scenario === "output-limit") { expect(projected.details?.["reason"]).toBe("Complete interface evidence exceeds the 16 MiB reply budget."); console.log("EXPLICIT_REFUSAL", JSON.stringify(projected)); } expect(result.error.cleanupIncomplete).toBe(false); await expect(access(runtimePath)).rejects.toMatchObject({ code: "ENOENT" }); } finally { restoreHost(); await provider.close(); await removeTestWorkspace(workspace.root); } }, ); ``` Remove the temporary test afterward. The executed run passed both cases. Public CLI/MCP calls and real Linux resource enforcement were not run. ### Expected behavior Treat the explicit complete-reply allowance refusal as a resource constraint, report the known 16777216-byte allowance in limit details, and explain that the fixed worker allowance cannot be raised by running doctor or changing the delivery channel. Preserve the existing non-retryable outcome and the specific reason. The [EVM resource contract](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/docs/evm-bytecode.md#L105-L122) documents separate complete-reply and diagnostic-output budgets. The [error algebra](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/domain/analysisErrorCore.ts#L23-L55) defines resource errors to include bounded admission refusals; [output errors](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/domain/analysisErrorCore.ts#L105-L124) describe failed output parsing. This worker reply is valid structured refusal data. ### Impact and scope P2: the refusal loses its machine-readable resource classification and numerical allowance, and recommends a diagnostic action that cannot change this fixed limit. The textual reason remains available for callers that inspect details. The injected test observed no partial success, retained cleanup, or evidence corruption. Source tracing shows the same provider serves CLI and MCP. The [service returns provider failures unchanged](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/application/evm/EvmInterfaceService.ts#L50-L61), and both the [CLI command](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/cli/evmCommands.ts#L16-L40) and [MCP handler](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/server/registerEvmTools.ts#L30-L50) call that service. Production admission requires Linux x64. Actual frequency, a real carrier exceeding the budget within the other worker limits, and downstream agent behavior are unknown. No workaround preserving the same complete analysis was verified. ### REA version rea-agents 6.7.0; source baseline a5f982de8dbe4b092cc20ab89d492b7e7028b758; locked dependencies and prebuilt runtime supplied, source test executed. ### Runtime and platform Node v24.22.0, npm 11.19.1; Darwin 27.0.0 arm64; Linux x64 admission and inherited-limit facts simulated, real host restored for Node process supervision. ### Target and artifact context Small local flat EVM hex carrier containing 0x60006000f3; worker reply injected, no real oversized artifact or chain interaction. ### Interface Other ### Earliest failure stage Transport or result projection ### Relevant output or evidence The focused source test printed this actual projection: ```json {"code":"unreadable_output","category":"execution_failure","message":"Analysis returned an unreadable result. Run `rea doctor` and review the reported details; repeating the unchanged request is not expected to succeed.","retryable":false,"remediation":{"action":"Analysis returned an unreadable result. Run `rea doctor` and review the reported details; repeating the unchanged request is not expected to succeed."},"details":{"operation":"inspect_evm_interface","reason":"Complete interface evidence exceeds the 16 MiB reply budget.","captured_output":{"stdout":"","stderr":"","truncated":false}}} ``` Trimmed runner output: ```text Test Files 1 passed (1) Tests 2 passed (2) ``` The [worker's budget branch](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/evm/EvmoleInterfaceWorker.ts#L84-L92) produces the tested protocol reply. The provider's [reply schema](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/evm/EvmoleInterfaceProvider.ts#L179-L184) accepts it, then the [reason-specific branch](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/evm/EvmoleInterfaceProvider.ts#L594-L597) constructs `AnalysisOutputError`. The [code mapping](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/domain/analysisErrorProjection.ts#L242-L254) and [generic presentation](https://github.com/morluto/rea/blob/a5f982de8dbe4b092cc20ab89d492b7e7028b758/src/domain/analysisErrorPresentation.ts#L374-L376) explain the observed outcome. ### Typed outcome or recovery expected `resource_constraint`, non-retryable, with the numerical complete-reply allowance retained and fixed-allowance guidance. This refusal occurs before a successful Evidence result exists; summary delivery or retained views do not provide a completed oversized result to retrieve. ### Analysis or suspected cause Source-derived: the provider successfully parses the worker's explicit refusal and then selects the generic output-parsing error class. That class supplies the wrong stable code and generic recovery. Classify this reason at the provider boundary while preserving captured output and existing cleanup ownership. The independent reply-file read guard and diagnostic-capture overflow are outside this report's reproduced scope. ### Additional context Read-only searches of open and closed issues and PRs using EVM/output-limit, reply-budget, unreadable_output, and the exact refusal phrase found no matching report or fix. [#5808](https://github.com/morluto/rea/issues/5808) proposes a broader audit of residual fixed-allowance recovery without reporting this EVM refusal. No known-good version or regression range was established. The existing EVM cleanup tests supplied the host-admission simulation pattern; this report makes no real EVMole or util-linux verification claim.
Author
Owner

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

Agreed that this needs a typed boundary rather than unreadable_output. The worker already emits a precise output-limit reason with the 16 MiB budget in the message, so the projection should carry that through as a resource-constraint style error (with reported_limits populated), exactly like the binary-plist depth cases. Collapsing it into the same code as a malformed-JSON reply hides a deterministic, actionable limit behind "unreadable" and sends users to doctor for something the worker already diagnosed. Keeping the two scenarios distinct in tests would prevent that regression.

<!-- gh-comment-id:6311797697 --> @XinwangTao commented on GitHub (Oct 21, 2026): Agreed that this needs a typed boundary rather than unreadable_output. The worker already emits a precise `output-limit` reason with the 16 MiB budget in the message, so the projection should carry that through as a resource-constraint style error (with reported_limits populated), exactly like the binary-plist depth cases. Collapsing it into the same code as a malformed-JSON reply hides a deterministic, actionable limit behind "unreadable" and sends users to doctor for something the worker already diagnosed. Keeping the two scenarios distinct in tests would prevent that regression.
Author
Owner

@coygeek commented on GitHub (Oct 24, 2026):

Checked resolved on main at d87e73f2db.

The Linux x64 structured output-limit worker-reply case now produces a resource constraint; the malformed-reply and success controls remain distinct. The projection check passed with resource_constraint, retryable: false, maximum_reply_bytes: 16777216, fixed-report recovery and no doctor advice. The exact owned-worker fixtures and the allowance helper regression passed; a naturally oversized EVMole result was not generated.

Relevant implementation: EvmoleInterfaceProvider.ts, EvmoleFailures.ts.

Closing as completed on main.

<!-- gh-comment-id:6380750697 --> @coygeek commented on GitHub (Oct 24, 2026): Checked resolved on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. The Linux x64 structured `output-limit` worker-reply case now produces a resource constraint; the malformed-reply and success controls remain distinct. The projection check passed with `resource_constraint`, `retryable: false`, `maximum_reply_bytes: 16777216`, fixed-report recovery and no doctor advice. The exact owned-worker fixtures and the allowance helper regression passed; a naturally oversized EVMole result was not generated. Relevant implementation: [EvmoleInterfaceProvider.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/evm/EvmoleInterfaceProvider.ts#L624), [EvmoleFailures.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/evm/EvmoleFailures.ts#L39). Closing as completed on main.
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#3546
No description provided.