{"record":{"id":"84791e244c7d9d36","repo":"paperclipai/paperclip","slug":"reason-must-be-a-string-up-to-4000-characters","errorCode":null,"errorMessage":"reason must be a string up to 4000 characters","messagePattern":"reason must be a string up to 4000 characters","errorType":"validation","errorClass":null,"httpStatus":400,"severity":"warning","filePath":"server/src/routes/tool-gateway.ts","lineNumber":621,"sourceCode":"          userId: req.actor.type === \"board\" ? req.actor.userId : null,\n        },\n      });\n      res.json(actionRequest);\n    } catch (err) {\n      sendGatewayError(res, err);\n    }\n  });\n\n  router.post(\"/tool-gateway/action-requests/:id/decline\", async (req, res) => {\n    try {\n      assertBoard(req);\n      const body = (req.body ?? {}) as { companyId?: string; reason?: string };\n      const companyId = body.companyId ?? (typeof req.query.companyId === \"string\" ? req.query.companyId : null);\n      if (!companyId) {\n        res.status(400).json({ error: \"companyId is required\" });\n        return;\n      }\n      if (body.reason !== undefined && (typeof body.reason !== \"string\" || body.reason.length > 4000)) { res.status(400).json({ error: \"reason must be a string up to 4000 characters\" }); return; }\n      assertBoardMutationAccess(req, companyId);\n      const actor = getActorInfo(req);\n      const actionRequest = await toolGateway.declineActionRequest({\n        companyId,\n        actionRequestId: req.params.id,\n        reason: body.reason,\n        actor: {\n          agentId: actor.agentId,\n          userId: req.actor.type === \"board\" ? req.actor.userId : null,\n        },\n      });\n      res.json(actionRequest);\n    } catch (err) {\n      sendGatewayError(res, err);\n    }\n  });\n\n  router.get(\"/tool-gateway/runtime-slots\", async (req, res) => {","sourceCodeStart":603,"sourceCodeEnd":639,"githubUrl":"https://github.com/paperclipai/paperclip/blob/01ad8584922b5d85292b1723cae71fa0d9b07a19/server/src/routes/tool-gateway.ts#L603-L639","documentation":"Validation guard in the tool-gateway action-request decline route: the optional `reason` body field, when present, must be a string of at most 4000 characters. It fires when the decline reason is a non-string value or an over-long string, before the decline is recorded.","triggerScenarios":"POST /api/tool-gateway/action-requests/:id/decline with { reason: 123 }, { reason: { text: 'x' } }, or a string longer than 4000 characters.","commonSituations":"User pastes a very long explanation into a decline dialog; client sends non-string reason after failed parsing; template renders 'null' or an object instead of a string.","solutions":["Send reason as a plain string of at most 4000 characters.","Omit reason entirely if there is no explanation.","Truncate client-side (e.g. reason.slice(0, 4000)) before sending.","Coerce/validate the reason field type in the client before the request."],"exampleFix":"// before\ndecline({ companyId, reason: longText })            // longText.length = 5200 -> 400\n// after\ndecline({ companyId, reason: longText.slice(0, 4000) })","handlingStrategy":"validation","validationCode":"if (reason !== undefined && (typeof reason !== 'string' || reason.length > 4000)) throw new TypeError('reason must be a string of at most 4000 characters');","typeGuard":"const isValidReason = (v) => v === undefined || (typeof v === 'string' && v.length <= 4000);","tryCatchPattern":"try {\n  return await declineActionRequest({ actionRequestId, companyId, reason });\n} catch (e) {\n  if (e.status === 400 && /reason/.test(e.body?.error ?? '')) {\n    return declineActionRequest({ actionRequestId, companyId, reason: String(reason ?? '').slice(0, 4000) });\n  }\n  throw e;\n}","preventionTips":["Cap textarea input client-side with maxLength=4000.","Strip non-string values before sending the decline payload.","Trim whitespace and truncate long user notes before submit.","Validate the decline payload with a schema before the request."],"tags":["validation","http-400","string-length","request-body"],"backgroundTag":"invalid-argument-value","analyzedSha":"01ad8584922b5d85292b1723cae71fa0d9b07a19","analyzedAt":"2026-09-10T03:14:50.855Z","contentChangedAt":"2026-09-10T03:14:50.855Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}