{"record":{"id":"0600dfa0bd3fb584","repo":"paperclipai/paperclip","slug":"request-body-is-required","errorCode":null,"errorMessage":"Request body is required","messagePattern":"Request body is required","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"server/src/routes/plugins.ts","lineNumber":1022,"sourceCode":"   *\n   * Response: `ToolExecutionResult`\n   * Errors:\n   * - 400 if request validation fails\n   * - 404 if tool is not found\n   * - 501 if tool dispatcher is not configured\n   * - 502 if the plugin worker is unavailable or the RPC call fails\n   */\n  router.post(\"/plugins/tools/execute\", async (req, res) => {\n    assertBoardOrAgent(req);\n\n    if (!toolDeps) {\n      res.status(501).json({ error: \"Plugin tool dispatch is not enabled\" });\n      return;\n    }\n\n    const body = (req.body as PluginToolExecuteRequest | undefined);\n    if (!body) {\n      res.status(400).json({ error: \"Request body is required\" });\n      return;\n    }\n\n    const { tool, parameters, runContext } = body;\n\n    // Validate required fields\n    if (!tool || typeof tool !== \"string\") {\n      res.status(400).json({ error: '\"tool\" is required and must be a string' });\n      return;\n    }\n\n    if (!runContext || typeof runContext !== \"object\") {\n      res.status(400).json({ error: '\"runContext\" is required and must be an object' });\n      return;\n    }\n\n    if (!runContext.agentId || !runContext.runId || !runContext.companyId || !runContext.projectId) {\n      res.status(400).json({","sourceCodeStart":1004,"sourceCodeEnd":1040,"githubUrl":"https://github.com/paperclipai/paperclip/blob/a7e689b3c35347b529cb9f54c9b9a8575a3dcab6/server/src/routes/plugins.ts#L1004-L1040","documentation":"Returned as HTTP 400 by POST /api/plugins/tools/execute (server/src/routes/plugins.ts:1007) when req.body is falsy — the request carried no parseable JSON body. With express.json() middleware this means an empty body or a missing/wrong Content-Type (anything not application/json leaves body undefined and the cast yields undefined).","triggerScenarios":"POST /api/plugins/tools/execute with no body at all; a JSON body sent without the Content-Type: application/json header (so the parser skips it); curl invoked without -d; fetch() called with no body option.","commonSituations":"Manual curl testing that omits -H 'Content-Type: application/json' or the data flag; HTTP clients whose default POST has no body; proxies stripping content-type headers; copy-pasted request snippets missing one line.","solutions":["Send a JSON body and the header: Content-Type: application/json","In fetch, pass headers: { 'Content-Type': 'application/json' } and body: JSON.stringify(payload)","In curl, use: curl -X POST -H 'Content-Type: application/json' -d '{\"tool\":...}' <url>"],"exampleFix":"# before\ncurl -X POST http://localhost:3100/api/plugins/tools/execute\n\n# after\ncurl -X POST http://localhost:3100/api/plugins/tools/execute \\\n  -H 'Content-Type: application/json' \\\n  -d '{\"tool\":\"acme.linear:list_issues\",\"parameters\":{},\"runContext\":{...}}'","handlingStrategy":"validation","validationCode":"await fetch(\"/api/plugins/tools/execute\", {\n  method: \"POST\",\n  headers: { \"Content-Type\": \"application/json\" },\n  body: JSON.stringify({ tool, parameters, runContext }),\n});","typeGuard":"const hasJsonBody = (opts: RequestInit): boolean =>\n  Boolean(opts.body) && (opts.headers?.[\"Content-Type\"] ?? \"\").includes(\"application/json\");","tryCatchPattern":null,"preventionTips":["Always set Content-Type: application/json and a non-empty body on POSTs to this route","Centralize request construction in one API client so headers are never forgotten"],"tags":["plugins","plugin-tools","http-400","request-body","rest-api"],"backgroundTag":"missing-request-body","analyzedSha":"a7e689b3c35347b529cb9f54c9b9a8575a3dcab6","analyzedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}