Capture exception message in browser telemetry#304
Conversation
Runtime.exceptionThrown events set console_error.data.text to CDP's generic "Uncaught" prefix and dropped the actual error message, which lives on the exception RemoteObject (already parsed but unused). Add a `message` field populated from the object's description (first line) or thrown value, so consumers can see what an exception said, not just that one occurred and where.
A thrown JSON null rendered as "<nil>" instead of falling back to the generic text; guard the nil case. Symbol/BigInt/NaN/Infinity throws land in unserializableValue, which was ignored and dropped the thrown value; read it after description. Adds regression tests for both.
| if i := strings.IndexByte(o.Description, '\n'); i >= 0 { | ||
| return o.Description[:i] | ||
| } | ||
| return o.Description |
There was a problem hiding this comment.
Leading newline yields empty message
Medium Severity
The exceptionMessage function returns an empty string when a CDP exception's description begins with a newline. This occurs because the first-line extraction logic misinterprets a leading newline, resulting in a blank message field in telemetry despite the generic text fallback.
Reviewed by Cursor Bugbot for commit 77185e4. Configure here.
rgarcia
left a comment
There was a problem hiding this comment.
reviewed the openapi.yaml change — one schema suggestion:
-
server/openapi.yaml:1771— having bothtextandmessageonBrowserConsoleErrorEventDataforces consumers to learn a rule ("prefermessageif present, elsetext"), and the required field (text) is the near-useless one on the exception path ("Uncaught"). suggest droppingmessageentirely and makingtextthe full console-rendered line on the exception path — that's exactly what DevTools displays (exceptionDetails.text + " " + exception.descriptionfirst line, e.g.Uncaught Error: boom), so it's zero invention over CDP, keeps one required field, and preserves the uncaught-vs-in-promise signal inside the string. since this is beta, no back-compat concern. suggested description:text: type: string description: > Human-readable error text, as the browser console would display it. For console.error() calls, the first argument coerced to a string. For uncaught exceptions, the prefix and error message, e.g. "Uncaught Error: boom" or "Uncaught (in promise) TypeError: x is not a function".
implementation-wise this is a small change at
handlers.go:228-229— joinp.ExceptionDetails.Textwith the derived message, taking care the existing fallback-to-textinexceptionMessagedoesn't produce"Uncaught Uncaught".
Keep the console_error data field descriptions consistent: state the contract and the "Present only when sourced from Runtime.exceptionThrown" qualifier like its siblings, without the derivation detail.
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes using high effort and found 1 potential issue.
There are 2 total unresolved issues (including 1 from previous review).
❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.
Reviewed by Cursor Bugbot for commit 1c55b27. Configure here.
| } | ||
| return string(o.Value) | ||
| } | ||
| return fallback |
There was a problem hiding this comment.
Valueless throws omit type label
Low Severity
exceptionMessage parses a minimal cdpRemoteObject without type, so when CDP sends a valueless primitive (e.g. throw undefined) with no description, value, or unserializableValue, it falls back to generic text instead of the thrown type. consoleArgString already uses Type in the same situation.
Additional Locations (1)
Reviewed by Cursor Bugbot for commit 1c55b27. Configure here.


Summary
Runtime.exceptionThrowntelemetry events setconsole_error.data.textto CDP's generic"Uncaught"/"Uncaught (in promise)"prefix and dropped the actual error message. The message lives on the exceptionRemoteObject(exceptionDetails.exception), which was already parsed into the struct but never read — so consumers could see that an exception fired and where (line/column/source_url/stack), but not what it said.console.error()messages were unaffected.Changes
messagetoBrowserConsoleErrorEventData(openapi.yaml + regenerated oapi).RemoteObject(description,value) and derive the message: first line ofdescription(e.g.Error: boom) → thrown value for non-Error throws → fall back to the generictext. The full stack stays instack_trace, somessageis just the name+message line.messageis present only on exceptionThrown-sourced events;textis left unchanged so the uncaught-vs-in-promise signal is preserved.Test plan
go build ./...go test ./lib/cdpmonitor/ -run TestExceptionMessagethrow new Error("...")yieldsconsole_error.data.messagecontaining the message (needs Docker + image)Note
Low Risk
Additive optional API field and localized CDP handler logic with unit tests; no auth or breaking changes to existing
textbehavior.Overview
Runtime.exceptionThrown console error events now expose the real exception text via a new optional
messagefield onBrowserConsoleErrorEventData, whiletextstays as CDP’s generic"Uncaught"/"Uncaught (in promise)"prefix.The CDP monitor unmarshals the exception
RemoteObjectand derivesmessagewithexceptionMessage: first line ofdescriptionforErrorthrows (stack remains instack_trace), thrown primitives viavalue,unserializableValuefor Symbol/BigInt, then fallback totext. OpenAPI and generatedoapitypes were updated;consoleAPICalledpaths are unchanged.Reviewed by Cursor Bugbot for commit 1c55b27. Bugbot is set up for automated code reviews on this repo. Configure here.