[GH-ISSUE #6764] [Bug] Signal decoding rejects valid filenames when its private snapshot name exceeds the filesystem limit #4121

Closed
opened 2026-10-31 00:46:58 +00:00 by bsakaguchi · 0 comments
Owner

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

Before submitting

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

Area

Evidence, artifacts, or reconstruction

Observed behavior

SignalRecordingService.decode with an existing local recording named "a." + "x".repeat(246) and explicit input_format: "vcd" fails before launching the decode command. The valid source basename is 248 bytes, but REA builds a 256-byte private basename by prefixing its entire suffix with recording. The real filesystem write returns ENAMETOOLONG, projected as execution_failure at workspace preparation. The remediation advises repairing TMPDIR, although the same directory admits the control.

An otherwise identical source with 245 suffix characters completes the simulated path: its private basename is exactly 255 bytes. The version probe runs in both cases; only the control launches the decode command. This is a production service/provider and real filesystem reproduction with an injected command simulator, not real sigrok decoding.

Reproduction

From the checkout root with locked dependencies, save this as /tmp/rea-signal-suffix-repro.mjs, then run node --import tsx /tmp/rea-signal-suffix-repro.mjs on Linux. The harness creates equivalent idle VCD fixtures, records process launches, preserves source hashes, projects errors, and closes private runtimes. The inline reproducer ran once; earlier equivalent boundary probes were consistent. Its source fixtures and simulator remain in the generated temporary directory and can be removed afterward.

import { writeFile, chmod, readdir, readFile } from 'node:fs/promises';
import { join,resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
import { mkdtempSync } from 'node:fs';
import { createHash } from 'node:crypto';
const { SigrokRecordingProvider } = await import(pathToFileURL(resolve('src/signals/SigrokRecordingProvider.ts')).href);
const { SignalRecordingService } = await import(pathToFileURL(resolve('src/application/signals/SignalRecordingService.ts')).href);
const { PrivateRuntimeRoot } = await import(pathToFileURL(resolve('src/process/PrivateRuntimeRoot.ts')).href);
const { spawnOwnedProviderProcess } = await import(pathToFileURL(resolve('src/process/ProviderProcess.ts')).href);
const { readStableArtifact } = await import(pathToFileURL(resolve('src/artifacts/readStableArtifact.ts')).href);
const { projectAnalysisError } = await import(pathToFileURL(resolve('src/domain/analysisErrorProjection.ts')).href);
const directory=process.env.REA_REPRO_DIR ?? mkdtempSync('/tmp/rea-signal-suffix-');
const script=join(directory,'producer.mjs');
await writeFile(script, `if(process.argv.includes('--version'))console.log('sigrok-cli 0.7.2\\nlibsigrokdecode 0.5.3');else console.log('0-1 uart-1: fixture');`);
await chmod(script,0o700);
const source='$timescale 1 us $end\n$scope module logic $end\n$var wire 1 ! RX $end\n$upscope $end\n$enddefinitions $end\n#0\n1!\n#1000\n1!\n';
const calls=[];
const provider=new SigrokRecordingProvider({REA_SIGROK_COMMAND:script,REA_SIGROK_PRLIMIT_COMMAND:script},async input=>{
 calls.push([...input.arguments]);
 return spawnOwnedProviderProcess({...input,command:process.execPath,arguments:[script,...input.arguments],expectedCommand:process.execPath});
},()=>PrivateRuntimeRoot.create({parent:directory,prefix:'runtime-'}),'linux',readStableArtifact,async limits=>limits);
const service=new SignalRecordingService(provider);
try {
 for(const suffixLength of [245,246]){
  const name='a.'+'x'.repeat(suffixLength),path=join(directory,name);
  await writeFile(path,source);
  const before=calls.length;
  const result=await service.decode({path,decoders:'uart:rx=RX:baudrate=9600',input_format:'vcd',line_limit:null});
  const outcome=result.ok?{ok:true,window:result.value.normalized_result.output_window,annotations:result.value.normalized_result.annotations_text}:{ok:false,error:(()=>{const e=projectAnalysisError(result.error);return {code:e.code,stage:e.details.diagnostics.stage,system_code:e.details.diagnostics.system_code};})()};
  console.log(JSON.stringify({suffix_length:suffixLength,source_name_bytes:Buffer.byteLength(name),private_name_bytes:Buffer.byteLength('recording.'+'x'.repeat(suffixLength)),source_sha256:createHash('sha256').update(await readFile(path)).digest('hex'),calls:calls.slice(before).map(a=>a.includes('--version')?'version':'decode'),outcome}));
 }
}finally{await service.close();}
console.log(JSON.stringify({remaining_runtimes:(await readdir(directory)).filter(n=>n.startsWith('runtime-'))}));

Expected behavior

An existing supported local recording should reach decoding without private snapshot naming making its valid basename unrepresentable. Explicit input_format should remain selected and source bytes should remain unchanged. The public contract at src/contracts/signals/signalToolContracts.ts:34 supports an existing local recording with optional explicit input format. src/domain/signals/signalRecording.ts:14-27 admits this path and selector without a filename-suffix restriction. This report does not request truncating parser-relevant filename hints or removing private-copy isolation.

Impact and scope

Valid source names can fail deterministically at private staging on the tested filesystem, preventing decoding and sending the caller toward unrelated temporary-directory repair. The boundary observed here is a 256-byte private basename versus a successful 255-byte control. The input files were successfully created and read, both retained SHA-256 7043622f263a797230f08d3acbaf1f938b76713ca7b7aa78e4436a45d2d82820, and no private runtime directories remained after close. Using an equivalent shorter source filename is an available staging workaround; the harness created the equivalent files rather than literally renaming one. Broader filesystem thresholds and real decoder behavior were not tested.

REA version

Source manifest 6.7.0, commit source revision. The checkout source was tested; the published npm artifact was not executed.

Runtime and platform

Node v26.12.1; Linux 7.2.8-9-omarchy, x86_64; locked dependencies and tsx. The simulator prints a sigrok-cli 0.7.2-shaped version banner. Real sigrok-cli and prlimit were not run; the harness injects a command runner, private runtime parent, and limits reader. Actual file acquisition, snapshot creation, process ownership, cleanup and error projection use production implementations.

Target and artifact context

Two identical self-authored idle VCD recordings with ordinary local filenames differing only in suffix length. Both requests specify decoders: "uart:rx=RX:baudrate=9600", input_format: "vcd", and line_limit: null. No hardware scan or acquisition occurs. The simulator's annotation line is a harness marker and does not establish real VCD acceptance or decoded annotations.

Interface

Other

Earliest failure stage

Setup or provider admission

Relevant output or evidence

Actual final inline-reproducer output:

{"suffix_length":245,"source_name_bytes":247,"private_name_bytes":255,"source_sha256":"7043622f263a797230f08d3acbaf1f938b76713ca7b7aa78e4436a45d2d82820","calls":["version","decode"],"outcome":{"ok":true,"window":{"total_lines":1,"returned_lines":1,"has_more":false},"annotations":"0-1 uart-1: fixture\n"}}
{"suffix_length":246,"source_name_bytes":248,"private_name_bytes":256,"source_sha256":"7043622f263a797230f08d3acbaf1f938b76713ca7b7aa78e4436a45d2d82820","calls":["version"],"outcome":{"ok":false,"error":{"code":"execution_failure","stage":"workspace","system_code":"ENAMETOOLONG"}}}
{"remaining_runtimes":[]}

The full projected error's remediation was: Make the host temporary directory (TMPDIR) an existing directory this user can create files in on a writable filesystem, then retry. The recorded error includes the private path and ENAMETOOLONG; paths are omitted here because the name lengths and error code establish the failure.

Evidence provenance: observed. Local verification: reproduced. Reproduction completeness: complete for private-copy staging. The valid source input passes shared validation and stable acquisition, then the failing case stops at the real snapshot write before decode launch. CLI/MCP execution, actual producer limits, real VCD acceptance and real decoding recovery remain unverified.

Typed outcome or recovery expected

The 248-byte source basename should stage successfully with unchanged bytes and the selected vcd format, then launch decoding; the 247-byte control should keep passing. Closing both operations should continue to remove their private runtimes. These are proposed restoration checks, not a claim that real decoding was restored.

Analysis or suspected cause

Source-derived: src/signals/SigrokRecordingProvider.ts:180-184 builds recording${extname(input.path)} regardless of explicit format selection. For the failing input, extname contributes a dot and 246 ASCII characters, making the internal basename 256 bytes. The write at lines 344-348 fails in workspace phase, and lines 505-511 route it to the generic private-workspace failure guidance. This behavior depends on an adapter-generated name rather than a failing source path or unwritable temporary directory.

Additional context

The open-title corpus was consulted first. Open and closed searches for repo:morluto/rea is:issue sigrok ENAMETOOLONG and repo:morluto/rea is:issue sigrok "long suffix" returned no results. Related #6580 and #6733 concern lost CSV autodetection hints. This request explicitly selects its format and fails before the decode process starts. A separate unknown-annotation-decoder error-mapping lead was excluded because it overlaps #5515. NOT_ENOUGH_INFO: introducing revision, other filesystems, real sigrok execution, and public CLI/MCP execution.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A valid local source filename deterministically prevents private staging and decoding admission on the tested filesystem, with misleading recovery guidance; no corruption or broader incident was observed.
  • Workaround status: available
  • Workaround basis: The identical source bytes under the one-character-shorter suffix pass staging and simulated completion with matching hashes. This establishes the shorter-name workaround for staging, without claiming actual decoder output.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6764 ## 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 Evidence, artifacts, or reconstruction ## Observed behavior `SignalRecordingService.decode` with an existing local recording named `"a." + "x".repeat(246)` and explicit `input_format: "vcd"` fails before launching the decode command. The valid source basename is 248 bytes, but REA builds a 256-byte private basename by prefixing its entire suffix with `recording`. The real filesystem write returns `ENAMETOOLONG`, projected as `execution_failure` at workspace preparation. The remediation advises repairing `TMPDIR`, although the same directory admits the control. An otherwise identical source with 245 suffix characters completes the simulated path: its private basename is exactly 255 bytes. The version probe runs in both cases; only the control launches the decode command. This is a production service/provider and real filesystem reproduction with an injected command simulator, not real sigrok decoding. ## Reproduction From the checkout root with locked dependencies, save this as `/tmp/rea-signal-suffix-repro.mjs`, then run `node --import tsx /tmp/rea-signal-suffix-repro.mjs` on Linux. The harness creates equivalent idle VCD fixtures, records process launches, preserves source hashes, projects errors, and closes private runtimes. The inline reproducer ran once; earlier equivalent boundary probes were consistent. Its source fixtures and simulator remain in the generated temporary directory and can be removed afterward. ```js import { writeFile, chmod, readdir, readFile } from 'node:fs/promises'; import { join,resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; import { mkdtempSync } from 'node:fs'; import { createHash } from 'node:crypto'; const { SigrokRecordingProvider } = await import(pathToFileURL(resolve('src/signals/SigrokRecordingProvider.ts')).href); const { SignalRecordingService } = await import(pathToFileURL(resolve('src/application/signals/SignalRecordingService.ts')).href); const { PrivateRuntimeRoot } = await import(pathToFileURL(resolve('src/process/PrivateRuntimeRoot.ts')).href); const { spawnOwnedProviderProcess } = await import(pathToFileURL(resolve('src/process/ProviderProcess.ts')).href); const { readStableArtifact } = await import(pathToFileURL(resolve('src/artifacts/readStableArtifact.ts')).href); const { projectAnalysisError } = await import(pathToFileURL(resolve('src/domain/analysisErrorProjection.ts')).href); const directory=process.env.REA_REPRO_DIR ?? mkdtempSync('/tmp/rea-signal-suffix-'); const script=join(directory,'producer.mjs'); await writeFile(script, `if(process.argv.includes('--version'))console.log('sigrok-cli 0.7.2\\nlibsigrokdecode 0.5.3');else console.log('0-1 uart-1: fixture');`); await chmod(script,0o700); const source='$timescale 1 us $end\n$scope module logic $end\n$var wire 1 ! RX $end\n$upscope $end\n$enddefinitions $end\n#0\n1!\n#1000\n1!\n'; const calls=[]; const provider=new SigrokRecordingProvider({REA_SIGROK_COMMAND:script,REA_SIGROK_PRLIMIT_COMMAND:script},async input=>{ calls.push([...input.arguments]); return spawnOwnedProviderProcess({...input,command:process.execPath,arguments:[script,...input.arguments],expectedCommand:process.execPath}); },()=>PrivateRuntimeRoot.create({parent:directory,prefix:'runtime-'}),'linux',readStableArtifact,async limits=>limits); const service=new SignalRecordingService(provider); try { for(const suffixLength of [245,246]){ const name='a.'+'x'.repeat(suffixLength),path=join(directory,name); await writeFile(path,source); const before=calls.length; const result=await service.decode({path,decoders:'uart:rx=RX:baudrate=9600',input_format:'vcd',line_limit:null}); const outcome=result.ok?{ok:true,window:result.value.normalized_result.output_window,annotations:result.value.normalized_result.annotations_text}:{ok:false,error:(()=>{const e=projectAnalysisError(result.error);return {code:e.code,stage:e.details.diagnostics.stage,system_code:e.details.diagnostics.system_code};})()}; console.log(JSON.stringify({suffix_length:suffixLength,source_name_bytes:Buffer.byteLength(name),private_name_bytes:Buffer.byteLength('recording.'+'x'.repeat(suffixLength)),source_sha256:createHash('sha256').update(await readFile(path)).digest('hex'),calls:calls.slice(before).map(a=>a.includes('--version')?'version':'decode'),outcome})); } }finally{await service.close();} console.log(JSON.stringify({remaining_runtimes:(await readdir(directory)).filter(n=>n.startsWith('runtime-'))})); ``` ## Expected behavior An existing supported local recording should reach decoding without private snapshot naming making its valid basename unrepresentable. Explicit `input_format` should remain selected and source bytes should remain unchanged. The public contract at `src/contracts/signals/signalToolContracts.ts:34` supports an existing local recording with optional explicit input format. `src/domain/signals/signalRecording.ts:14-27` admits this path and selector without a filename-suffix restriction. This report does not request truncating parser-relevant filename hints or removing private-copy isolation. ## Impact and scope Valid source names can fail deterministically at private staging on the tested filesystem, preventing decoding and sending the caller toward unrelated temporary-directory repair. The boundary observed here is a 256-byte private basename versus a successful 255-byte control. The input files were successfully created and read, both retained SHA-256 `7043622f263a797230f08d3acbaf1f938b76713ca7b7aa78e4436a45d2d82820`, and no private runtime directories remained after close. Using an equivalent shorter source filename is an available staging workaround; the harness created the equivalent files rather than literally renaming one. Broader filesystem thresholds and real decoder behavior were not tested. ## REA version Source manifest `6.7.0`, commit [source revision](https://github.com/morluto/rea/commit/2bcd7e6d13f0a8118d6619718254255e84f75d86). The checkout source was tested; the published npm artifact was not executed. ## Runtime and platform Node `v26.12.1`; Linux `7.2.8-9-omarchy`, x86_64; locked dependencies and `tsx`. The simulator prints a sigrok-cli 0.7.2-shaped version banner. Real sigrok-cli and prlimit were not run; the harness injects a command runner, private runtime parent, and limits reader. Actual file acquisition, snapshot creation, process ownership, cleanup and error projection use production implementations. ## Target and artifact context Two identical self-authored idle VCD recordings with ordinary local filenames differing only in suffix length. Both requests specify `decoders: "uart:rx=RX:baudrate=9600"`, `input_format: "vcd"`, and `line_limit: null`. No hardware scan or acquisition occurs. The simulator's annotation line is a harness marker and does not establish real VCD acceptance or decoded annotations. ## Interface Other ## Earliest failure stage Setup or provider admission ## Relevant output or evidence Actual final inline-reproducer output: ```jsonl {"suffix_length":245,"source_name_bytes":247,"private_name_bytes":255,"source_sha256":"7043622f263a797230f08d3acbaf1f938b76713ca7b7aa78e4436a45d2d82820","calls":["version","decode"],"outcome":{"ok":true,"window":{"total_lines":1,"returned_lines":1,"has_more":false},"annotations":"0-1 uart-1: fixture\n"}} {"suffix_length":246,"source_name_bytes":248,"private_name_bytes":256,"source_sha256":"7043622f263a797230f08d3acbaf1f938b76713ca7b7aa78e4436a45d2d82820","calls":["version"],"outcome":{"ok":false,"error":{"code":"execution_failure","stage":"workspace","system_code":"ENAMETOOLONG"}}} {"remaining_runtimes":[]} ``` The full projected error's remediation was: `Make the host temporary directory (TMPDIR) an existing directory this user can create files in on a writable filesystem, then retry.` The recorded error includes the private path and `ENAMETOOLONG`; paths are omitted here because the name lengths and error code establish the failure. Evidence provenance: observed. Local verification: reproduced. Reproduction completeness: complete for private-copy staging. The valid source input passes shared validation and stable acquisition, then the failing case stops at the real snapshot write before decode launch. CLI/MCP execution, actual producer limits, real VCD acceptance and real decoding recovery remain unverified. ## Typed outcome or recovery expected The 248-byte source basename should stage successfully with unchanged bytes and the selected `vcd` format, then launch decoding; the 247-byte control should keep passing. Closing both operations should continue to remove their private runtimes. These are proposed restoration checks, not a claim that real decoding was restored. ## Analysis or suspected cause Source-derived: `src/signals/SigrokRecordingProvider.ts:180-184` builds `recording${extname(input.path)}` regardless of explicit format selection. For the failing input, `extname` contributes a dot and 246 ASCII characters, making the internal basename 256 bytes. The write at lines 344-348 fails in workspace phase, and lines 505-511 route it to the generic private-workspace failure guidance. This behavior depends on an adapter-generated name rather than a failing source path or unwritable temporary directory. ## Additional context The open-title corpus was consulted first. Open and closed searches for `repo:morluto/rea is:issue sigrok ENAMETOOLONG` and `repo:morluto/rea is:issue sigrok "long suffix"` returned no results. Related [#6580](https://github.com/morluto/rea/issues/6580) and [#6733](https://github.com/morluto/rea/issues/6733) concern lost CSV autodetection hints. This request explicitly selects its format and fails before the decode process starts. A separate unknown-annotation-decoder error-mapping lead was excluded because it overlaps [#5515](https://github.com/morluto/rea/issues/5515). NOT_ENOUGH_INFO: introducing revision, other filesystems, real sigrok execution, and public CLI/MCP execution. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A valid local source filename deterministically prevents private staging and decoding admission on the tested filesystem, with misleading recovery guidance; no corruption or broader incident was observed. - Workaround status: available - Workaround basis: The identical source bytes under the one-character-shorter suffix pass staging and simulated completion with matching hashes. This establishes the shorter-name workaround for staging, without claiming actual decoder output.
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#4121
No description provided.