{"record":{"id":"e9b426938a56efd4","repo":"paperclipai/paperclip","slug":"runcontext-is-required-and-must-be-an-object","errorCode":null,"errorMessage":"\"runContext\" is required and must be an object","messagePattern":"\"runContext\" is required and must be an object","errorType":"http","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"server/src/routes/plugins.ts","lineNumber":1035,"sourceCode":"      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({\n        error: '\"runContext\" must include agentId, runId, companyId, and projectId',\n      });\n      return;\n    }\n\n    assertCompanyAccess(req, runContext.companyId);\n    const scopeError = await validateToolRunContextScope(runContext);\n    if (scopeError) {\n      res.status(403).json({ error: scopeError });\n      return;\n    }\n\n    if (req.actor.type === \"agent\" && toolGatewayDeps) {","sourceCodeStart":1017,"sourceCodeEnd":1053,"githubUrl":"https://github.com/paperclipai/paperclip/blob/a7e689b3c35347b529cb9f54c9b9a8575a3dcab6/server/src/routes/plugins.ts#L1017-L1053","documentation":"Returned as HTTP 400 by POST /api/plugins/tools/execute (server/src/routes/plugins.ts:1020) when the body's `runContext` field is absent, null, or not of type 'object'. The run context carries the execution scope (agentId, runId, companyId, projectId) that the dispatcher uses for authorization and audit, so it is mandatory on every execute call.","triggerScenarios":"POSTing {\"tool\": \"x:y\"} with no runContext; runContext: null (caught by !runContext); runContext passed as a string or number (typeof !== 'object'). Note an array technically passes this check but then fails the field-presence validation with the follow-up 400.","commonSituations":"Adapters or scripts that only send tool + parameters and assume the server infers context from auth; runContext lost through serialization bugs (stringified twice, so it arrives as a string); older clients predating the runContext requirement.","solutions":["Include runContext as an object with at least agentId, runId, companyId, and projectId in the execute body","Take the four ids from the current run/agent session state instead of reconstructing them ad hoc","Check that JSON.stringify round-trips your payload once — double-encoded objects arrive as strings and fail this check"],"exampleFix":"// before\nawait api.executePluginTool({ tool, parameters });\n\n// after\nawait api.executePluginTool({ tool, parameters, runContext: { agentId, runId, companyId, projectId } });","handlingStrategy":"validation","validationCode":"if (!runContext || typeof runContext !== \"object\" || Array.isArray(runContext)) {\n  throw new Error(\"runContext must be an object with agentId, runId, companyId, projectId\");\n}\nawait api.executePluginTool({ tool, parameters, runContext });","typeGuard":"const isRunContext = (v: unknown): v is Record<string, unknown> =>\n  typeof v === \"object\" && v !== null && !Array.isArray(v);","tryCatchPattern":null,"preventionTips":["Always construct runContext from the current run session; never omit it assuming the server infers scope","Verify the payload JSON round-trips once — double-stringified contexts arrive as strings and fail"],"tags":["plugins","plugin-tools","validation","http-400","rest-api"],"backgroundTag":"request-validation-failed","analyzedSha":"a7e689b3c35347b529cb9f54c9b9a8575a3dcab6","analyzedAt":"2026-08-18T22:49:45.177Z","schemaVersion":2},"datasetVersion":"2026-08-22T04:17:13.399Z"}