paperclipai/paperclip · warning
reason must be a string up to 4000 characters
Error message
reason must be a string up to 4000 characters
What it means
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.
Source
Thrown at server/src/routes/tool-gateway.ts:621
userId: req.actor.type === "board" ? req.actor.userId : null,
},
});
res.json(actionRequest);
} catch (err) {
sendGatewayError(res, err);
}
});
router.post("/tool-gateway/action-requests/:id/decline", async (req, res) => {
try {
assertBoard(req);
const body = (req.body ?? {}) as { companyId?: string; reason?: string };
const companyId = body.companyId ?? (typeof req.query.companyId === "string" ? req.query.companyId : null);
if (!companyId) {
res.status(400).json({ error: "companyId is required" });
return;
}
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; }
assertBoardMutationAccess(req, companyId);
const actor = getActorInfo(req);
const actionRequest = await toolGateway.declineActionRequest({
companyId,
actionRequestId: req.params.id,
reason: body.reason,
actor: {
agentId: actor.agentId,
userId: req.actor.type === "board" ? req.actor.userId : null,
},
});
res.json(actionRequest);
} catch (err) {
sendGatewayError(res, err);
}
});
router.get("/tool-gateway/runtime-slots", async (req, res) => {View on GitHub (pinned to 01ad858492)
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.
Example fix
// before
decline({ companyId, reason: longText }) // longText.length = 5200 -> 400
// after
decline({ companyId, reason: longText.slice(0, 4000) }) Defensive patterns
Strategy: validation
Validate before calling
if (reason !== undefined && (typeof reason !== 'string' || reason.length > 4000)) throw new TypeError('reason must be a string of at most 4000 characters'); Type guard
const isValidReason = (v) => v === undefined || (typeof v === 'string' && v.length <= 4000);
Try / catch
try {
return await declineActionRequest({ actionRequestId, companyId, reason });
} catch (e) {
if (e.status === 400 && /reason/.test(e.body?.error ?? '')) {
return declineActionRequest({ actionRequestId, companyId, reason: String(reason ?? '').slice(0, 4000) });
}
throw e;
} Prevention
- 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.
When it happens
Trigger: POST /api/tool-gateway/action-requests/:id/decline with { reason: 123 }, { reason: { text: 'x' } }, or a string longer than 4000 characters.
Common situations: 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.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- rememberAction must be a boolean
- Invalid status '${String(rawStatus)}'. Must be one of: ${PLU
- Request body is required
- "tool" is required and must be a string
- "runContext" is required and must be an object
AI-assisted analysis of paperclipai/paperclip@01ad858492 (2026-09-10).
Data as JSON: /api/errors/84791e244c7d9d36.
Report an issue: GitHub.