{"record":{"id":"63a506120cde3161","repo":"koala73/worldmonitor","slug":"get-china-decision-signals-returned-no-canonical-p","errorCode":null,"errorMessage":"get-china-decision-signals returned no canonical payload","messagePattern":"get-china-decision-signals returned no canonical payload","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"api/mcp/registry/rpc-tools.ts","lineNumber":615,"sourceCode":"      },\n    },\n    annotations: { readOnlyHint: true, destructiveHint: false, idempotentHint: true, openWorldHint: false },\n    _execute: async (_params, base, context, execution) => {\n      const url = `${base}/api/intelligence/v1/get-china-decision-signals`;\n      const auth = await buildAuthHeaders(context, 'GET', url, null);\n      const response = await fetch(url, {\n        headers: { ...auth, 'User-Agent': 'worldmonitor-mcp-edge/1.0' },\n        signal: AbortSignal.timeout(12_000),\n      });\n      await assertMcpToolFetchOk(response, {\n        operation: 'get-china-decision-signals',\n        tool: 'get_china_decision_signals',\n        auth: context,\n        execution,\n      });\n      const wire = await response.json() as { payloadJson?: unknown };\n      if (typeof wire.payloadJson !== 'string') {\n        throw new Error('get-china-decision-signals returned no canonical payload');\n      }\n      const payload = JSON.parse(wire.payloadJson) as unknown;\n      if (!isChinaDecisionSignalSnapshot(payload)) {\n        throw new Error('get-china-decision-signals returned an invalid canonical payload');\n      }\n      return payload;\n    },\n    _coverageKeys: [\n      'china:policy-events:v1',\n      'military:cross-strait-activity:v1',\n      'military:cross-strait-activity-bootstrap:v1',\n      'market:china:corporate-disclosures:v1',\n      'intelligence:china-decision-signals:v1',\n    ],\n    _apiPaths: [\n      'GET /api/intelligence/v1/get-china-decision-signals',\n    ],\n  },","sourceCodeStart":597,"sourceCodeEnd":633,"githubUrl":"https://github.com/koala73/worldmonitor/blob/ffec79ac339946fd2d24e85845da5755dcaa534b/api/mcp/registry/rpc-tools.ts#L597-L633","documentation":"Thrown by the get_china_decision_signals MCP tool when the downstream handler returned HTTP 200 (it passed assertMcpToolFetchOk) but the response body lacks a string-typed payloadJson field. The canonical contract is that the handler wraps the full snapshot in { payloadJson: '<JSON string>' }; a missing or non-string payloadJson means the handler has a contract regression or returned an unexpected envelope shape.","triggerScenarios":"The /api/intelligence/v1/get-china-decision-signals handler returned 200 but with a body where payloadJson is undefined, null, a number, or an object — not a JSON-encoded string. This happens on a handler version mismatch, a partial deploy where the handler changed its response shape, or a gateway/proxy that stripped or rewrote the body.","commonSituations":"Deploying a new handler version that changed the envelope shape before the MCP tool was updated (or vice versa); a gateway or middleware that re-serialized the response and dropped payloadJson; the handler returning an empty 200 on an internal edge case instead of a proper error status.","solutions":["Call GET /api/intelligence/v1/get-china-decision-signals directly and inspect the response body — confirm payloadJson is present and string-typed.","Check for a version mismatch between the handler (server/worldmonitor/) and the MCP tool (api/mcp/registry/rpc-tools.ts) if either was recently changed.","Verify no middleware/gateway is rewriting the response body between handler and MCP edge function.","If the handler itself is returning an unexpected shape, fix it to always emit { payloadJson: JSON.stringify(snapshot) } on success."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// Before relying on the result, probe the handler directly\nconst probe = await fetch('/api/intelligence/v1/get-china-decision-signals', { headers: authHeaders });\nconst body = await probe.json();\nif (typeof body.payloadJson !== 'string') {\n  throw new Error('Handler contract regression: payloadJson missing or non-string');\n}","typeGuard":"function isNoCanonicalPayloadError(e: unknown): boolean {\n  return e instanceof Error && e.message.includes('returned no canonical payload');\n}","tryCatchPattern":"try {\n  const snapshot = await callMcpTool('get_china_decision_signals', {});\n} catch (e) {\n  if (e instanceof Error && e.message.includes('returned no canonical payload')) {\n    // Handler returned unexpected shape — report contract regression, do not retry blindly\n    logContractRegression('get-china-decision-signals', e);\n  } else throw e;\n}","preventionTips":["Run the china-decision-signals parity audit (scripts/audit-china-decision-parity.mjs) after any handler or seeder change.","Monitor for this error in Sentry — it indicates a deploy version mismatch, not a transient issue.","Do not retry automatically — a contract regression will fail identically until the handler is fixed."],"tags":["mcp","contract-regression","china-decision-signals","http"],"backgroundTag":null,"analyzedSha":"ffec79ac339946fd2d24e85845da5755dcaa534b","analyzedAt":"2026-08-12T11:24:56.012Z","schemaVersion":2},"datasetVersion":"2026-08-13T09:17:06.757Z"}