[GH-ISSUE #6122] [Bug] CDP operation relabeling drops existing target-authorization detail #3756

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/6122

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

Process capture or runtime observation

Observed behavior

One disallowed target produces different diagnostics depending on the requested browser operation. inspectPage retains the detail naming disallowed-page and its HTTP(S)/origin-scope requirement. analyzeBundle, observeSession, discoverWebMcpTools and captureScreenshot lose that already-created detail and return generic rea doctor advice instead. Each still correctly refuses the target.

Reproduction

Executed the production provider against the existing loopback CDP discovery fixture. Save the following as tests/boundary/browser/operationDetail.repro.test.ts and run it with the locked dependencies:

node node_modules/vitest/vitest.mjs run tests/boundary/browser/operationDetail.repro.test.ts --maxWorkers 1
import { expect, it } from 'vitest';
import { CdpBrowserProvider } from '../../../src/browser/CdpBrowserProvider.js';
import { projectAnalysisError } from '../../../src/domain/analysisErrorProjection.js';
import { inspectWebPageInputSchema } from '../../../src/domain/browserObservation.js';
import { captureWebScreenshotInputSchema } from '../../../src/domain/webScreenshot.js';
import { analyzeWebBundleInputSchema } from '../../../src/domain/webBundleAnalysis.js';
import { observeWebSessionInputSchema } from '../../../src/domain/browserSession.js';
import { discoverWebMcpToolsInputSchema } from '../../../src/domain/webMcpDiscovery.js';
import { startFakeCdpBrowser } from '../../fixtures/fakeCdpBrowser.js';
it('keeps inspect detail but loses the same authorization detail in other operations', async () => {
 const browser=await startFakeCdpBrowser(); const provider=new CdpBrowserProvider();
 try {
  const input={cdp_endpoint:browser.endpoint,target_id:'disallowed-page',allowed_origins:[browser.allowedOrigin],observation_ms:0};
  const calls = [
   ['inspect',()=>provider.inspectPage(inspectWebPageInputSchema.parse(input))],
   ['bundle',()=>provider.analyzeBundle(analyzeWebBundleInputSchema.parse(input))],
   ['observe',()=>provider.observeSession(observeWebSessionInputSchema.parse({cdp_endpoint:input.cdp_endpoint,target_id:input.target_id,allowed_origins:input.allowed_origins}))],
   ['webmcp',()=>provider.discoverWebMcpTools(discoverWebMcpToolsInputSchema.parse({cdp_endpoint:input.cdp_endpoint,target_id:input.target_id,allowed_origins:input.allowed_origins}))],
   ['screenshot',()=>provider.captureScreenshot(captureWebScreenshotInputSchema.parse({cdp_endpoint:input.cdp_endpoint,target_id:input.target_id,allowed_origins:input.allowed_origins}))],
  ] as const;
  for (const [name,call] of calls){
   const result=await call();expect(result.ok).toBe(false);if(result.ok)throw Error('expected refusal');
   const projected=projectAnalysisError(result.error);console.log('CDP',name,JSON.stringify(projected));
   expect(projected.details).toMatchObject({reason:'target_not_allowed'});
   expect(projected.message).toContain(name==='inspect'?'Target disallowed-page must be an HTTP(S) page':'rea doctor');
   if(name!=='inspect')expect(result.error.cause).toMatchObject({operation:'inspect_web_page',userMessage:expect.stringContaining('Target disallowed-page')});
  }
  expect(browser.commands).toEqual([]);expect(browser.openedConnectionCount()).toBe(0);
 } finally {await provider.close();await browser.close();}
});


This is a deterministic production-provider reproduction with simulated discovery metadata. No Chrome/Electron executable or CLI/MCP transport was exercised. The test verifies that the retained cause still has the original detail and that refusal happens before any WebSocket connection or CDP command. Assertions characterize the defect.

Expected behavior

Keep the selected-target refusal and preserve the existing target-specific detail for every caller operation. The authorization owner already creates that fact. Relabeling the operation must not discard it. This requires no change to origin authorization, public error code or retryability.

Impact and scope

P3: known target-selection detail and useful recovery context are lost from the displayed error for a correctly refused selection. The origin policy stays effective. No valid-input failure, data loss, actual agent retry loop or live-browser recovery was observed. A caller can compare discovery metadata with the request origin list manually. The executed matrix covers an out-of-scope discovered page, not every other target_not_allowed producer.

REA version

rea-agents 6.7.0; source revision 52415975942a4b9c53a817915b313ccc404f280e.

Runtime and platform

Node.js v26.12.0; macOS 27.0.1 arm64; rea-agents 6.7.0 source checkout with existing build and locked dependencies. The repository Node.js 24.18.0 pin was not separately exercised. Locked Vitest 5.0.3.

Target and artifact context

Existing fake CDP endpoint advertising a page named disallowed-page outside the selected allowed origin. Refusal precedes attachment; zero WebSocket connections and zero CDP commands were observed.

Interface

Other

Earliest failure stage

Transport or result projection

Relevant output or evidence

Observed inspectPage projection:

{"code":"target_unavailable","message":"Target disallowed-page must be an HTTP(S) page in the selected origin scope.","details":{"operation":"inspect_web_page","reason":"target_not_allowed"}}

Observed bundle projection, with equivalent messages for the other three operations:

{"code":"target_unavailable","category":"unavailable","message":"Analysis could not complete. Run `rea doctor`, then try again.","retryable":false,"remediation":{"action":"Analysis could not complete. Run `rea doctor`, then try again."},"details":{"operation":"analyze_web_bundle","reason":"target_not_allowed"}}

The replacement error still has a cause whose operation is inspect_web_page and whose userMessage contains the rejected target detail, proving where information is lost.

Typed outcome or recovery expected

Keep target_unavailable/unavailable and target_not_allowed. Preserve the existing HTTP(S)/origin-scope explanation and rejected target ID when attributing the failure to the requested operation. Do not substitute unrelated host diagnostics.

Analysis or suspected cause

withTargetSession receives the real operation but authorizeTarget hardcodes inspect_web_page. providerError then constructs a new cross-operation error with only cause, losing the original detail. BrowserObservationError has no default message for target_not_allowed, which exposes the generic final presentation fallback. Passing the actual operation into authorization or preserving existing detail during relabeling addresses this producer; a universal origin-specific message for all target_not_allowed reasons would be inaccurate.

Additional context

Fetched main aa51e4343963fdd0b97ec2ebdc3c41579fd9cb38 leaves these relevant lines unchanged; no newer runtime was executed. #5994 covers a different producer: the later CdpScreenshot current-frame scope check creates an error without detail. Giving that producer frame-scope context would not fix this loss of an already-created CdpAuthorizedTarget detail, and correcting operation propagation here would not repair #5994. #5947 is target_not_found detail overriding refresh guidance, #5724 is navigation/target_changed, and #5753 concerns Inspector attachment/runtime-role guards. Closed #3126 and PRs #3140/#3143 concern service-worker policy. This report does not include the separate Electron detail-less refusal path. No last known good version was established.

Originally created by @coygeek on GitHub (Oct 21, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/6122 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 Process capture or runtime observation ### Observed behavior One disallowed target produces different diagnostics depending on the requested browser operation. `inspectPage` retains the detail naming `disallowed-page` and its HTTP(S)/origin-scope requirement. `analyzeBundle`, `observeSession`, `discoverWebMcpTools` and `captureScreenshot` lose that already-created detail and return generic `rea doctor` advice instead. Each still correctly refuses the target. ### Reproduction Executed the production provider against the existing loopback CDP discovery fixture. Save the following as `tests/boundary/browser/operationDetail.repro.test.ts` and run it with the locked dependencies: ```sh node node_modules/vitest/vitest.mjs run tests/boundary/browser/operationDetail.repro.test.ts --maxWorkers 1 ``` ```ts import { expect, it } from 'vitest'; import { CdpBrowserProvider } from '../../../src/browser/CdpBrowserProvider.js'; import { projectAnalysisError } from '../../../src/domain/analysisErrorProjection.js'; import { inspectWebPageInputSchema } from '../../../src/domain/browserObservation.js'; import { captureWebScreenshotInputSchema } from '../../../src/domain/webScreenshot.js'; import { analyzeWebBundleInputSchema } from '../../../src/domain/webBundleAnalysis.js'; import { observeWebSessionInputSchema } from '../../../src/domain/browserSession.js'; import { discoverWebMcpToolsInputSchema } from '../../../src/domain/webMcpDiscovery.js'; import { startFakeCdpBrowser } from '../../fixtures/fakeCdpBrowser.js'; it('keeps inspect detail but loses the same authorization detail in other operations', async () => { const browser=await startFakeCdpBrowser(); const provider=new CdpBrowserProvider(); try { const input={cdp_endpoint:browser.endpoint,target_id:'disallowed-page',allowed_origins:[browser.allowedOrigin],observation_ms:0}; const calls = [ ['inspect',()=>provider.inspectPage(inspectWebPageInputSchema.parse(input))], ['bundle',()=>provider.analyzeBundle(analyzeWebBundleInputSchema.parse(input))], ['observe',()=>provider.observeSession(observeWebSessionInputSchema.parse({cdp_endpoint:input.cdp_endpoint,target_id:input.target_id,allowed_origins:input.allowed_origins}))], ['webmcp',()=>provider.discoverWebMcpTools(discoverWebMcpToolsInputSchema.parse({cdp_endpoint:input.cdp_endpoint,target_id:input.target_id,allowed_origins:input.allowed_origins}))], ['screenshot',()=>provider.captureScreenshot(captureWebScreenshotInputSchema.parse({cdp_endpoint:input.cdp_endpoint,target_id:input.target_id,allowed_origins:input.allowed_origins}))], ] as const; for (const [name,call] of calls){ const result=await call();expect(result.ok).toBe(false);if(result.ok)throw Error('expected refusal'); const projected=projectAnalysisError(result.error);console.log('CDP',name,JSON.stringify(projected)); expect(projected.details).toMatchObject({reason:'target_not_allowed'}); expect(projected.message).toContain(name==='inspect'?'Target disallowed-page must be an HTTP(S) page':'rea doctor'); if(name!=='inspect')expect(result.error.cause).toMatchObject({operation:'inspect_web_page',userMessage:expect.stringContaining('Target disallowed-page')}); } expect(browser.commands).toEqual([]);expect(browser.openedConnectionCount()).toBe(0); } finally {await provider.close();await browser.close();} }); ``` This is a deterministic production-provider reproduction with simulated discovery metadata. No Chrome/Electron executable or CLI/MCP transport was exercised. The test verifies that the retained cause still has the original detail and that refusal happens before any WebSocket connection or CDP command. Assertions characterize the defect. ### Expected behavior Keep the selected-target refusal and preserve the existing target-specific detail for every caller operation. The [authorization owner](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/browser/CdpAuthorizedTarget.ts#L18-L34) already creates that fact. Relabeling the operation must not discard it. This requires no change to origin authorization, public error code or retryability. ### Impact and scope P3: known target-selection detail and useful recovery context are lost from the displayed error for a correctly refused selection. The origin policy stays effective. No valid-input failure, data loss, actual agent retry loop or live-browser recovery was observed. A caller can compare discovery metadata with the request origin list manually. The executed matrix covers an out-of-scope discovered page, not every other target_not_allowed producer. ### REA version rea-agents 6.7.0; source revision `52415975942a4b9c53a817915b313ccc404f280e`. ### Runtime and platform Node.js v26.12.0; macOS 27.0.1 arm64; rea-agents 6.7.0 source checkout with existing build and locked dependencies. The repository Node.js 24.18.0 pin was not separately exercised. Locked Vitest 5.0.3. ### Target and artifact context Existing fake CDP endpoint advertising a page named disallowed-page outside the selected allowed origin. Refusal precedes attachment; zero WebSocket connections and zero CDP commands were observed. ### Interface Other ### Earliest failure stage Transport or result projection ### Relevant output or evidence Observed inspectPage projection: ```json {"code":"target_unavailable","message":"Target disallowed-page must be an HTTP(S) page in the selected origin scope.","details":{"operation":"inspect_web_page","reason":"target_not_allowed"}} ``` Observed bundle projection, with equivalent messages for the other three operations: ```json {"code":"target_unavailable","category":"unavailable","message":"Analysis could not complete. Run `rea doctor`, then try again.","retryable":false,"remediation":{"action":"Analysis could not complete. Run `rea doctor`, then try again."},"details":{"operation":"analyze_web_bundle","reason":"target_not_allowed"}} ``` The replacement error still has a cause whose operation is inspect_web_page and whose userMessage contains the rejected target detail, proving where information is lost. ### Typed outcome or recovery expected Keep target_unavailable/unavailable and target_not_allowed. Preserve the existing HTTP(S)/origin-scope explanation and rejected target ID when attributing the failure to the requested operation. Do not substitute unrelated host diagnostics. ### Analysis or suspected cause [withTargetSession](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/browser/CdpBrowserProvider.ts#L359-L391) receives the real operation but [authorizeTarget](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/browser/CdpBrowserProvider.ts#L546-L551) hardcodes inspect_web_page. [providerError](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/browser/CdpBrowserProvider.ts#L564-L581) then constructs a new cross-operation error with only cause, losing the original detail. [BrowserObservationError](https://github.com/morluto/rea/blob/52415975942a4b9c53a817915b313ccc404f280e/src/domain/browserObservationError.ts#L18-L61) has no default message for target_not_allowed, which exposes the generic final presentation fallback. Passing the actual operation into authorization or preserving existing detail during relabeling addresses this producer; a universal origin-specific message for all target_not_allowed reasons would be inaccurate. ### Additional context Fetched main `aa51e4343963fdd0b97ec2ebdc3c41579fd9cb38` leaves these relevant lines unchanged; no newer runtime was executed. #5994 covers a different producer: the later CdpScreenshot current-frame scope check creates an error without detail. Giving that producer frame-scope context would not fix this loss of an already-created CdpAuthorizedTarget detail, and correcting operation propagation here would not repair #5994. #5947 is target_not_found detail overriding refresh guidance, #5724 is navigation/target_changed, and #5753 concerns Inspector attachment/runtime-role guards. Closed #3126 and PRs #3140/#3143 concern service-worker policy. This report does not include the separate Electron detail-less refusal path. No last known good version was established.
Author
Owner

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

确认,问题出在 relabel 把已生成的 authorization detail 整体覆盖掉了:同一个 disallowed target、同一个授权失败,inspectPage 还能给出 disallowed-page + origin-scope 要求,其它四个操作却退化回泛泛的 rea doctor advice——这让 MCP 客户端无法统一处理授权拒绝,它从 inspectPage 学会的解释在别的操作上根本用不上。建议 relabel 只改写 operation 身份字段(operation id/名称),绝不碰 detail 载荷;并加一条跨操作断言:同一拒绝目标下所有浏览器操作返回相同的授权 detail。

<!-- gh-comment-id:6330459457 --> @XinwangTao commented on GitHub (Oct 22, 2026): 确认,问题出在 relabel 把已生成的 authorization detail 整体覆盖掉了:同一个 disallowed target、同一个授权失败,inspectPage 还能给出 disallowed-page + origin-scope 要求,其它四个操作却退化回泛泛的 rea doctor advice——这让 MCP 客户端无法统一处理授权拒绝,它从 inspectPage 学会的解释在别的操作上根本用不上。建议 relabel 只改写 operation 身份字段(operation id/名称),绝不碰 detail 载荷;并加一条跨操作断言:同一拒绝目标下所有浏览器操作返回相同的授权 detail。
Author
Owner

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

Reproduced on main 34fd7bdf7 with the issue's repro test (existing loopback CDP discovery fixture; no browser executable, no CLI/MCP transport):

  • inspect_web_page: Target disallowed-page must be an HTTP(S) page in the selected origin scope.
  • analyze_web_bundle, observe_web_session, discover_webmcp_tools, capture_web_screenshot: Analysis could not complete. Run \rea doctor`, then try again.(code/reason stilltarget_unavailable/target_not_allowed`).

Same loss for a missing target: analyze_web_bundle reports the generic target_not_found default instead of Target missing was not found at the selected endpoint.

Source: withTargetSession authorizes with a hardcoded inspect_web_page, then providerError relabels with only { cause }; the CDP Electron provider relabels the same way. Working on a fix.

<!-- gh-comment-id:6331277735 --> @kaoru0822-kitauji commented on GitHub (Oct 22, 2026): Reproduced on main `34fd7bdf7` with the issue's repro test (existing loopback CDP discovery fixture; no browser executable, no CLI/MCP transport): - `inspect_web_page`: `Target disallowed-page must be an HTTP(S) page in the selected origin scope.` - `analyze_web_bundle`, `observe_web_session`, `discover_webmcp_tools`, `capture_web_screenshot`: `Analysis could not complete. Run \`rea doctor\`, then try again.` (code/reason still `target_unavailable`/`target_not_allowed`). Same loss for a missing target: `analyze_web_bundle` reports the generic `target_not_found` default instead of `Target missing was not found at the selected endpoint.` Source: `withTargetSession` authorizes with a hardcoded `inspect_web_page`, then `providerError` relabels with only `{ cause }`; the CDP Electron provider relabels the same way. Working on a fix.
Author
Owner

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

修复已合并,方向和我上面的建议一致:relabel 不再丢弃已生成的 detail。kaoru0822 补充发现的 missing-target 同类丢失(analyze_web_bundle 报泛化 target_not_found 默认文案)也建议一并纳入跨操作断言——同一缺失/拒绝目标下所有浏览器操作要么保留原始 detail、要么至少有统一文案兜底,避免 per-operation 碎片化再次出现。

<!-- gh-comment-id:6332613115 --> @XinwangTao commented on GitHub (Oct 22, 2026): 修复已合并,方向和我上面的建议一致:relabel 不再丢弃已生成的 detail。kaoru0822 补充发现的 missing-target 同类丢失(analyze_web_bundle 报泛化 target_not_found 默认文案)也建议一并纳入跨操作断言——同一缺失/拒绝目标下所有浏览器操作要么保留原始 detail、要么至少有统一文案兜底,避免 per-operation 碎片化再次出现。
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#3756
No description provided.