Uh oh!
There was an error while loading. Please reload this page.
fix: handleMessage avoids [object Object] errors and enforces valid JSON-RPC error codes for thrown plain objects - #40715
Conversation
Hey A couple of things to address before this is ready for review:
If you'd like a hand getting started:
|
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
…ndleMessage
The catch block in handleMessage used `String(e)` for non-Error thrown values,
producing `[object Object]` instead of the actual validation message. This caused
submit_pull_request_review calls to fail with -32603 `[object Object]` instead of
the descriptive -32602 validation error when safe_outputs_handlers.cjs threw
plain objects like { code: -32602, message: '...' }.
Align handleMessage with the existing handleRequest pattern: extract the error
code from the thrown value (requiring it to be negative per JSON-RPC spec) and
use e.message when available.
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>There was a problem hiding this comment.
Pull request overview
This PR fixes stdio-transport MCP error responses so that when a tool handler throws a plain object (e.g., validation errors), the JSON-RPC response includes the intended error.code and error.message instead of -32603 [object Object].
Changes:
- Updated
handleMessage(stdio path) to propagate a negative JSON-RPC errorcodefrom thrown objects and prefere.messagewhen present. - Added tests covering thrown plain-object errors with and without an explicit error code.
- Introduced whitespace-only changes in an auto-generated maintenance workflow file (should be reverted).
Show a summary per file
| File | Description |
|---|---|
| actions/setup/js/mcp_server_core.cjs | Adjusts stdio error handling to extract JSON-RPC error code/message from non-Error thrown values. |
| actions/setup/js/mcp_server_core.test.cjs | Adds regression tests ensuring thrown plain-object errors return the correct code/message and avoid [object Object]. |
| .github/workflows/agentics-maintenance.yml | Contains whitespace-only edits (trailing-space # lines) in an auto-generated workflow header section. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
- Files reviewed: 2/3 changed files
- Comments generated: 4
| // Use the error code only if it's a valid JSON-RPC error code (must be a negative integer). | ||
| // Subprocess exit codes (positive integers like 1, 2, etc.) must not be used as JSON-RPC | ||
| // error codes, as that would produce non-conformant responses (e.g. "code=1"). | ||
| const code = e && typeof e.code === "number" && e.code < 0 ? e.code : -32603; |
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
Uh oh!
There was an error while loading. Please reload this page.
✅ Design Decision Gate 🏗️ completed the design decision gate check. No ADR enforcement needed: PR #40715 does not have the 'implementation' label and has 0 new lines of code in business logic directories (≤100 threshold). 3 files changed. |
✅ PR Code Quality Reviewer completed the code quality review. |
🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅ |
✅ Test Quality Sentinel completed test quality analysis. |
test body
|
There was a problem hiding this comment.
✅ Test Quality Sentinel: 80/100. Test quality is excellent — 0% of new tests are implementation tests (threshold: 30%). Both new tests verify observable JSON-RPC error responses when a handler throws a plain object, directly covering the behavioral contract fixed in this PR. Note: a test inflation flag (49 test lines / 5 prod lines = 9.8×) deducted 10 pts but is non-blocking — the verbosity is justified by full MCP request/response cycle scaffolding.
There was a problem hiding this comment.
Skills-Based Review 🧠
Applied /diagnose and /tdd — approving with minor suggestions.
📋 Key Themes & Highlights
Key Themes
- Root cause correctly addressed: The
String(e)coercion in the oldhandleMessagecatch block was the real bug; the fix extractse.codeande.messagedirectly, mirroring the already-correcthandleRequestpath. - Positive-code guard is important but untested: The code comment explicitly warns that subprocess exit codes (positive integers) must not be forwarded as JSON-RPC error codes. A third test case would lock in this behaviour.
- Subtle transport asymmetry:
handleRequestfalls back to"Internal error"for messageless throws;handleMessagenow falls back toString(e). Both are defensible, but the divergence should be intentional.
Positive Highlights
- ✅ Fix is a surgical one-liner that correctly aligns
handleMessagewithhandleRequest - ✅ Two targeted regression tests reproduce both failure modes before asserting the fix
- ✅ Null guard (
e && ...) on the code extraction is a nice defensive addition - ✅ The code comment clearly explains why positive codes are rejected — good documentation
🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 78.8 AIC · ⌖ 10.4 AIC · ⊞ 6.5K
Uh oh!
There was an error while loading. Please reload this page.
| // Subprocess exit codes (positive integers like 1, 2, etc.) must not be used as JSON-RPC | ||
| // error codes, as that would produce non-conformant responses (e.g. "code=1"). | ||
| const code = e && typeof e.code === "number" && e.code < 0 ? e.code : -32603; | ||
| server.replyError(id, code, e && e.message ? String(e.message) : String(e)); |
There was a problem hiding this comment.
[/diagnose] The message fallback here (String(e)) diverges from how handleRequest handles the same situation (err.message || "Internal error"). For primitives like throw 42 the stdio path returns "42" while the HTTP path returns "Internal error" — a subtle per-transport inconsistency that can complicate debugging.
💡 Suggested alignment
handleRequest (line 840):
message: err.message||"Internal error",handleMessage (this line):
e&&e.message ? String(e.message) : String(e)Consider aligning both paths to the same fallback message, or add a comment explaining why the two transports intentionally differ. Either choice is fine, but the asymmetry should be deliberate.
Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Non-blocking but fix is incomplete and the key guard has no test
The core bug ([object Object] in error responses for plain-object throws) is correctly fixed for the primary case (safe_outputs_handlers.cjs throwing { code, message }). Two issues prevent a clean approve:
⚠️ Issues found
1. Message fallback still produces [object Object] (line 991) — String(e) fires any time e.message is falsy, including absent or empty-string. The reference implementation (handleRequest) avoids this entirely with err.message || "Internal error". The stdio path should match.
2. Positive-code suppression untested (test file, line 521) — The comment on lines 987–989 explicitly calls out blocking positive subprocess exit codes as the motivation for e.code < 0. No test exercises throw { code: 1, message: "..." } → code should be -32603. If the guard is ever inverted or removed, nothing catches it.
Warning
Firewall blocked 1 domain
The following domain was blocked by the firewall during workflow execution:
patchdiff.githubusercontent.com
To allow these domains, add them to the
network.allowedlist in your workflow frontmatter:
network:
allowed:
- defaults
- "patchdiff.githubusercontent.com"See Network Configuration for more information.
🔎 Code quality review by PR Code Quality Reviewer · 109.1 AIC · ⌖ 7.39 AIC · ⊞ 5.1K
| // Subprocess exit codes (positive integers like 1, 2, etc.) must not be used as JSON-RPC | ||
| // error codes, as that would produce non-conformant responses (e.g. "code=1"). | ||
| const code = e && typeof e.code === "number" && e.code < 0 ? e.code : -32603; | ||
| server.replyError(id, code, e && e.message ? String(e.message) : String(e)); |
There was a problem hiding this comment.
Message fallback String(e) still produces [object Object] for objects without a message, diverging from handleRequest.
e && e.message ? is a truthiness check — it drops any thrown object whose message property is an empty string "" or absent, falling through to String(e) which produces [object Object] for any plain object. This is the exact symptom the PR claims to fix.
The reference implementation (handleRequest, line 840) correctly uses err.message || "Internal error", avoiding the object serialization entirely.
💡 Suggested fix
// Current (can still produce "[object Object]" when message is absent or "")server.replyError(id,code,e&&e.message ? String(e.message) : String(e));// Aligned with handleRequest:server.replyError(id,code,(e&&e.message) ? String(e.message) : "Internal error");This matches handleRequest's err.message || "Internal error" exactly and ensures the stdio path never serializes a plain object into the message field.
Uh oh!
There was an error while loading. Please reload this page.
pelikhan
commented
Jun 22, 2026
@copilot Rub pr-finisher skill |
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Ran pr-finisher flow and pushed follow-up fixes in |
Uh oh!
There was an error while loading. Please reload this page.
Weekly update covering the week of June 15–22, 2026: - Compiler +320% performance regression fix (#40662) - New deferinloop Go linter (#40679) - gh-aw-detection rollout to 50% of workflows (#40698) - JSON-RPC error handling fix (#40715) - Skillet sparse checkout path fix (#40684) - FNV-1a heredoc delimiter generation (#40696) - Agent of the Week: delight ✨ Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
submit_pull_request_reviewwas failing with-32603 [object Object]because thehandleMessagecatch block inmcp_server_core.cjsusedString(e)for non-Errorthrown values.safe_outputs_handlers.cjsthrows plain objects — notErrorinstances — for validation errors, soString({code: -32602, message: "..."})→[object Object].The HTTP path (
handleRequest) already handled this correctly; the stdio path did not.Changes
mcp_server_core.cjs— alignhandleMessagecatch withhandleRequest, and tighten JSON-RPC code handling:e.codeonly when it is a negative integer (and from an object)e.messagewhen present"Internal error"instead of serializing the thrown valuemcp_server_core.test.cjs— expanded regression coverage for thrown plain-object errors:-32603-32603-32603"Internal error"