[GH-ISSUE #6666] [Bug] GPU inspection misclassifies descriptor exhaustion as an unavailable target #4045

Closed
opened 2026-10-31 00:46:20 +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/6666

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 production GPU inspection service and public error projection report real per-process descriptor exhaustion as target_unavailable / unavailable, with retryable: false and advice to check the selected file's existence, readability and format. The unchanged PTX file inspects successfully before and after releasing the extra descriptors. The filesystem error was produced by the operating system, with no mocked provider or filesystem.

Reproduction

From the source checkout with locked dependencies and the Node runtime below, set WORK to an existing disposable writable directory. Save this executed probe as $WORK/gpu-descriptor-pressure.mts:

import { open, writeFile } from 'node:fs/promises';
import { join, resolve } from 'node:path';
import { pathToFileURL } from 'node:url';
import assert from 'node:assert/strict';
const repo=resolve(process.argv[2]), work=resolve(process.argv[3]);
const { GpuCodeObjectsProvider }=await import(pathToFileURL(join(repo,'src/gpu/GpuCodeObjectsProvider.ts')).href);
const { GpuCodeObjectsService }=await import(pathToFileURL(join(repo,'src/application/gpu/GpuCodeObjectsService.ts')).href);
const { projectAnalysisError }=await import(pathToFileURL(join(repo,'src/domain/analysisErrorProjection.ts')).href);
const path=join(work,'selected.ptx');
await writeFile(path,'.version 8.8\n.target sm_80\n.address_size 64\n.visible .entry k() { ret; }\n');
const service=new GpuCodeObjectsService(new GpuCodeObjectsProvider());
const handles=[];
const inspect=()=>service.inspect({path,analysis:'kernels',detail:'complete'});
try {
 console.log(JSON.stringify({node:process.version,platform:process.platform,arch:process.arch}));
 const before=await inspect();assert.equal(before.ok,true);console.log(JSON.stringify({stage:'before',ok:before.ok,kernel_count:before.value.normalized_result.kernels.length}));
 for (let i=0;i<128;i++) {try{handles.push(await open('/dev/null','r'));}catch(e){assert.equal(e.code,'EMFILE');console.log(JSON.stringify({stage:'pressure',opened:handles.length,errno:e.code}));break;}}
 assert(handles.length<128,'Run under a low per-process file limit');
 const underPressure=await inspect();assert.equal(underPressure.ok,false);
 const projected=projectAnalysisError(underPressure.error);
 console.log(JSON.stringify({stage:'under-pressure',tag:underPressure.error._tag,projection:projected}));
 for(const h of handles)await h.close();handles.length=0;
 const after=await inspect();assert.equal(after.ok,true);console.log(JSON.stringify({stage:'after',ok:after.ok,kernel_count:after.value.normalized_result.kernels.length}));
}finally{for(const h of handles)await h.close();await service.close();}

Run in a child shell; the file-descriptor limit affects only that child:

bash -c 'ulimit -n 64; exec node --import ./node_modules/tsx/dist/loader.mjs "$1/gpu-descriptor-pressure.mts" "$PWD" "$1"' _ "$WORK"

Executed once with three service requests: successful control, request under descriptor pressure, successful restored control. The process exited 0 and stderr was empty. No target code runs.

Expected behavior

Keep a host resource failure distinct from a selected-path or format refusal, preserving the diagnostic without advising the caller to reselect a valid artifact. The repository boundary rule requires meaningful typed failures. The existing model reader's host-error classification demonstrates the distinction: only selected-path errno values become BinaryTargetError; other host failures reach ProviderAdapterError, which projects to execution_failure. An appropriate resource outcome would also preserve the actual cause. This report does not require automatic retryability.

Impact and scope

Clients receive a definite target diagnosis and file-correction advice for process-wide descriptor pressure, despite a valid unchanged input. The raw EMFILE reason survives, so a caller can diagnose the resource condition manually. Releasing the probe-owned descriptors restored inspection without changing the artifact. No incorrect successful Evidence, file modification, leak, or GPU execution was observed. Actual deployment frequency and downstream automated-recovery behavior were not measured.

REA version

Source revision 5a5bcb6b549cfdfba6dd3f02dd681e505ffd2a26; package metadata 6.7.0. This newer source checkout was executed; the published rea-agents 6.7.0 runtime was not tested.

Runtime and platform

Node.js v24.22.1; Linux 7.2.8-9-omarchy x86_64; repository-locked tsx loader. No CUDA toolkit, GPU, Hopper or Ghidra was used.

Target and artifact context

An inert synthetic UTF-8 PTX 8.8 file targeting sm_80, with one parameterless kernel. The probe opens /dev/null until the child process reaches its descriptor limit, then closes every extra handle in the same process. The input bytes remain unchanged.

Interface

Other

Earliest failure stage

Setup or provider admission

Relevant output or evidence

Observed selected fields:

before:         ok=true; kernel_count=1
pressure:       opened=38; errno=EMFILE
under-pressure: tag=BinaryTargetError; code=target_unavailable;
                category=unavailable; retryable=false
after:          ok=true; kernel_count=1

The projected message and remediation action both read as follows; <fixture> replaces the disposable PTX path:

Cannot open artifact: EMFILE: too many open files, open '<fixture>'. Check that the path exists, is readable, and points to a supported file.
  • Expected source: the repository boundary rule and existing host-versus-selection classification linked above.
  • Failure source: the executed source-service probe and observed output above.
  • Evidence provenance: observed.
  • Local verification: reproduced.
  • Reproduction completeness: complete.

Execution reached the real shared service, provider, regular-file admission and error projection. CLI and MCP transports were not executed. Their CLI service call and MCP service binding establish public-path reachability as source evidence.

Typed outcome or recovery expected

Return a host execution/resource failure that retains EMFILE and the selected path, while keeping genuine missing-path, permission, malformed-input, integrity and cancellation failures distinct. Restoration check: the same three-stage probe must preserve both successful controls while the pressured request reports a host execution/resource outcome without path/format-correction advice.

Analysis or suspected cause

Source-derived: regular-file admission forwards the actual open error, and the GPU provider converts every remaining coded Error to BinaryTargetError. Its public projection and presentation account for the observed target diagnosis and recovery advice.

Additional context

Read the supplied full open-issue snapshot and searched all-state issues for GPU EIO, GPU EMFILE, GPU "I/O", GPU descriptor, GPU host, GPU target_unavailable, and "code objects". No matching GPU report was found. Related #6004 concerned the separate model provider; its current corrected branch does not change the GPU provider's independent classification. Only real EMFILE at open was executed here; read-time EIO, other platforms, transport execution, published-release behavior and first affected release are NOT_ENOUGH_INFO. Prepared with AI assistance.

Triage assessment

  • Impact level: P2
  • Assessment status: supported
  • Impact basis: A real bounded descriptor-exhaustion case produces the wrong machine-readable diagnosis and recovery advice for a valid input, while the original host reason remains available and both controls succeed.
  • Workaround status: available
  • Workaround basis: The retained EMFILE text identifies descriptor pressure, and releasing the probe-owned descriptors demonstrably restores the same request without changing the selected artifact.
Originally created by @coygeek on GitHub (Oct 23, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6666 ### 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 production GPU inspection service and public error projection report real per-process descriptor exhaustion as `target_unavailable` / `unavailable`, with `retryable: false` and advice to check the selected file's existence, readability and format. The unchanged PTX file inspects successfully before and after releasing the extra descriptors. The filesystem error was produced by the operating system, with no mocked provider or filesystem. ### Reproduction From the source checkout with locked dependencies and the Node runtime below, set `WORK` to an existing disposable writable directory. Save this executed probe as `$WORK/gpu-descriptor-pressure.mts`: ```ts import { open, writeFile } from 'node:fs/promises'; import { join, resolve } from 'node:path'; import { pathToFileURL } from 'node:url'; import assert from 'node:assert/strict'; const repo=resolve(process.argv[2]), work=resolve(process.argv[3]); const { GpuCodeObjectsProvider }=await import(pathToFileURL(join(repo,'src/gpu/GpuCodeObjectsProvider.ts')).href); const { GpuCodeObjectsService }=await import(pathToFileURL(join(repo,'src/application/gpu/GpuCodeObjectsService.ts')).href); const { projectAnalysisError }=await import(pathToFileURL(join(repo,'src/domain/analysisErrorProjection.ts')).href); const path=join(work,'selected.ptx'); await writeFile(path,'.version 8.8\n.target sm_80\n.address_size 64\n.visible .entry k() { ret; }\n'); const service=new GpuCodeObjectsService(new GpuCodeObjectsProvider()); const handles=[]; const inspect=()=>service.inspect({path,analysis:'kernels',detail:'complete'}); try { console.log(JSON.stringify({node:process.version,platform:process.platform,arch:process.arch})); const before=await inspect();assert.equal(before.ok,true);console.log(JSON.stringify({stage:'before',ok:before.ok,kernel_count:before.value.normalized_result.kernels.length})); for (let i=0;i<128;i++) {try{handles.push(await open('/dev/null','r'));}catch(e){assert.equal(e.code,'EMFILE');console.log(JSON.stringify({stage:'pressure',opened:handles.length,errno:e.code}));break;}} assert(handles.length<128,'Run under a low per-process file limit'); const underPressure=await inspect();assert.equal(underPressure.ok,false); const projected=projectAnalysisError(underPressure.error); console.log(JSON.stringify({stage:'under-pressure',tag:underPressure.error._tag,projection:projected})); for(const h of handles)await h.close();handles.length=0; const after=await inspect();assert.equal(after.ok,true);console.log(JSON.stringify({stage:'after',ok:after.ok,kernel_count:after.value.normalized_result.kernels.length})); }finally{for(const h of handles)await h.close();await service.close();} ``` Run in a child shell; the file-descriptor limit affects only that child: ```sh bash -c 'ulimit -n 64; exec node --import ./node_modules/tsx/dist/loader.mjs "$1/gpu-descriptor-pressure.mts" "$PWD" "$1"' _ "$WORK" ``` Executed once with three service requests: successful control, request under descriptor pressure, successful restored control. The process exited 0 and stderr was empty. No target code runs. ### Expected behavior Keep a host resource failure distinct from a selected-path or format refusal, preserving the diagnostic without advising the caller to reselect a valid artifact. The [repository boundary rule](https://github.com/morluto/rea/blob/5a5bcb6b549cfdfba6dd3f02dd681e505ffd2a26/AGENTS.md#L33) requires meaningful typed failures. The existing [model reader's host-error classification](https://github.com/morluto/rea/blob/5a5bcb6b549cfdfba6dd3f02dd681e505ffd2a26/src/model/ModelArtifactProvider.ts#L197-L211) demonstrates the distinction: only selected-path errno values become `BinaryTargetError`; other host failures reach `ProviderAdapterError`, which projects to `execution_failure`. An appropriate resource outcome would also preserve the actual cause. This report does not require automatic retryability. ### Impact and scope Clients receive a definite target diagnosis and file-correction advice for process-wide descriptor pressure, despite a valid unchanged input. The raw `EMFILE` reason survives, so a caller can diagnose the resource condition manually. Releasing the probe-owned descriptors restored inspection without changing the artifact. No incorrect successful Evidence, file modification, leak, or GPU execution was observed. Actual deployment frequency and downstream automated-recovery behavior were not measured. ### REA version Source revision `5a5bcb6b549cfdfba6dd3f02dd681e505ffd2a26`; package metadata 6.7.0. This newer source checkout was executed; the published rea-agents 6.7.0 runtime was not tested. ### Runtime and platform Node.js v24.22.1; Linux 7.2.8-9-omarchy x86_64; repository-locked tsx loader. No CUDA toolkit, GPU, Hopper or Ghidra was used. ### Target and artifact context An inert synthetic UTF-8 PTX 8.8 file targeting `sm_80`, with one parameterless kernel. The probe opens `/dev/null` until the child process reaches its descriptor limit, then closes every extra handle in the same process. The input bytes remain unchanged. ### Interface Other ### Earliest failure stage Setup or provider admission ### Relevant output or evidence Observed selected fields: ```text before: ok=true; kernel_count=1 pressure: opened=38; errno=EMFILE under-pressure: tag=BinaryTargetError; code=target_unavailable; category=unavailable; retryable=false after: ok=true; kernel_count=1 ``` The projected message and remediation action both read as follows; `<fixture>` replaces the disposable PTX path: ```text Cannot open artifact: EMFILE: too many open files, open '<fixture>'. Check that the path exists, is readable, and points to a supported file. ``` - Expected source: the repository boundary rule and existing host-versus-selection classification linked above. - Failure source: the executed source-service probe and observed output above. - Evidence provenance: observed. - Local verification: reproduced. - Reproduction completeness: complete. Execution reached the real shared service, provider, regular-file admission and error projection. CLI and MCP transports were not executed. Their [CLI service call](https://github.com/morluto/rea/blob/5a5bcb6b549cfdfba6dd3f02dd681e505ffd2a26/src/cli/gpuCommands.ts#L102-L118) and [MCP service binding](https://github.com/morluto/rea/blob/5a5bcb6b549cfdfba6dd3f02dd681e505ffd2a26/src/server/registerGpuCodeObjectsTools.ts#L20-L24) establish public-path reachability as source evidence. ### Typed outcome or recovery expected Return a host execution/resource failure that retains `EMFILE` and the selected path, while keeping genuine missing-path, permission, malformed-input, integrity and cancellation failures distinct. Restoration check: the same three-stage probe must preserve both successful controls while the pressured request reports a host execution/resource outcome without path/format-correction advice. ### Analysis or suspected cause Source-derived: [regular-file admission forwards the actual open error](https://github.com/morluto/rea/blob/5a5bcb6b549cfdfba6dd3f02dd681e505ffd2a26/src/filesystem/RegularFile.ts#L81-L95), and [the GPU provider converts every remaining coded Error to BinaryTargetError](https://github.com/morluto/rea/blob/5a5bcb6b549cfdfba6dd3f02dd681e505ffd2a26/src/gpu/GpuCodeObjectsProvider.ts#L189-L199). Its [public projection](https://github.com/morluto/rea/blob/5a5bcb6b549cfdfba6dd3f02dd681e505ffd2a26/src/domain/analysisErrorProjection.ts#L171-L176) and [presentation](https://github.com/morluto/rea/blob/5a5bcb6b549cfdfba6dd3f02dd681e505ffd2a26/src/domain/analysisErrorPresentation.ts#L319-L325) account for the observed target diagnosis and recovery advice. ### Additional context Read the supplied full open-issue snapshot and searched all-state issues for `GPU EIO`, `GPU EMFILE`, `GPU "I/O"`, `GPU descriptor`, `GPU host`, `GPU target_unavailable`, and `"code objects"`. No matching GPU report was found. Related [#6004](https://github.com/morluto/rea/issues/6004) concerned the separate model provider; its current corrected branch does not change the GPU provider's independent classification. Only real `EMFILE` at open was executed here; read-time `EIO`, other platforms, transport execution, published-release behavior and first affected release are NOT_ENOUGH_INFO. Prepared with AI assistance. ## Triage assessment - Impact level: P2 - Assessment status: supported - Impact basis: A real bounded descriptor-exhaustion case produces the wrong machine-readable diagnosis and recovery advice for a valid input, while the original host reason remains available and both controls succeed. - Workaround status: available - Workaround basis: The retained EMFILE text identifies descriptor pressure, and releasing the probe-owned descriptors demonstrably restores the same request without changing the selected artifact.
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#4045
No description provided.