[GH-ISSUE #3126] [Bug] Attached browser scenario rejected by the default service-worker policy reports only target_not_allowed #1087

Closed
opened 2026-10-15 18:30:58 +00:00 by bsakaguchi · 2 comments
Owner

Originally created by @Sergio-763 on GitHub (Oct 14, 2026).
Original GitHub issue: https://github.com/morluto/rea/issues/3126

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

Interface

CLI

Earliest failure stage

Transport or result projection

Observed behavior

capture-browser-scenario in connect mode with the default environment.service_workers (block) rejects a tab whose origin has an active service worker. This rejection is documented as intended (docs/browser-scenario-contract.md:18-20). The error the caller receives does not identify the service worker as the cause or name the remedy:

{"error":"Browser scenario capture failed","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":"capture_browser_scenario","reason":"target_not_allowed"}}

details.reason is target_not_allowed, which is also thrown by many unrelated target checks (CDP page capture, authorized main frame, screenshot, Electron). message and remediation.action direct the caller to rea doctor, which cannot resolve this condition. The same request with "environment": {"service_workers": "allow"} succeeds, and the same policy on an origin without a worker succeeds, so the only discriminator is the service worker, but nothing in the response says so.

Reproduction

Fixture server (server.mjs, serves on a free 127.0.0.1 port and prints it):

import http from "node:http";
const page = (s) => `<!doctype html><meta charset=utf-8><body><script>${s}</script>`;
const server = http.createServer((req, res) => {
  const p = new URL(req.url, "http://x").pathname;
  const send = (t, b) => { res.writeHead(200, { "content-type": t, "cache-control": "no-store" }); res.end(b); };
  if (p === "/sw/") return send("text/html", page(`navigator.serviceWorker.register("/sw.js").then(()=>navigator.serviceWorker.ready)`));
  if (p === "/sw.js") return send("text/javascript", `self.addEventListener("install",()=>self.skipWaiting());self.addEventListener("activate",e=>e.waitUntil(self.clients.claim()));`);
  if (p === "/plain/") return send("text/html", page(``));
  res.writeHead(404); res.end();
});
server.listen(0, "127.0.0.1", () => console.log(server.address().port));
  1. node server.mjs and note <web-port>.
  2. Start a Chromium-family browser with a disposable profile: <browser> --user-data-dir=<tmp-profile> --remote-debugging-address=127.0.0.1 --remote-debugging-port=0 --headless=new about:blank; read the port from <tmp-profile>/DevToolsActivePort as <cdp-port>.
  3. Open the worker page: curl -X PUT "http://127.0.0.1:<cdp-port>/json/new?http://127.0.0.1:<web-port>/sw/"; wait about 3 seconds, then read the page target id from http://127.0.0.1:<cdp-port>/json as <target-id>.
  4. Save as sw-block.json:
{
  "browser": { "mode": "connect", "cdp_endpoint": "http://127.0.0.1:<cdp-port>", "target_id": "<target-id>" },
  "start_url": { "url": "http://127.0.0.1:<web-port>/sw/" },
  "actions": [{ "step_id": "settle", "action": "wait_for_timeout", "duration_ms": 200 }]
}
  1. rea capture-browser-scenario sw-block.json --format json returns the error above.
  2. Add "environment": { "service_workers": "allow" } to the same file: the capture succeeds (environment.service_workers: "allow" in the returned Evidence).
  3. Control: with the second (about:blank) tab, start_url http://localhost:<web-port>/plain/ (a different origin with no worker) and the default policy succeeds.

Each step was run on revision f3534749e.

Expected behavior

When the block policy rejects a capture, the typed error should say that a service-worker controller or registration was detected on the selected page or one of its frames, and that environment.service_workers: "allow" is the caller's way to permit it (or that the worker must be removed by the caller, since REA never removes existing registrations, docs/browser-scenario-contract.md:20). Directing the caller to rea doctor is not applicable to this condition.

Typed outcome or recovery expected

A distinct reason (for example a service-worker-specific BrowserObservationError reason, or a field in details) mapped through the same target_unavailable code, with a remediation naming service_workers: "allow".

Impact and scope

Callers (including agents) that attach to a tab already controlled by a worker get no actionable direction and are told to run an unrelated diagnostic. Evidence integrity is not affected: the capture is correctly refused. Launch mode and the allow policy are not affected. This is distinct from #2956, which concerns the block policy being evaluated against the pre-navigation page; this report is only about the content of the error when the rejection does occur.

Analysis or suspected cause

Source-derived, not otherwise verified:

  • src/browser/PlaywrightScenarioServiceWorkers.ts:206-228 (assertBlocked) throws new BrowserObservationError(OPERATION, "target_not_allowed") with no worker-specific reason or details.
  • src/domain/analysisErrorProjection.ts:131-141 maps target_not_allowed (together with target_not_found and target_changed) to target_unavailable, and the generic message/remediation is produced from that code.

REA version

6.4.0 (source revision f3534749e)

Runtime and platform

Node 24.18.0; macOS arm64 (Darwin 25); Brave 155.1.97.56 (Chromium 155.0.8059.40) headless, disposable profile, attached over CDP

Target and artifact context

Local HTTP fixture page with a skipWaiting/clients.claim service worker; browser scenario connect mode.

Originally created by @Sergio-763 on GitHub (Oct 14, 2026). Original GitHub issue: https://github.com/morluto/rea/issues/3126 ## 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 ## Interface CLI ## Earliest failure stage Transport or result projection ## Observed behavior `capture-browser-scenario` in `connect` mode with the default `environment.service_workers` (`block`) rejects a tab whose origin has an active service worker. This rejection is documented as intended (`docs/browser-scenario-contract.md:18-20`). The error the caller receives does not identify the service worker as the cause or name the remedy: ```json {"error":"Browser scenario capture failed","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":"capture_browser_scenario","reason":"target_not_allowed"}} ``` `details.reason` is `target_not_allowed`, which is also thrown by many unrelated target checks (CDP page capture, authorized main frame, screenshot, Electron). `message` and `remediation.action` direct the caller to `rea doctor`, which cannot resolve this condition. The same request with `"environment": {"service_workers": "allow"}` succeeds, and the same policy on an origin without a worker succeeds, so the only discriminator is the service worker, but nothing in the response says so. ## Reproduction Fixture server (`server.mjs`, serves on a free `127.0.0.1` port and prints it): ```js import http from "node:http"; const page = (s) => `<!doctype html><meta charset=utf-8><body><script>${s}</script>`; const server = http.createServer((req, res) => { const p = new URL(req.url, "http://x").pathname; const send = (t, b) => { res.writeHead(200, { "content-type": t, "cache-control": "no-store" }); res.end(b); }; if (p === "/sw/") return send("text/html", page(`navigator.serviceWorker.register("/sw.js").then(()=>navigator.serviceWorker.ready)`)); if (p === "/sw.js") return send("text/javascript", `self.addEventListener("install",()=>self.skipWaiting());self.addEventListener("activate",e=>e.waitUntil(self.clients.claim()));`); if (p === "/plain/") return send("text/html", page(``)); res.writeHead(404); res.end(); }); server.listen(0, "127.0.0.1", () => console.log(server.address().port)); ``` 1. `node server.mjs` and note `<web-port>`. 2. Start a Chromium-family browser with a disposable profile: `<browser> --user-data-dir=<tmp-profile> --remote-debugging-address=127.0.0.1 --remote-debugging-port=0 --headless=new about:blank`; read the port from `<tmp-profile>/DevToolsActivePort` as `<cdp-port>`. 3. Open the worker page: `curl -X PUT "http://127.0.0.1:<cdp-port>/json/new?http://127.0.0.1:<web-port>/sw/"`; wait about 3 seconds, then read the page target `id` from `http://127.0.0.1:<cdp-port>/json` as `<target-id>`. 4. Save as `sw-block.json`: ```json { "browser": { "mode": "connect", "cdp_endpoint": "http://127.0.0.1:<cdp-port>", "target_id": "<target-id>" }, "start_url": { "url": "http://127.0.0.1:<web-port>/sw/" }, "actions": [{ "step_id": "settle", "action": "wait_for_timeout", "duration_ms": 200 }] } ``` 5. `rea capture-browser-scenario sw-block.json --format json` returns the error above. 6. Add `"environment": { "service_workers": "allow" }` to the same file: the capture succeeds (`environment.service_workers: "allow"` in the returned Evidence). 7. Control: with the second (`about:blank`) tab, `start_url` `http://localhost:<web-port>/plain/` (a different origin with no worker) and the default policy succeeds. Each step was run on revision f3534749e. ## Expected behavior When the block policy rejects a capture, the typed error should say that a service-worker controller or registration was detected on the selected page or one of its frames, and that `environment.service_workers: "allow"` is the caller's way to permit it (or that the worker must be removed by the caller, since REA never removes existing registrations, `docs/browser-scenario-contract.md:20`). Directing the caller to `rea doctor` is not applicable to this condition. ## Typed outcome or recovery expected A distinct reason (for example a service-worker-specific `BrowserObservationError` reason, or a field in `details`) mapped through the same `target_unavailable` code, with a remediation naming `service_workers: "allow"`. ## Impact and scope Callers (including agents) that attach to a tab already controlled by a worker get no actionable direction and are told to run an unrelated diagnostic. Evidence integrity is not affected: the capture is correctly refused. Launch mode and the `allow` policy are not affected. This is distinct from #2956, which concerns the block policy being evaluated against the pre-navigation page; this report is only about the content of the error when the rejection does occur. ## Analysis or suspected cause Source-derived, not otherwise verified: - `src/browser/PlaywrightScenarioServiceWorkers.ts:206-228` (`assertBlocked`) throws `new BrowserObservationError(OPERATION, "target_not_allowed")` with no worker-specific reason or details. - `src/domain/analysisErrorProjection.ts:131-141` maps `target_not_allowed` (together with `target_not_found` and `target_changed`) to `target_unavailable`, and the generic message/remediation is produced from that code. ## REA version 6.4.0 (source revision f3534749e) ## Runtime and platform Node 24.18.0; macOS arm64 (Darwin 25); Brave 155.1.97.56 (Chromium 155.0.8059.40) headless, disposable profile, attached over CDP ## Target and artifact context Local HTTP fixture page with a skipWaiting/clients.claim service worker; browser scenario `connect` mode.
bsakaguchi 2026-10-15 18:30:58 +00:00
  • closed this issue
  • added the
    bug
    label
Author
Owner

@jpdefo commented on GitHub (Oct 14, 2026):

I reproduced this on current main 5830088520 with Node 24.18.0 on native Windows and a disposable Edge profile using a self-owned loopback service worker. The public CLI returns target_unavailable / target_not_allowed and the unrelated doctor guidance for block, while the allow control succeeds and the existing registration remains intact.

I will work on a focused diagnostic correction with regression coverage and real-browser CLI validation. This will preserve policy enforcement and caller-owned registrations. I checked #2957 at c909778bd4, whose documented result still uses target_not_allowed, so this is complementary error reporting rather than another implementation of its policy fix.

<!-- gh-comment-id:6160422329 --> @jpdefo commented on GitHub (Oct 14, 2026): I reproduced this on current main 58300885207133422af235e0f2b10658cf958b03 with Node 24.18.0 on native Windows and a disposable Edge profile using a self-owned loopback service worker. The public CLI returns target_unavailable / target_not_allowed and the unrelated doctor guidance for block, while the allow control succeeds and the existing registration remains intact. I will work on a focused diagnostic correction with regression coverage and real-browser CLI validation. This will preserve policy enforcement and caller-owned registrations. I checked #2957 at c909778bd42bd64225e2dbb24a465fc63dc2e52d, whose documented result still uses target_not_allowed, so this is complementary error reporting rather than another implementation of its policy fix.
Author
Owner

@morluto commented on GitHub (Oct 14, 2026):

Delivered on main in fffb07419.

The intentional service-worker policy refusal used generic target_not_allowed output. It now returns service_worker_blocked with specific recovery guidance across navigations and frames.

Validation: Service-worker policy regressions and actual Chrome CLI/MCP verifier, including restoration and cancellation. Full exact-revision qualification passed.

<!-- gh-comment-id:6177592842 --> @morluto commented on GitHub (Oct 14, 2026): Delivered on main in [fffb07419](https://github.com/morluto/rea/commit/fffb0741936067530d1ce9777461cd53e87398c2). The intentional service-worker policy refusal used generic target_not_allowed output. It now returns service_worker_blocked with specific recovery guidance across navigations and frames. Validation: Service-worker policy regressions and actual Chrome CLI/MCP verifier, including restoration and cancellation. Full exact-revision [qualification](https://github.com/morluto/rea/actions/runs/38629313022) passed.
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#1087
No description provided.