[GH-ISSUE #6123] [Bug] Pcap workspace allocation errors masquerade as file-size limits #3755

Closed
opened 2026-10-31 00:43:42 +00:00 by bsakaguchi · 3 comments
Owner

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

Originally assigned to: @kaoru0822-kitauji on GitHub.

Before submitting

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

Area

Provider or capability discovery

Observed behavior

Public MCP inspect_pcap_capture reports real workspace-allocation ENOTDIR and EACCES failures as resource_constraint with resource: "file-size", null reported limits and advice to increase the worker/evidence-reply file-size allowance. Neither errno nor its failing filesystem coordinate appears in the projected error. The message does identify the owned capture workspace.

This was executed through the production MCP server, service, provider and real filesystem allocator. Wireshark version banners and successful native-resource admission were simulated on macOS, which normally refuses capture inspection before this stage. This is a controlled supported-admission reproduction, not a native Linux/Windows or real-Wireshark run.

Reproduction

Save this focused test under tests/boundary/mcp/pcapWorkspaceAllocation.test.ts in the source checkout and run node node_modules/vitest/vitest.mjs run tests/boundary/mcp/pcapWorkspaceAllocation.test.ts --maxWorkers=1 --reporter=verbose. The final executed test passed, asserting the current incorrect projections. Use a non-root POSIX account for the mode-0500 permission case.

It chooses controlled parent paths only for capture roots, invokes real mkdtemp, and restores permissions and closes all owners afterward. A normal-parent control proceeds to the expected missing-capture input error.

import { Client, InMemoryTransport } from '@modelcontextprotocol/client';
import { mkdir, mkdtemp, writeFile, chmod, rm } from 'node:fs/promises';
import { join } from 'node:path';
import { tmpdir } from 'node:os';
import { expect, it, vi } from 'vitest';
import * as native from '../../../src/pcap/PcapNativeBound.js';
import { PrivateRuntimeRoot } from '../../../src/process/PrivateRuntimeRoot.js';
import { PcapProvider } from '../../../src/pcap/PcapProvider.js';
import { spawnOwnedProviderProcess } from '../../../src/process/ProviderProcess.js';
import { createServer } from '../../../src/server/createServer.js';
import { createTestBinarySession } from '../../fixtures/binarySession.js';
import { parseMcpToolError } from '../../fixtures/mcpToolError.js';

it('projects actual workspace allocation failures after simulated native admission', async () => {
 const root=await mkdtemp(join(tmpdir(),'rea-workspace-test-'));
 const file=join(root,'ordinary-file');await writeFile(file,'not a directory');
 const denied=join(root,'denied');await mkdir(denied,{recursive:true});await chmod(denied,0o500);
 let mode='file';let cause:unknown;let attempts=0;
 const create=PrivateRuntimeRoot.create.bind(PrivateRuntimeRoot);
 const rootSpy=vi.spyOn(PrivateRuntimeRoot,'create').mockImplementation(async options=>{
   if(options?.prefix!=='rea-pcap-')return create(options);
   attempts++;
   try{return await create({...options,parent:mode==='file'?file:mode==='denied'?denied:root});}
   catch(error){cause=error;throw error;}
 });
 const boundSpy=vi.spyOn(native,'preparePcapNativeBound').mockResolvedValue({nativeBound:{},limitations:['Native resource admission simulated on macOS']});
 let launches=0;
 const provider=new PcapProvider({environment:{REA_TSHARK_COMMAND:process.execPath,REA_CAPINFOS_COMMAND:process.execPath},launcher:async spawn=>{launches++;return spawnOwnedProviderProcess({...spawn,command:process.execPath,arguments:['-e',`process.stdout.write(${JSON.stringify((launches % 2 ? 'TShark' : 'Capinfos') + ' (Wireshark) 4.6.6\n')})`]});}});
 const session=createTestBinarySession(()=>{throw new Error('No native binary provider');});
 const server=createServer({kind:'session',session},{pcapAnalysis:provider});
 const client=new Client({name:'workspace-test',version:'1'});
 const [ct,st]=InMemoryTransport.createLinkedPair();
 try{
  await server.connect(st);await client.connect(ct);
  for(mode of ['file','denied','normal']){
   cause=undefined;
   const response=await client.callTool({name:'inspect_pcap_capture',arguments:{path:join(root,'missing.pcap')}});
   const error=parseMcpToolError(response).error;
   console.log(JSON.stringify({mode,systemCode:(cause as NodeJS.ErrnoException|undefined)?.code??null,error,attempts,launches}).replaceAll(root,'<fixture-root>'));
   if(mode==='normal')expect(error.code).toBe('invalid_request');
   else {expect((cause as NodeJS.ErrnoException).code).toBe(mode==='file'?'ENOTDIR':'EACCES');expect(error).toMatchObject({code:'resource_constraint',details:{resource:'file-size',reported_limits:null}});expect(JSON.stringify(error)).not.toContain(mode==='file'?'ENOTDIR':'EACCES');}
  }
 }finally{await client.close();await server.close();await provider.close();rootSpy.mockRestore();boundSpy.mockRestore();await chmod(denied,0o700);await rm(root,{recursive:true,force:true});}
},30000);

Expected behavior

A workspace-preparation error should preserve the meaningful filesystem reason and direct recovery to the temporary parent/access problem. A permission denial or non-directory parent does not establish an exceeded file-size allowance. Genuine storage-capacity failures should retain their actual cause and applicable recovery.

AnalysisResourceConstraintError denotes a measured resource or bounded admission refusal. The sibling packet provider already distinguishes ENOSPC/EDQUOT/EFBIG from other workspace failures and preserves system_code/reason. The tool contract requires meaningful facts and actual resource boundaries.

Impact and scope

P2: bounded setup diagnosis and recovery fail for an unusable host temporary parent. Two different real allocation errors become indistinguishable file-size refusals, so clients cannot direct the caller to fix the parent path or permissions. A usable writable temporary parent is an environmental workaround; the normal-parent control confirms allocation then proceeds to input admission.

The failure occurs before capture bytes are read. No successful packet decode, source corruption, retained-child leak or security-boundary crossing was observed. Actual Linux/Windows behavior, EROFS/disk-full cases and natural occurrence rates were not tested.

REA version

rea-agents 6.7.0; source revision 52415975942a4b9c53a817915b313ccc404f280e.

Runtime and platform

macOS 27.0.1 arm64, non-root POSIX account; repository-pinned Node.js v24.18.0 and Vitest 5.0.3. InMemoryTransport MCP. Supervised Node children supply version banners; successful native admission is explicitly simulated.

Target and artifact context

An absent capture path is supplied because both failing cases stop during private-workspace allocation, before file admission. One parent is a regular file (ENOTDIR); the other is an owned directory with mode 0500 (EACCES). The writable-parent control reaches the expected missing-file error.

Interface

MCP

Earliest failure stage

Setup or provider admission

Relevant output or evidence

Selected complete error projections from the executed test, alongside the errno caught at the real allocator:

{"mode":"file","systemCode":"ENOTDIR","error":{"code":"resource_constraint","category":"resource_constraint","message":"Cannot allocate the owned capture workspace","retryable":false,"remediation":{"action":"Review the reported worker file-size limits and write failure. Retry with a sufficient file-size allowance for the evidence reply; REA retains tighter inherited limits."},"details":{"operation":"inspect_pcap_capture","resource":"file-size","reason":"Cannot allocate the owned capture workspace","reported_limits":null}},"attempts":1,"launches":2}
{"mode":"denied","systemCode":"EACCES","error":{"code":"resource_constraint","category":"resource_constraint","message":"Cannot allocate the owned capture workspace","retryable":false,"remediation":{"action":"Review the reported worker file-size limits and write failure. Retry with a sufficient file-size allowance for the evidence reply; REA retains tighter inherited limits."},"details":{"operation":"inspect_pcap_capture","resource":"file-size","reason":"Cannot allocate the owned capture workspace","reported_limits":null}},"attempts":2,"launches":4}

Final run: one test passed. Three allocation attempts and six version-probe subprocesses occurred. For the normal-parent control, the third request returns invalid_request with an explicit ENOENT issue on path; it does not report a size constraint.

Typed outcome or recovery expected

Preserve an access/provider workspace failure with its system code and safe reason/coordinate, and recovery appropriate to that cause. Retain the separate privacy-capability case and real resource-limit behavior. No new public error type is prescribed.

Analysis or suspected cause

Source-derived: PcapSnapshot.create handles the privacy-capability error separately, then wraps every other allocation failure as a file-size constraint with null limits and only cause. PrivateRuntimeRoot.create performs mkdtemp on the selected temporary parent. PcapProvider preserves the resulting AnalysisError; the public resource projection does not expose Error.cause, so the allocator diagnosis is absent. The shared file-size fallback supplies unrelated worker/evidence-reply advice.

Additional context

Fetched upstream main aa51e4343963fdd0b97ec2ebdc3c41579fd9cb38 leaves the affected snapshot/provider/allocator unchanged. Its new fixed-allowance presentation branch does not apply to this constructor. Upstream was source-compared, not executed. #5632 concerns actual fixed input-size guards, #5940 concerns version-probe cleanup dependency reporting, #2822 concerns selected-file sharing violations, and PR #2507 covers host-tool/capability refusals plus privacy-capability retryability. None handles arbitrary non-privacy workspace allocation failures. No fix or restored Linux/Windows run is claimed.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6123 Originally assigned to: @kaoru0822-kitauji on GitHub. ### 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 Provider or capability discovery ### Observed behavior Public MCP `inspect_pcap_capture` reports real workspace-allocation `ENOTDIR` and `EACCES` failures as `resource_constraint` with `resource: "file-size"`, null reported limits and advice to increase the worker/evidence-reply file-size allowance. Neither errno nor its failing filesystem coordinate appears in the projected error. The message does identify the owned capture workspace. This was executed through the production MCP server, service, provider and real filesystem allocator. Wireshark version banners and successful native-resource admission were simulated on macOS, which normally refuses capture inspection before this stage. This is a controlled supported-admission reproduction, not a native Linux/Windows or real-Wireshark run. ### Reproduction Save this focused test under `tests/boundary/mcp/pcapWorkspaceAllocation.test.ts` in the source checkout and run `node node_modules/vitest/vitest.mjs run tests/boundary/mcp/pcapWorkspaceAllocation.test.ts --maxWorkers=1 --reporter=verbose`. The final executed test passed, asserting the current incorrect projections. Use a non-root POSIX account for the mode-0500 permission case. It chooses controlled parent paths only for capture roots, invokes real mkdtemp, and restores permissions and closes all owners afterward. A normal-parent control proceeds to the expected missing-capture input error. ```ts import { Client, InMemoryTransport } from '@modelcontextprotocol/client'; import { mkdir, mkdtemp, writeFile, chmod, rm } from 'node:fs/promises'; import { join } from 'node:path'; import { tmpdir } from 'node:os'; import { expect, it, vi } from 'vitest'; import * as native from '../../../src/pcap/PcapNativeBound.js'; import { PrivateRuntimeRoot } from '../../../src/process/PrivateRuntimeRoot.js'; import { PcapProvider } from '../../../src/pcap/PcapProvider.js'; import { spawnOwnedProviderProcess } from '../../../src/process/ProviderProcess.js'; import { createServer } from '../../../src/server/createServer.js'; import { createTestBinarySession } from '../../fixtures/binarySession.js'; import { parseMcpToolError } from '../../fixtures/mcpToolError.js'; it('projects actual workspace allocation failures after simulated native admission', async () => { const root=await mkdtemp(join(tmpdir(),'rea-workspace-test-')); const file=join(root,'ordinary-file');await writeFile(file,'not a directory'); const denied=join(root,'denied');await mkdir(denied,{recursive:true});await chmod(denied,0o500); let mode='file';let cause:unknown;let attempts=0; const create=PrivateRuntimeRoot.create.bind(PrivateRuntimeRoot); const rootSpy=vi.spyOn(PrivateRuntimeRoot,'create').mockImplementation(async options=>{ if(options?.prefix!=='rea-pcap-')return create(options); attempts++; try{return await create({...options,parent:mode==='file'?file:mode==='denied'?denied:root});} catch(error){cause=error;throw error;} }); const boundSpy=vi.spyOn(native,'preparePcapNativeBound').mockResolvedValue({nativeBound:{},limitations:['Native resource admission simulated on macOS']}); let launches=0; const provider=new PcapProvider({environment:{REA_TSHARK_COMMAND:process.execPath,REA_CAPINFOS_COMMAND:process.execPath},launcher:async spawn=>{launches++;return spawnOwnedProviderProcess({...spawn,command:process.execPath,arguments:['-e',`process.stdout.write(${JSON.stringify((launches % 2 ? 'TShark' : 'Capinfos') + ' (Wireshark) 4.6.6\n')})`]});}}); const session=createTestBinarySession(()=>{throw new Error('No native binary provider');}); const server=createServer({kind:'session',session},{pcapAnalysis:provider}); const client=new Client({name:'workspace-test',version:'1'}); const [ct,st]=InMemoryTransport.createLinkedPair(); try{ await server.connect(st);await client.connect(ct); for(mode of ['file','denied','normal']){ cause=undefined; const response=await client.callTool({name:'inspect_pcap_capture',arguments:{path:join(root,'missing.pcap')}}); const error=parseMcpToolError(response).error; console.log(JSON.stringify({mode,systemCode:(cause as NodeJS.ErrnoException|undefined)?.code??null,error,attempts,launches}).replaceAll(root,'<fixture-root>')); if(mode==='normal')expect(error.code).toBe('invalid_request'); else {expect((cause as NodeJS.ErrnoException).code).toBe(mode==='file'?'ENOTDIR':'EACCES');expect(error).toMatchObject({code:'resource_constraint',details:{resource:'file-size',reported_limits:null}});expect(JSON.stringify(error)).not.toContain(mode==='file'?'ENOTDIR':'EACCES');} } }finally{await client.close();await server.close();await provider.close();rootSpy.mockRestore();boundSpy.mockRestore();await chmod(denied,0o700);await rm(root,{recursive:true,force:true});} },30000); ``` ### Expected behavior A workspace-preparation error should preserve the meaningful filesystem reason and direct recovery to the temporary parent/access problem. A permission denial or non-directory parent does not establish an exceeded file-size allowance. Genuine storage-capacity failures should retain their actual cause and applicable recovery. [AnalysisResourceConstraintError](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/analysisErrorCore.ts#L24-L40) denotes a measured resource or bounded admission refusal. The [sibling packet provider](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/captures/TsharkCaptureProvider.ts#L498-L522) already distinguishes ENOSPC/EDQUOT/EFBIG from other workspace failures and preserves system_code/reason. The [tool contract](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/docs/tool-design.md#L46-L68) requires meaningful facts and actual resource boundaries. ### Impact and scope P2: bounded setup diagnosis and recovery fail for an unusable host temporary parent. Two different real allocation errors become indistinguishable file-size refusals, so clients cannot direct the caller to fix the parent path or permissions. A usable writable temporary parent is an environmental workaround; the normal-parent control confirms allocation then proceeds to input admission. The failure occurs before capture bytes are read. No successful packet decode, source corruption, retained-child leak or security-boundary crossing was observed. Actual Linux/Windows behavior, EROFS/disk-full cases and natural occurrence rates were not tested. ### REA version `rea-agents` 6.7.0; source revision `52415975942a4b9c53a817915b313ccc404f280e`. ### Runtime and platform macOS 27.0.1 arm64, non-root POSIX account; repository-pinned Node.js v24.18.0 and Vitest 5.0.3. InMemoryTransport MCP. Supervised Node children supply version banners; successful native admission is explicitly simulated. ### Target and artifact context An absent capture path is supplied because both failing cases stop during private-workspace allocation, before file admission. One parent is a regular file (ENOTDIR); the other is an owned directory with mode 0500 (EACCES). The writable-parent control reaches the expected missing-file error. ### Interface MCP ### Earliest failure stage Setup or provider admission ### Relevant output or evidence Selected complete error projections from the executed test, alongside the errno caught at the real allocator: ```json {"mode":"file","systemCode":"ENOTDIR","error":{"code":"resource_constraint","category":"resource_constraint","message":"Cannot allocate the owned capture workspace","retryable":false,"remediation":{"action":"Review the reported worker file-size limits and write failure. Retry with a sufficient file-size allowance for the evidence reply; REA retains tighter inherited limits."},"details":{"operation":"inspect_pcap_capture","resource":"file-size","reason":"Cannot allocate the owned capture workspace","reported_limits":null}},"attempts":1,"launches":2} {"mode":"denied","systemCode":"EACCES","error":{"code":"resource_constraint","category":"resource_constraint","message":"Cannot allocate the owned capture workspace","retryable":false,"remediation":{"action":"Review the reported worker file-size limits and write failure. Retry with a sufficient file-size allowance for the evidence reply; REA retains tighter inherited limits."},"details":{"operation":"inspect_pcap_capture","resource":"file-size","reason":"Cannot allocate the owned capture workspace","reported_limits":null}},"attempts":2,"launches":4} ``` Final run: one test passed. Three allocation attempts and six version-probe subprocesses occurred. For the normal-parent control, the third request returns `invalid_request` with an explicit ENOENT issue on `path`; it does not report a size constraint. ### Typed outcome or recovery expected Preserve an access/provider workspace failure with its system code and safe reason/coordinate, and recovery appropriate to that cause. Retain the separate privacy-capability case and real resource-limit behavior. No new public error type is prescribed. ### Analysis or suspected cause Source-derived: [PcapSnapshot.create](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/pcap/PcapSnapshot.ts#L43-L64) handles the privacy-capability error separately, then wraps every other allocation failure as a file-size constraint with null limits and only `cause`. [PrivateRuntimeRoot.create](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/process/PrivateRuntimeRoot.ts#L52-L76) performs mkdtemp on the selected temporary parent. [PcapProvider](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/pcap/PcapProvider.ts#L538-L581) preserves the resulting AnalysisError; the public resource projection does not expose Error.cause, so the allocator diagnosis is absent. [The shared file-size fallback](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/analysisErrorPresentation.ts#L67-L79) supplies unrelated worker/evidence-reply advice. ### Additional context Fetched upstream main `aa51e4343963fdd0b97ec2ebdc3c41579fd9cb38` leaves the affected snapshot/provider/allocator unchanged. Its new fixed-allowance presentation branch does not apply to this constructor. Upstream was source-compared, not executed. #5632 concerns actual fixed input-size guards, #5940 concerns version-probe cleanup dependency reporting, #2822 concerns selected-file sharing violations, and PR #2507 covers host-tool/capability refusals plus privacy-capability retryability. None handles arbitrary non-privacy workspace allocation failures. No fix or restored Linux/Windows run is claimed.
Author
Owner

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

确认,这是错误翻译链路里最伤的一类:把 ENOTDIR/EACCES 归成 resource: "file-size",建议还让用户去调 file-size 限额——分类错导致行动错,用户排查方向整个被带偏。建议错误投影时把 errno 和失败的 filesystem coordinate(哪怕只是出问题的路径段)保留下来,按 errno 映射独立分类:EACCES → permission,ENOTDIR/ENOENT → workspace 损坏或路径缺失,和 #5632/#5940 的约束分类口径对齐。另外 advice 建议字段最好从错误模板里拆出来按分类动态生成,分类都错了就别再输出误导性建议。

<!-- gh-comment-id:6330452438 --> @XinwangTao commented on GitHub (Oct 22, 2026): 确认,这是错误翻译链路里最伤的一类:把 ENOTDIR/EACCES 归成 resource: "file-size",建议还让用户去调 file-size 限额——分类错导致行动错,用户排查方向整个被带偏。建议错误投影时把 errno 和失败的 filesystem coordinate(哪怕只是出问题的路径段)保留下来,按 errno 映射独立分类:EACCES → permission,ENOTDIR/ENOENT → workspace 损坏或路径缺失,和 #5632/#5940 的约束分类口径对齐。另外 advice 建议字段最好从错误模板里拆出来按分类动态生成,分类都错了就别再输出误导性建议。
Author
Owner

@kaoru0822-kitauji commented on GitHub (Oct 22, 2026):

Reproduced on latest main (d77bc0e8a), macOS arm64, non-root account, running the focused test from the report verbatim (native admission simulated as in the report):

  • ENOTDIR (regular-file parent) and EACCES (mode-0500 parent) from the real mkdtemp both project as resource_constraint / resource: "file-size" / reported_limits: null with the worker file-size remediation; neither errno appears in the projection.
  • Writable-parent control returns invalid_request (missing capture).

Source check: the same errno-blind file-size mapping also applies to pcap snapshot writes inside the allocated workspace (Owned capture snapshot I/O failed) and to Flutter's owned snapshot storage; TShark and sigrok carry duplicate copies of the correct ENOSPC/EDQUOT/EFBIG split. Working on a shared fix.

<!-- gh-comment-id:6331382264 --> @kaoru0822-kitauji commented on GitHub (Oct 22, 2026): Reproduced on latest main (`d77bc0e8a`), macOS arm64, non-root account, running the focused test from the report verbatim (native admission simulated as in the report): - `ENOTDIR` (regular-file parent) and `EACCES` (mode-0500 parent) from the real `mkdtemp` both project as `resource_constraint` / `resource: "file-size"` / `reported_limits: null` with the worker file-size remediation; neither errno appears in the projection. - Writable-parent control returns `invalid_request` (missing capture). Source check: the same errno-blind `file-size` mapping also applies to pcap snapshot writes inside the allocated workspace (`Owned capture snapshot I/O failed`) and to Flutter's owned snapshot storage; TShark and sigrok carry duplicate copies of the correct ENOSPC/EDQUOT/EFBIG split. Working on a shared fix.
Author
Owner

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

修复已合并,方向正确:Pcap workspace allocation 的 ENOTDIR/EACCES 不再被压成 file-size 资源限制,而是按系统原因上报。建议给 shared file-size fallback 补一条边界测试:当系统错误与 size 限制同时存在时,确认优先上报系统 cause、file-size 只作为兜底 fallback,避免未来重构把两类错误再次混在一起。

<!-- gh-comment-id:6332654155 --> @XinwangTao commented on GitHub (Oct 22, 2026): 修复已合并,方向正确:Pcap workspace allocation 的 ENOTDIR/EACCES 不再被压成 file-size 资源限制,而是按系统原因上报。建议给 shared file-size fallback 补一条边界测试:当系统错误与 size 限制同时存在时,确认优先上报系统 cause、file-size 只作为兜底 fallback,避免未来重构把两类错误再次混在一起。
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#3755
No description provided.