[GH-ISSUE #6028] [Bug] Frida resume state refusals return unsupported-provider errors and unrelated recovery #3675

Closed
opened 2026-10-31 00:42:57 +00:00 by bsakaguchi · 1 comment
Owner

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

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

Calling resume_frida_session on an attached session or a spawned session already resumed once returns capability_unavailable / unsupported_provider. Both the public message and remediation say to choose another analysis or target. The actual problem is that this existing session has no paused spawn to resume. Its accurate state-specific reason appears only in details.reason. This was executed through registered MCP tools with a production session manager and simulated native bindings.

Reproduction

Save this focused test as tests/boundary/mcp/resumeState.test.ts in the source checkout with locked dependencies and runtime artifacts, then run npx --no-install vitest run tests/boundary/mcp/resumeState.test.ts --maxWorkers 1. The two cases use the production Frida manager and registered MCP tools; only native device/session objects are simulated. No real process is spawned or attached. Assertions characterize the current incorrect result. Remove the test afterward.

import { Client, InMemoryTransport } from '@modelcontextprotocol/client';
import { expect, it } from 'vitest';
import { FridaInstrumentationManager } from '../../../src/frida/FridaInstrumentationManager.js';
import { createServer } from '../../../src/server/createServer.js';
import { createTestBinarySession } from '../../fixtures/binarySession.js';
import { parseMcpToolError } from '../../fixtures/mcpToolError.js';
import { silentLogger } from '../../../src/logger.js';
import type { DeviceManager } from 'frida';

it.each(['attach', 'spawn'] as const)('Frida resume state: %s', async (mode) => {
  let detached = false;
  let resumes = 0;
  const nativeSession = { pid: 512, isDetached: () => detached,
    detached: { connect() {}, disconnect() {} }, async detach() { detached = true; } };
  const device = { id: 'local', name: 'fixture', type: 'local',
    async getProcessByPid() { return {pid: 512, name: 'fixture'}; },
    async spawn() { return 512; }, async attach() { return nativeSession; },
    async resume() { resumes++; } };
  const manager = new FridaInstrumentationManager({ deviceManager: {
    async getDeviceById() { return device; }, async enumerateDevices() { return [device]; },
  } as unknown as DeviceManager });
  const session = createTestBinarySession(() => { throw new Error('unused'); });
  const server = createServer({kind:'session', session}, {fridaInstrumentation: manager, logger: silentLogger});
  const client = new Client({name:'resume-state',version:'1'});
  const [ct,st] = InMemoryTransport.createLinkedPair();
  try {
    await server.connect(st); await client.connect(ct);
    const started = await client.callTool({name:'start_frida_session', arguments: mode === 'attach'
      ? {mode, device_id:'local', pid:512} : {mode, device_id:'local', program:'fixture'}});
    expect(started.isError).not.toBe(true);
    const session_id = (started.structuredContent as {session_id:string}).session_id;
    expect(session_id).toBeTruthy();
    if (mode === 'spawn') {
      const first = await client.callTool({name:'resume_frida_session',arguments:{session_id}});
      expect(first.isError).not.toBe(true); expect(resumes).toBe(1);
    }
    const {error} = parseMcpToolError(await client.callTool({name:'resume_frida_session',arguments:{session_id}}));
    expect(error).toMatchObject({code:'capability_unavailable',category:'unsupported_provider',
      message:'This analysis is unavailable for the current target. Choose another analysis or target.',
      details:{reason:'Only a paused process spawned by this session can be resumed.'}});
    expect(error.remediation.action).toBe(error.message);
    expect(resumes).toBe(mode === 'spawn' ? 1 : 0);
    console.log('FRIDA', mode, JSON.stringify(error));
  } finally { await client.close(); await server.close(); await session.close(); await manager.closeAll(); }
});

Expected behavior

The resume contract explicitly applies to paused spawns and says attached processes are already running. Report the session-state constraint directly, with recovery that says to omit resume for an attach or already-resumed session. A caller-input/state error with a session_id issue would distinguish this from provider availability; at minimum, preserve state-specific public message and remediation. The operation should continue refusing to resume these sessions.

Impact and scope

P2: a deterministic caller state error is exposed as an unsupported provider, and the recovery directs callers toward changing targets or analysis rather than handling an already-running session. Both cases reproduced. The first spawn resume succeeds; the second makes no additional native resume call. Manually inspect details.reason and skip resume for these sessions as a practical workaround. No retrying client, provider outage, data loss or real-device failure frequency was observed.

REA version

rea-agents 6.7.0; source revision 52415975942a4b9c53a817915b313ccc404f280e. Fetched main 6d356b84344659085f2b912857b5e27fe0160c9b has unchanged relevant code.

Runtime and platform

Node.js v26.12.0; macOS 27.0.1 arm64; Vitest 5.0.3. Native providers were simulated. The repository-pinned Node 24.18.0 was not exercised.

Target and artifact context

Synthetic local attach and spawn sessions with PID 512; no target code or Frida script ran.

Interface

MCP

Earliest failure stage

Request parsing or validation

Relevant output or evidence

Both rejected MCP calls returned:

{"code":"capability_unavailable","category":"unsupported_provider","message":"This analysis is unavailable for the current target. Choose another analysis or target.","retryable":false,"remediation":{"action":"This analysis is unavailable for the current target. Choose another analysis or target."},"details":{"provider_id":"frida","operation":"resume_frida_session","reason":"Only a paused process spawned by this session can be resumed."}}

The attach case recorded zero native resumes. The spawn case recorded one successful native resume, then the second request returned this error with the counter still one. Both cases passed in the final focused run; the complete file passed four cases including separate Ghidra checks. Cleanup closed all sessions and MCP endpoints.

Typed outcome or recovery expected

Return a state-specific diagnostic, ideally invalid_request / invalid_input with an issue naming session_id. Explain that only a spawn still paused by this session can resume; attached or already-resumed sessions require no resume. Do not imply that the Frida provider is unsupported.

Analysis or suspected cause

Source-derived: the mode/state guard constructs AnalysisCapabilityUnavailableError with only its internal reason. The category mapping labels it unsupported_provider, and the fallback message replaces its caller-visible explanation with generic analysis/target advice. Other Frida session-selection errors already retain argument-specific correction details.

Additional context

Related reports #2213 and merged #2217 repaired missing issues for unknown session/script IDs, not valid sessions in a nonresumable state. #3978 concerns detachment observed during native resume settlement, and #2535 concerns cancellation of queued operations. #5864 and #5865 concern one-shot CLI Evidence shape and final-cleanup projection. Their fixes address different contracts and producers. Persistent resume is an MCP operation without a dedicated CLI command.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6028 ### 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 Calling `resume_frida_session` on an attached session or a spawned session already resumed once returns `capability_unavailable` / `unsupported_provider`. Both the public message and remediation say to choose another analysis or target. The actual problem is that this existing session has no paused spawn to resume. Its accurate state-specific reason appears only in `details.reason`. This was executed through registered MCP tools with a production session manager and simulated native bindings. ### Reproduction Save this focused test as `tests/boundary/mcp/resumeState.test.ts` in the source checkout with locked dependencies and runtime artifacts, then run `npx --no-install vitest run tests/boundary/mcp/resumeState.test.ts --maxWorkers 1`. The two cases use the production Frida manager and registered MCP tools; only native device/session objects are simulated. No real process is spawned or attached. Assertions characterize the current incorrect result. Remove the test afterward. ```ts import { Client, InMemoryTransport } from '@modelcontextprotocol/client'; import { expect, it } from 'vitest'; import { FridaInstrumentationManager } from '../../../src/frida/FridaInstrumentationManager.js'; import { createServer } from '../../../src/server/createServer.js'; import { createTestBinarySession } from '../../fixtures/binarySession.js'; import { parseMcpToolError } from '../../fixtures/mcpToolError.js'; import { silentLogger } from '../../../src/logger.js'; import type { DeviceManager } from 'frida'; it.each(['attach', 'spawn'] as const)('Frida resume state: %s', async (mode) => { let detached = false; let resumes = 0; const nativeSession = { pid: 512, isDetached: () => detached, detached: { connect() {}, disconnect() {} }, async detach() { detached = true; } }; const device = { id: 'local', name: 'fixture', type: 'local', async getProcessByPid() { return {pid: 512, name: 'fixture'}; }, async spawn() { return 512; }, async attach() { return nativeSession; }, async resume() { resumes++; } }; const manager = new FridaInstrumentationManager({ deviceManager: { async getDeviceById() { return device; }, async enumerateDevices() { return [device]; }, } as unknown as DeviceManager }); const session = createTestBinarySession(() => { throw new Error('unused'); }); const server = createServer({kind:'session', session}, {fridaInstrumentation: manager, logger: silentLogger}); const client = new Client({name:'resume-state',version:'1'}); const [ct,st] = InMemoryTransport.createLinkedPair(); try { await server.connect(st); await client.connect(ct); const started = await client.callTool({name:'start_frida_session', arguments: mode === 'attach' ? {mode, device_id:'local', pid:512} : {mode, device_id:'local', program:'fixture'}}); expect(started.isError).not.toBe(true); const session_id = (started.structuredContent as {session_id:string}).session_id; expect(session_id).toBeTruthy(); if (mode === 'spawn') { const first = await client.callTool({name:'resume_frida_session',arguments:{session_id}}); expect(first.isError).not.toBe(true); expect(resumes).toBe(1); } const {error} = parseMcpToolError(await client.callTool({name:'resume_frida_session',arguments:{session_id}})); expect(error).toMatchObject({code:'capability_unavailable',category:'unsupported_provider', message:'This analysis is unavailable for the current target. Choose another analysis or target.', details:{reason:'Only a paused process spawned by this session can be resumed.'}}); expect(error.remediation.action).toBe(error.message); expect(resumes).toBe(mode === 'spawn' ? 1 : 0); console.log('FRIDA', mode, JSON.stringify(error)); } finally { await client.close(); await server.close(); await session.close(); await manager.closeAll(); } }); ``` ### Expected behavior The [resume contract](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/contracts/fridaToolContracts.ts#L186-L193) explicitly applies to paused spawns and says attached processes are already running. Report the session-state constraint directly, with recovery that says to omit resume for an attach or already-resumed session. A caller-input/state error with a `session_id` issue would distinguish this from provider availability; at minimum, preserve state-specific public message and remediation. The operation should continue refusing to resume these sessions. ### Impact and scope P2: a deterministic caller state error is exposed as an unsupported provider, and the recovery directs callers toward changing targets or analysis rather than handling an already-running session. Both cases reproduced. The first spawn resume succeeds; the second makes no additional native resume call. Manually inspect `details.reason` and skip resume for these sessions as a practical workaround. No retrying client, provider outage, data loss or real-device failure frequency was observed. ### REA version rea-agents 6.7.0; source revision `52415975942a4b9c53a817915b313ccc404f280e`. Fetched main `6d356b84344659085f2b912857b5e27fe0160c9b` has unchanged relevant code. ### Runtime and platform Node.js v26.12.0; macOS 27.0.1 arm64; Vitest 5.0.3. Native providers were simulated. The repository-pinned Node 24.18.0 was not exercised. ### Target and artifact context Synthetic local attach and spawn sessions with PID 512; no target code or Frida script ran. ### Interface MCP ### Earliest failure stage Request parsing or validation ### Relevant output or evidence Both rejected MCP calls returned: ```json {"code":"capability_unavailable","category":"unsupported_provider","message":"This analysis is unavailable for the current target. Choose another analysis or target.","retryable":false,"remediation":{"action":"This analysis is unavailable for the current target. Choose another analysis or target."},"details":{"provider_id":"frida","operation":"resume_frida_session","reason":"Only a paused process spawned by this session can be resumed."}} ``` The attach case recorded zero native resumes. The spawn case recorded one successful native resume, then the second request returned this error with the counter still one. Both cases passed in the final focused run; the complete file passed four cases including separate Ghidra checks. Cleanup closed all sessions and MCP endpoints. ### Typed outcome or recovery expected Return a state-specific diagnostic, ideally `invalid_request` / `invalid_input` with an issue naming `session_id`. Explain that only a spawn still paused by this session can resume; attached or already-resumed sessions require no resume. Do not imply that the Frida provider is unsupported. ### Analysis or suspected cause Source-derived: the [mode/state guard](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/frida/FridaInstrumentationManager.ts#L543-L553) constructs `AnalysisCapabilityUnavailableError` with only its internal reason. The [category mapping](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/analysisErrorPresentation.ts#L225-L234) labels it `unsupported_provider`, and the [fallback message](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/analysisErrorPresentation.ts#L374-L378) replaces its caller-visible explanation with generic analysis/target advice. Other Frida [session-selection errors](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/application/frida/fridaInputErrors.ts#L1-L27) already retain argument-specific correction details. ### Additional context Related reports #2213 and merged #2217 repaired missing issues for unknown session/script IDs, not valid sessions in a nonresumable state. #3978 concerns detachment observed during native resume settlement, and #2535 concerns cancellation of queued operations. #5864 and #5865 concern one-shot CLI Evidence shape and final-cleanup projection. Their fixes address different contracts and producers. Persistent resume is an MCP operation without a dedicated CLI command.
Author
Owner

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

Checked the reported fix on main at d87e73f2db.

The attach/already-resumed guard now returns AnalysisInputError with a session_id issue and a state-specific userMessage. The MCP test exercises both states, checks invalid_request, and verifies no second native resume. It preserves refusal rather than changing session behavior.

Implementation: FridaInstrumentationManager.ts, fridaLifecycleMcp.test.ts.

The relevant regression checks in fridaLifecycleMcp.test.ts passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. Closing as completed on main.

<!-- gh-comment-id:6380717671 --> @coygeek commented on GitHub (Oct 24, 2026): Checked the reported fix on `main` at d87e73f2db50be72d33a4b3c7199af35518b441c. The attach/already-resumed guard now returns AnalysisInputError with a session_id issue and a state-specific userMessage. The MCP test exercises both states, checks invalid_request, and verifies no second native resume. It preserves refusal rather than changing session behavior. Implementation: [FridaInstrumentationManager.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/src/frida/FridaInstrumentationManager.ts#L589), [fridaLifecycleMcp.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/tests/boundary/mcp/fridaLifecycleMcp.test.ts#L1459). The relevant regression checks in [fridaLifecycleMcp.test.ts](https://github.com/morluto/rea/blob/d87e73f2db50be72d33a4b3c7199af35518b441c/tests/boundary/mcp/fridaLifecycleMcp.test.ts) passed in the focused test run on this revision. This closure is based on the current source and those checks; provider fixtures do not establish a real-engine run. 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#3675
No description provided.