[GH-ISSUE #4374] [Bug] Kuna failure message keeps empty stderr instead of its fallback #1731

Closed
opened 2026-10-16 21:09:34 +00:00 by bsakaguchi · 1 comment
Owner

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

Before submitting

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

Area

Process capture or runtime observation

Observed behavior

The empty-stderr case returns kuna functions failed with exit code 1: even though its retained cause is Command failed; inspect its exit status and diagnostics. The nonempty-stderr control returns the expected diagnostic tail. Both cases reproduced in the single bounded run.

Reproduction

From the main source baseline below, install locked dependencies with npm ci. Save the following as repro.mts outside the checkout and run it from the checkout with REA_CHECKOUT="$PWD" REA_PROBE_SCRATCH="$(mktemp -d /tmp/rea-repro.XXXXXX)" npx --no-install tsx /path/to/repro.mts. This was executed through source imports and real owned child supervision, with two harmless local shell fixtures. It is a module-boundary reproduction; the compiled CLI/MCP and a real Kuna binary were not run.

import assert from "node:assert/strict";
import {pathToFileURL} from "node:url";
import {resolve,join} from "node:path";
import {mkdir,mkdtemp,writeFile,rm} from "node:fs/promises";
import {createHash} from "node:crypto";
const root=process.env.REA_CHECKOUT!;
const {runKunaCommand}=await import(pathToFileURL(resolve(root,"src/kuna/KunaCommand.ts")).href);
const base=process.env.REA_PROBE_SCRATCH!;
assert.ok(base,"REA_PROBE_SCRATCH must name an isolated scratch directory");
await mkdir(base,{recursive:true});
const scratch=await mkdtemp(join(base,"run-"));
try {
for(const [name,body,tail] of [["empty","exit 1\n",""],["control","echo diagnostic >&2\nexit 1\n","diagnostic"]]) {
 const executable=join(scratch,name);const bytes=Buffer.from("#!/bin/sh\n"+body);await writeFile(executable,bytes,{mode:0o700});
 const configuration={command:executable,cacheRoot:scratch,cacheMaxBytes:1048576,projectTimeoutMs:1000,functionTimeoutMs:1000,jobs:1};
 const outcome=await runKunaCommand({installation:{configuration,executable,version:"1.833",verified:true,sha256:createHash("sha256").update(bytes).digest("hex"),bytes:bytes.length,script:true},arguments:["functions"],workspace:scratch,operation:"probe",timeoutMs:5000,reportedTimeoutMs:5000,outputBytes:65536,signal:new AbortController().signal,retainFailedFileClose:()=>{throw Error("unexpected cleanup");}});
 assert.equal(outcome.kind,"failed");
 assert.equal(outcome.error.userMessage,`kuna functions failed with exit code 1: ${tail}`);
 if(name==="empty") {assert.equal(outcome.error.capturedOutput.stderr,"");assert.equal(outcome.error.cause.message,"Command failed; inspect its exit status and diagnostics.");}
 console.log(JSON.stringify({case:name,userMessage:outcome.error.userMessage,cause:outcome.error.cause.message,stderr:outcome.error.capturedOutput.stderr}));
}
} finally {await rm(scratch,{recursive:true,force:true});}

Expected behavior

When a nonzero Kuna command provides no stderr diagnostic, its user message should select the available cause text rather than end with a blank diagnostic tail. KunaCommand.ts explicitly supplies cause.message as a fallback, and KunaInstallation.ts already treats empty trimmed stderr as unavailable.

Impact and scope

The observed defect affects wording for nonzero exits with empty stderr. Failure status, exit code, captured output, and the owned cause remain available. NOT_ENOUGH_INFO: frequency with a real Kuna executable; no broader command-outcome, cleanup, or evidence-integrity failure was observed.

REA version

Main source 8ab5024397ddbd3b37ab68a13cabedc2eb7ce7dd. Latest published rea-agents 6.5.0 source at 3b79af28e80785afce9f5998d8f03289402a1847 lacks the Kuna module and was inspected only; this report does not claim the release is affected.

Runtime and platform

Node v24.18.0; macOS 27.0.1 arm64. No native provider was started.

Target and artifact context

Two harmless local shell fixtures in isolated scratch; no Kuna binary, decompilation, or external target.

Interface

Other

Earliest failure stage

Transport or result projection

Relevant output or evidence

  • Expected source: The explicit cause fallback in KunaCommand and the empty-stderr handling in KunaInstallation, linked above.
  • Failure source: The source-boundary subprocess observation below; the formatter applies nullish coalescing to the final element of a split empty string.
  • Evidence provenance: observed
  • Local verification: reproduced
  • Reproduction completeness: complete
{"case":"empty","userMessage":"kuna functions failed with exit code 1: ","cause":"Command failed; inspect its exit status and diagnostics.","stderr":""}
{"case":"control","userMessage":"kuna functions failed with exit code 1: diagnostic","cause":"Command failed; inspect its exit status and diagnostics.","stderr":"diagnostic\n"}

The executed fixture supplies a matching byte fingerprint and a script installation shape, which KunaCommand explicitly supports for shims. Production callers route through KunaTargetCommands.#run (src/kuna/KunaTargetCommands.ts:114) and KunaProjectExport (src/kuna/KunaProjectExport.ts:200). The fixture does not establish real-provider frequency or a complete CLI/MCP run.

Typed outcome or recovery expected

Repeat the empty-stderr fixture and the nonempty-stderr control. The empty case must include a nonempty explanation from the available cause; the control must still include its actual stderr diagnostic. Keep the failed typed result, exit code, captured output and retained cause.

Analysis or suspected cause

The final element of a split empty string is an empty string, which is not nullish. The explicit cause.message fallback is therefore bypassed.

Additional context

Open and closed issue and PR bodies were searched, including related Kuna capability-message, timeout and output-limit changes. Those concern different typed outcomes. This report concerns the nonzero-exit message only.

Triage assessment

  • Impact level: P3
  • Assessment status: supported
  • Impact basis: The executed nonzero-exit fixture loses only the diagnostic wording; failure status, exit code and cause remain available. Real-Kuna frequency is unknown.
  • Workaround status: available
  • Workaround basis: The returned error retains its cause message and exit status; these were inspected in the same empty-stderr fixture.
Originally created by @coygeek on GitHub (Oct 16, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/4374 ### 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 Process capture or runtime observation ### Observed behavior The empty-stderr case returns `kuna functions failed with exit code 1: ` even though its retained cause is `Command failed; inspect its exit status and diagnostics.` The nonempty-stderr control returns the expected `diagnostic` tail. Both cases reproduced in the single bounded run. ### Reproduction From the main source baseline below, install locked dependencies with `npm ci`. Save the following as `repro.mts` outside the checkout and run it from the checkout with `REA_CHECKOUT="$PWD" REA_PROBE_SCRATCH="$(mktemp -d /tmp/rea-repro.XXXXXX)" npx --no-install tsx /path/to/repro.mts`. This was executed through source imports and real owned child supervision, with two harmless local shell fixtures. It is a module-boundary reproduction; the compiled CLI/MCP and a real Kuna binary were not run. ```ts import assert from "node:assert/strict"; import {pathToFileURL} from "node:url"; import {resolve,join} from "node:path"; import {mkdir,mkdtemp,writeFile,rm} from "node:fs/promises"; import {createHash} from "node:crypto"; const root=process.env.REA_CHECKOUT!; const {runKunaCommand}=await import(pathToFileURL(resolve(root,"src/kuna/KunaCommand.ts")).href); const base=process.env.REA_PROBE_SCRATCH!; assert.ok(base,"REA_PROBE_SCRATCH must name an isolated scratch directory"); await mkdir(base,{recursive:true}); const scratch=await mkdtemp(join(base,"run-")); try { for(const [name,body,tail] of [["empty","exit 1\n",""],["control","echo diagnostic >&2\nexit 1\n","diagnostic"]]) { const executable=join(scratch,name);const bytes=Buffer.from("#!/bin/sh\n"+body);await writeFile(executable,bytes,{mode:0o700}); const configuration={command:executable,cacheRoot:scratch,cacheMaxBytes:1048576,projectTimeoutMs:1000,functionTimeoutMs:1000,jobs:1}; const outcome=await runKunaCommand({installation:{configuration,executable,version:"1.833",verified:true,sha256:createHash("sha256").update(bytes).digest("hex"),bytes:bytes.length,script:true},arguments:["functions"],workspace:scratch,operation:"probe",timeoutMs:5000,reportedTimeoutMs:5000,outputBytes:65536,signal:new AbortController().signal,retainFailedFileClose:()=>{throw Error("unexpected cleanup");}}); assert.equal(outcome.kind,"failed"); assert.equal(outcome.error.userMessage,`kuna functions failed with exit code 1: ${tail}`); if(name==="empty") {assert.equal(outcome.error.capturedOutput.stderr,"");assert.equal(outcome.error.cause.message,"Command failed; inspect its exit status and diagnostics.");} console.log(JSON.stringify({case:name,userMessage:outcome.error.userMessage,cause:outcome.error.cause.message,stderr:outcome.error.capturedOutput.stderr})); } } finally {await rm(scratch,{recursive:true,force:true});} ``` ### Expected behavior When a nonzero Kuna command provides no stderr diagnostic, its user message should select the available cause text rather than end with a blank diagnostic tail. [KunaCommand.ts](https://github.com/morluto/rea/blob/8ab5024397ddbd3b37ab68a13cabedc2eb7ce7dd/src/kuna/KunaCommand.ts#L344-L350) explicitly supplies `cause.message` as a fallback, and [KunaInstallation.ts](https://github.com/morluto/rea/blob/8ab5024397ddbd3b37ab68a13cabedc2eb7ce7dd/src/kuna/KunaInstallation.ts#L168-L177) already treats empty trimmed stderr as unavailable. ### Impact and scope The observed defect affects wording for nonzero exits with empty stderr. Failure status, exit code, captured output, and the owned cause remain available. NOT_ENOUGH_INFO: frequency with a real Kuna executable; no broader command-outcome, cleanup, or evidence-integrity failure was observed. ### REA version Main source `8ab5024397ddbd3b37ab68a13cabedc2eb7ce7dd`. Latest published `rea-agents` 6.5.0 source at `3b79af28e80785afce9f5998d8f03289402a1847` lacks the Kuna module and was inspected only; this report does not claim the release is affected. ### Runtime and platform Node v24.18.0; macOS 27.0.1 arm64. No native provider was started. ### Target and artifact context Two harmless local shell fixtures in isolated scratch; no Kuna binary, decompilation, or external target. ### Interface Other ### Earliest failure stage Transport or result projection ### Relevant output or evidence - Expected source: The explicit cause fallback in KunaCommand and the empty-stderr handling in KunaInstallation, linked above. - Failure source: The source-boundary subprocess observation below; the formatter applies nullish coalescing to the final element of a split empty string. - Evidence provenance: observed - Local verification: reproduced - Reproduction completeness: complete ```json {"case":"empty","userMessage":"kuna functions failed with exit code 1: ","cause":"Command failed; inspect its exit status and diagnostics.","stderr":""} {"case":"control","userMessage":"kuna functions failed with exit code 1: diagnostic","cause":"Command failed; inspect its exit status and diagnostics.","stderr":"diagnostic\n"} ``` The executed fixture supplies a matching byte fingerprint and a script installation shape, which KunaCommand explicitly supports for shims. Production callers route through `KunaTargetCommands.#run` (`src/kuna/KunaTargetCommands.ts:114`) and `KunaProjectExport` (`src/kuna/KunaProjectExport.ts:200`). The fixture does not establish real-provider frequency or a complete CLI/MCP run. ### Typed outcome or recovery expected Repeat the empty-stderr fixture and the nonempty-stderr control. The empty case must include a nonempty explanation from the available cause; the control must still include its actual stderr diagnostic. Keep the failed typed result, exit code, captured output and retained cause. ### Analysis or suspected cause The final element of a split empty string is an empty string, which is not nullish. The explicit `cause.message` fallback is therefore bypassed. ### Additional context Open and closed issue and PR bodies were searched, including related Kuna capability-message, timeout and output-limit changes. Those concern different typed outcomes. This report concerns the nonzero-exit message only. ## Triage assessment - Impact level: P3 - Assessment status: supported - Impact basis: The executed nonzero-exit fixture loses only the diagnostic wording; failure status, exit code and cause remain available. Real-Kuna frequency is unknown. - Workaround status: available - Workaround basis: The returned error retains its cause message and exit status; these were inspected in the same empty-stderr fixture.
Author
Owner

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

Addressed on main in github.com/morluto/rea@6d52814070. The Kuna nonzero-exit formatter now falls back to the retained cause message when trimmed stderr is empty, while preserving an actual stderr diagnostic when present. Verified the current source contains the change; I have not rerun the fixture or tested a real Kuna executable. Closing as completed.

<!-- gh-comment-id:6244123576 --> @coygeek commented on GitHub (Oct 18, 2026): Addressed on main in https://github.com/morluto/rea/commit/6d528140702d4af2f8e7a28416cfc520cdd50955. The Kuna nonzero-exit formatter now falls back to the retained cause message when trimmed stderr is empty, while preserving an actual stderr diagnostic when present. Verified the current source contains the change; I have not rerun the fixture or tested a real Kuna executable. Closing as completed.
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#1731
No description provided.