[PR #6016] [MERGED] fix(browser)!: keep malformed console argument inventories unknown #7121

Closed
opened 2026-11-01 21:45:33 +00:00 by bsakaguchi · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/morluto/rea/pull/6016
Author: @kaoru0822-kitauji
Created: 10/21/2026
Status: ✅ Merged
Merged: 10/21/2026
Merged by: @kaoru0822-kitauji

Base: main ← Head: fix/5934-cdp-console-argument-inventory


📝 Commits (2)

  • e57f66f fix(browser)!: keep malformed console argument inventories unknown
  • 7f2052f fix(browser): report malformed WebMCP registration stacks as incomplete

📊 Changes

8 files changed (+377 additions, -37 deletions)

View changed files

📝 docs/browser-observation.md (+2 -2)
📝 src/browser/CdpCaptureEventHandlers.ts (+35 -10)
📝 src/browser/CdpCaptureEventHelpers.ts (+35 -10)
📝 src/browser/CdpWebMcpDiscovery.ts (+6 -4)
📝 src/domain/browserObservationSchemas.ts (+7 -2)
➕ tests/boundary/browser/cdpConsoleArgumentInventory.test.ts (+278 -0)
📝 tests/fixtures/fakeCdpBrowserEvents.ts (+12 -9)
📝 tests/fixtures/fakeCdpBrowserTypes.ts (+2 -0)

📄 Description

What broke: A CDP Runtime.consoleAPICalled event with an omitted, null or non-array args was reported as a complete empty argument inventory. A malformed element was dropped and its retained siblings were renumbered. A malformed stack or top stack frame was likewise replaced by its caller frame, or treated as having no source at all.
Fix: Console arguments are parsed by position. An unknown inventory is argument_types: null, and text capture is unavailable. A malformed element stays at its index as unknown. Call-site attribution uses only frame 0 of a well-formed stack. Each malformed case records invalid_protocol_value in completeness, including WebMCP registration stacks.
Risk: Low to moderate. Two schema widenings (argument_types nullable, new text_capture.status: "unavailable") are marked breaking. Conforming browser events produce the same output as before.

Summary

Fixes #5934. inspect_web_page / rea inspect-web-page now keep a missing or malformed console argument inventory distinguishable from an observed args: [], and keep retained console text at its original CDP argument_index.

Problem and expected behavior

recordsValue (in CdpCaptureValues.ts) turns non-array input into [] and filters out non-record elements. The console handler used that filtered list for both argument_types and text positions. As a result:

  • args omitted, null or "invalid" gave argument_types: [], text_capture: {status: "included", values: []} and no completeness marker.
  • args: [null, {type:"string", value:"second"}] gave argument_types: ["string"] and argument_index: 0 for "second", whose real position is 1.

The same drop-and-reindex pattern was in firstCallFrame (recordsValue(callFrames)[0]). With callFrames: [null, frame], it picked the caller frame as the call site. That frame was used for the console source location, the network initiator location and the WebMCP registration source.

Change and scope

  • CdpCaptureEventHandlers.handleConsoleAPICalled: parses args with admitRecords and maps each element by position. A non-record element, or a record without a string type, becomes "unknown" at its index. An absent or non-array inventory becomes argument_types: null, and approved text capture becomes unavailable. Any of these records console_events: invalid_protocol_value, once per event, together with the existing timestamp check.
  • CdpCaptureEventHelpers: firstCallFrame is replaced by topCallFrame, which takes the raw stack value and returns a Result with a MalformedCdpCallFrame tag. It returns ok(frame) for frame 0, ok(undefined) for an omitted stack or empty callFrames, and the error for a non-object stack, non-array callFrames or a non-record frame 0. initiatorLocation stops at a malformed stack or top frame, including a malformed parent, instead of walking on. Consumers record console_events / network_initiators / webmcp_tools: invalid_protocol_value. WebMCP discovery keeps the registration with registration_source: null and marks discovery incomplete, so compareWebCaptures does not confirm a changed source (Codex review). It replaces WebMCP's own copy of the pattern.
  • browserObservationSchemas.ts: argument_types is nullable and has a description. text_capture.status adds "unavailable", the vocabulary that accessibility text capture already uses.
  • docs/browser-observation.md: documents positional argument types, the null/unknown cases and the frame-0 rule.
  • Not included: other recordsValue consumers (cookies, IndexedDB stores and entries, cache entries, metrics, DOM properties). Those are unordered inventories with no positional identity. Whether they report dropped malformed elements is a separate question, left out of this PR.
  • Scope: an ordinary fix inside the existing passive CDP observation boundary; no maintainer scope decision is needed.

Contract and boundary impact

  • Semantic owner and earliest changed stage: the CDP capture event handlers and helpers in src/browser/, where Runtime.consoleAPICalled and initiator stacks are first parsed.

  • Contribution scope fit and linked maintainer decision: not applicable (bug fix inside an existing boundary).

  • CLI and MCP/tool-catalog contract: the inspect_web_page output schema widens console.events[].argument_types to nullable and adds "unavailable" to console.events[].text_capture.status.

  • Provider, bridge, target-format, or platform compatibility: none. Conforming CDP producers always send an args array of RemoteObjects with type.

  • Evidence, artifact, provenance, or reconstruction contract: missing or malformed producer data is now reported as unknown and incomplete instead of as a complete empty observation. Retained text keeps the original CDP argument identity.

  • Target selection, authority, upstream behavior, and cleanup details: unchanged.

  • Generated metadata, package, or installation impact: the advertised output schema changes as described; nothing generated is committed.

  • Read existing files or captured data.

  • Launch or attach to a process.

Evidence and regression coverage

  • Tests added or updated: tests/boundary/browser/cdpConsoleArgumentInventory.test.ts runs CdpBrowserProvider.inspectPage against the repository's fake CDP browser over HTTP/WebSocket and validates the result with webPageInspectionSchema. Cases: args omitted, null and non-array; a non-object element; an element without type; the empty-array control; a non-primitive (object) control; console stacks with a malformed top frame, non-array callFrames and a non-object stack; a malformed top initiator frame; WebMCP registration stacks with the same three malformed shapes, plus omitted and empty controls. The fake browser gained a webMcpStackTrace option for this.

  • Base reproduction: on the base revision (6d356b843), 7 of the original 9 cases fail. The two controls pass. The 4 cases added for the review (the non-object console stack and the three malformed WebMCP stacks) fail against the previous PR head (e57f66f7e), and the WebMCP controls pass there. The failures match the issue output, for example expected [] to be null and expected ['string'] to deeply equal ['unknown','string']. All 16 pass with the fix.

  • User-visible CLI/MCP output: not run separately. The CLI and MCP adapters reach the same provider; consoleTimestampCliMcp.test.ts (CLI and MCP) still passes with the widened schema.

  • Remaining proof gaps: no real browser was used. Only the simulated CDP producer was exercised.

  • Observed, derived, and inferred claims remain distinguishable.

  • Artifact identity, source provenance, and failed attempts remain preserved.

  • Unsupported, incomplete, unavailable, or uncertain outcomes remain visible.

Validation performed

  • tests/boundary/browser/cdpConsoleArgumentInventory.test.ts: base revision 7 of 9 failed. The previous PR head failed 4 of 16. The current head passes all 16.
  • npm run test:local -- all src/browser/Cdp*.test.ts plus src/domain/webMcpDiscovery.test.ts: 19 files, 251 tests passed.
  • npm run test:focused -- all tests/boundary/browser/cdp*.test.ts (including the new test), consoleTimestampCliMcp, webMcpReceiverCli, webSocketOpcodeCliMcp, tests/boundary/mcp/streamableHttpMcp.test.ts: 46 files passed, 1 skipped (cdpSvgXlinkHref, which skips itself); 473 tests passed, 3 skipped. streamableHttpMcp, which failed in CI shard 8/8 with fetch failed / other side closed, also passed on its own (2 of 2).
  • npm run check:fast: passed.
  • npm run format:check: passed.
  • npm run docs:check: passed.
  • Performance: the change adds one positional map over each console event's arguments in place of the existing filter. It was not measured.

Compatibility, safety, and release

  • Breaking changes or migration steps: before this change, a console event with a malformed args returned argument_types: [] and text_capture.status: "included". It now returns argument_types: null and text_capture.status: "unavailable" (when text capture was selected), plus a console_events: invalid_protocol_value exclusion. Consumers must handle null and "unavailable". Compatibility cannot be kept without reporting missing evidence as a known empty inventory. The commit uses ! and a BREAKING CHANGE: footer.
  • Real Hopper/Ghidra, browser, or OS coverage: macOS arm64, fake CDP browser only. No real Chromium.
  • Package or release metadata impact: none beyond the output schema.
  • Security, privacy, process, or containment review: no new data is retained. Malformed producer values are reduced to unknown / null.

Author readiness

Human owner: @kaoru0822-kitauji

  • I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work.
  • I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps.
  • I declare this PR ready for initial maintainer review.

Before merge

  • This PR addresses a concrete problem or an agreed enhancement.
  • Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff.
  • The PR has one focused outcome and the title follows type(scope): outcome.
  • Related issue is linked, or the reason for not linking one is stated above.
  • Tests cover changed observable behavior and meaningful failure paths.
  • Owning docs, contracts, and generated metadata are updated where needed.
  • User-visible CLI/MCP changes include representative output.
  • I checked the final diff for secrets, unrelated cleanup, and unsupported claims.

🤖 Generated with Claude Code


🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/morluto/rea/pull/6016 **Author:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Created:** 10/21/2026 **Status:** ✅ Merged **Merged:** 10/21/2026 **Merged by:** [@kaoru0822-kitauji](https://github.com/kaoru0822-kitauji) **Base:** `main` ← **Head:** `fix/5934-cdp-console-argument-inventory` --- ### 📝 Commits (2) - [`e57f66f`](https://github.com/morluto/rea/commit/e57f66f7eddbd53afc02a758e8295c90e801c24d) fix(browser)!: keep malformed console argument inventories unknown - [`7f2052f`](https://github.com/morluto/rea/commit/7f2052ff3133b3fd94728a692085f83f3e22ba79) fix(browser): report malformed WebMCP registration stacks as incomplete ### 📊 Changes **8 files changed** (+377 additions, -37 deletions) <details> <summary>View changed files</summary> 📝 `docs/browser-observation.md` (+2 -2) 📝 `src/browser/CdpCaptureEventHandlers.ts` (+35 -10) 📝 `src/browser/CdpCaptureEventHelpers.ts` (+35 -10) 📝 `src/browser/CdpWebMcpDiscovery.ts` (+6 -4) 📝 `src/domain/browserObservationSchemas.ts` (+7 -2) ➕ `tests/boundary/browser/cdpConsoleArgumentInventory.test.ts` (+278 -0) 📝 `tests/fixtures/fakeCdpBrowserEvents.ts` (+12 -9) 📝 `tests/fixtures/fakeCdpBrowserTypes.ts` (+2 -0) </details> ### 📄 Description **What broke:** A CDP `Runtime.consoleAPICalled` event with an omitted, null or non-array `args` was reported as a complete empty argument inventory. A malformed element was dropped and its retained siblings were renumbered. A malformed stack or top stack frame was likewise replaced by its caller frame, or treated as having no source at all. **Fix:** Console arguments are parsed by position. An unknown inventory is `argument_types: null`, and text capture is `unavailable`. A malformed element stays at its index as `unknown`. Call-site attribution uses only frame 0 of a well-formed stack. Each malformed case records `invalid_protocol_value` in completeness, including WebMCP registration stacks. **Risk:** Low to moderate. Two schema widenings (`argument_types` nullable, new `text_capture.status: "unavailable"`) are marked breaking. Conforming browser events produce the same output as before. ## Summary Fixes #5934. `inspect_web_page` / `rea inspect-web-page` now keep a missing or malformed console argument inventory distinguishable from an observed `args: []`, and keep retained console text at its original CDP `argument_index`. ## Problem and expected behavior `recordsValue` (in `CdpCaptureValues.ts`) turns non-array input into `[]` and filters out non-record elements. The console handler used that filtered list for both `argument_types` and text positions. As a result: - `args` omitted, `null` or `"invalid"` gave `argument_types: []`, `text_capture: {status: "included", values: []}` and no completeness marker. - `args: [null, {type:"string", value:"second"}]` gave `argument_types: ["string"]` and `argument_index: 0` for `"second"`, whose real position is 1. The same drop-and-reindex pattern was in `firstCallFrame` (`recordsValue(callFrames)[0]`). With `callFrames: [null, frame]`, it picked the caller frame as the call site. That frame was used for the console source location, the network initiator location and the WebMCP registration source. ## Change and scope - `CdpCaptureEventHandlers.handleConsoleAPICalled`: parses `args` with `admitRecords` and maps each element by position. A non-record element, or a record without a string `type`, becomes `"unknown"` at its index. An absent or non-array inventory becomes `argument_types: null`, and approved text capture becomes `unavailable`. Any of these records `console_events: invalid_protocol_value`, once per event, together with the existing timestamp check. - `CdpCaptureEventHelpers`: `firstCallFrame` is replaced by `topCallFrame`, which takes the raw stack value and returns a `Result` with a `MalformedCdpCallFrame` tag. It returns ok(frame) for frame 0, ok(undefined) for an omitted stack or empty `callFrames`, and the error for a non-object stack, non-array `callFrames` or a non-record frame 0. `initiatorLocation` stops at a malformed stack or top frame, including a malformed `parent`, instead of walking on. Consumers record `console_events` / `network_initiators` / `webmcp_tools: invalid_protocol_value`. WebMCP discovery keeps the registration with `registration_source: null` and marks discovery incomplete, so `compareWebCaptures` does not confirm a changed source (Codex review). It replaces WebMCP's own copy of the pattern. - `browserObservationSchemas.ts`: `argument_types` is nullable and has a description. `text_capture.status` adds `"unavailable"`, the vocabulary that accessibility text capture already uses. - `docs/browser-observation.md`: documents positional argument types, the null/unknown cases and the frame-0 rule. - Not included: other `recordsValue` consumers (cookies, IndexedDB stores and entries, cache entries, metrics, DOM properties). Those are unordered inventories with no positional identity. Whether they report dropped malformed elements is a separate question, left out of this PR. - Scope: an ordinary fix inside the existing passive CDP observation boundary; no maintainer scope decision is needed. ## Contract and boundary impact - Semantic owner and earliest changed stage: the CDP capture event handlers and helpers in `src/browser/`, where `Runtime.consoleAPICalled` and initiator stacks are first parsed. - Contribution scope fit and linked maintainer decision: not applicable (bug fix inside an existing boundary). - CLI and MCP/tool-catalog contract: the `inspect_web_page` output schema widens `console.events[].argument_types` to nullable and adds `"unavailable"` to `console.events[].text_capture.status`. - Provider, bridge, target-format, or platform compatibility: none. Conforming CDP producers always send an `args` array of RemoteObjects with `type`. - Evidence, artifact, provenance, or reconstruction contract: missing or malformed producer data is now reported as unknown and incomplete instead of as a complete empty observation. Retained text keeps the original CDP argument identity. - Target selection, authority, upstream behavior, and cleanup details: unchanged. - Generated metadata, package, or installation impact: the advertised output schema changes as described; nothing generated is committed. - [x] Read existing files or captured data. - [x] Launch or attach to a process. ## Evidence and regression coverage - Tests added or updated: `tests/boundary/browser/cdpConsoleArgumentInventory.test.ts` runs `CdpBrowserProvider.inspectPage` against the repository's fake CDP browser over HTTP/WebSocket and validates the result with `webPageInspectionSchema`. Cases: `args` omitted, `null` and non-array; a non-object element; an element without `type`; the empty-array control; a non-primitive (`object`) control; console stacks with a malformed top frame, non-array `callFrames` and a non-object stack; a malformed top initiator frame; WebMCP registration stacks with the same three malformed shapes, plus omitted and empty controls. The fake browser gained a `webMcpStackTrace` option for this. - Base reproduction: on the base revision (`6d356b843`), 7 of the original 9 cases fail. The two controls pass. The 4 cases added for the review (the non-object console stack and the three malformed WebMCP stacks) fail against the previous PR head (`e57f66f7e`), and the WebMCP controls pass there. The failures match the issue output, for example `expected [] to be null` and `expected ['string'] to deeply equal ['unknown','string']`. All 16 pass with the fix. - User-visible CLI/MCP output: not run separately. The CLI and MCP adapters reach the same provider; `consoleTimestampCliMcp.test.ts` (CLI and MCP) still passes with the widened schema. - Remaining proof gaps: no real browser was used. Only the simulated CDP producer was exercised. - [x] Observed, derived, and inferred claims remain distinguishable. - [x] Artifact identity, source provenance, and failed attempts remain preserved. - [x] Unsupported, incomplete, unavailable, or uncertain outcomes remain visible. ## Validation performed - `tests/boundary/browser/cdpConsoleArgumentInventory.test.ts`: base revision 7 of 9 failed. The previous PR head failed 4 of 16. The current head passes all 16. - `npm run test:local --` all `src/browser/Cdp*.test.ts` plus `src/domain/webMcpDiscovery.test.ts`: 19 files, 251 tests passed. - `npm run test:focused --` all `tests/boundary/browser/cdp*.test.ts` (including the new test), `consoleTimestampCliMcp`, `webMcpReceiverCli`, `webSocketOpcodeCliMcp`, `tests/boundary/mcp/streamableHttpMcp.test.ts`: 46 files passed, 1 skipped (`cdpSvgXlinkHref`, which skips itself); 473 tests passed, 3 skipped. `streamableHttpMcp`, which failed in CI shard 8/8 with `fetch failed / other side closed`, also passed on its own (2 of 2). - `npm run check:fast`: passed. - `npm run format:check`: passed. - `npm run docs:check`: passed. - Performance: the change adds one positional `map` over each console event's arguments in place of the existing filter. It was not measured. ## Compatibility, safety, and release - Breaking changes or migration steps: before this change, a console event with a malformed `args` returned `argument_types: []` and `text_capture.status: "included"`. It now returns `argument_types: null` and `text_capture.status: "unavailable"` (when text capture was selected), plus a `console_events: invalid_protocol_value` exclusion. Consumers must handle `null` and `"unavailable"`. Compatibility cannot be kept without reporting missing evidence as a known empty inventory. The commit uses `!` and a `BREAKING CHANGE:` footer. - Real Hopper/Ghidra, browser, or OS coverage: macOS arm64, fake CDP browser only. No real Chromium. - Package or release metadata impact: none beyond the output schema. - Security, privacy, process, or containment review: no new data is retained. Malformed producer values are reduced to `unknown` / `null`. ## Author readiness Human owner: @kaoru0822-kitauji - [x] I reviewed the final diff and relevant existing code, understand the change and its edge cases, and take responsibility for the complete PR, including AI-assisted work. - [x] I checked the evidence above, confirmed it supports the current change, and recorded meaningful validation gaps. - [x] I declare this PR ready for initial maintainer review. ## Before merge - [x] This PR addresses a concrete problem or an agreed enhancement. - [x] Material scope and authority questions are resolved, and any required maintainer scope decision covers the final diff. - [x] The PR has one focused outcome and the title follows `type(scope): outcome`. - [x] Related issue is linked, or the reason for not linking one is stated above. - [x] Tests cover changed observable behavior and meaningful failure paths. - [x] Owning docs, contracts, and generated metadata are updated where needed. - [ ] User-visible CLI/MCP changes include representative output. - [x] I checked the final diff for secrets, unrelated cleanup, and unsupported claims. 🤖 Generated with [Claude Code](https://claude.com/claude-code) --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
bsakaguchi 2026-11-01 21:45:33 +00:00
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#7121
No description provided.