paperclipai/paperclip · error
Invalid decision stats filters
Error message
Invalid decision stats filters
What it means
Query-schema guard on the decisions list route: the status/bundleId/targetIssueId/originAgentId/limit query failed its Zod parse (bad enum, non-UUID, or out-of-range limit), so the list request is rejected with 400 and the flattened issues.
Source
Thrown at server/src/routes/decisions.ts:167
const companyId = req.params.companyId as string; assertBoard(req); assertCompanyAccess(req, companyId);
const query = z.object({ status: z.enum(["open", "decided", "expired", "cancelled"]).optional(), bundleId: z.string().guid().optional(), targetIssueId: z.string().guid().optional(), originAgentId: z.string().guid().optional(), limit: z.coerce.number().int().positive().max(100).optional() }).safeParse(req.query);
if (!query.success) { res.status(400).json({ error: "Invalid decision filters", details: query.error.flatten() }); return; }
res.json(await svc.list(companyId, query.data));
});
/**
* Gardener telemetry contract:
* { groupBy: "ruleKey", filters: { originAgentId: string|null, since: ISO-8601|null },
* totals: { proposed, accepted, rejected, expired },
* groups: [{ ruleKey: string|null, proposed, accepted, rejected, expired,
* chosenOptions: [{ optionId, count }] }] }
* Accepted means a non-dismissed decided outcome; rejected means an explicit dismiss;
* chosenOptions counts accepted outcomes only; expired is separate, and cancelled
* decisions contribute only to proposed.
*/
router.get("/companies/:companyId/decisions/stats", async (req, res) => {
const companyId = req.params.companyId as string; assertBoardOrAgent(req); assertCompanyAccess(req, companyId);
const query = statsQuerySchema.safeParse(req.query);
if (!query.success) { res.status(400).json({ error: "Invalid decision stats filters", details: query.error.flatten() }); return; }
if (req.actor.type === "agent" && query.data.originAgentId && query.data.originAgentId !== req.actor.agentId) {
res.status(403).json({ error: "Agents may only read their own decision stats" }); return;
}
const originAgentId = req.actor.type === "agent" ? req.actor.agentId : query.data.originAgentId;
res.json(await svc.stats(companyId, { originAgentId, since: query.data.since }));
});
router.get("/decisions/:id", async (req, res) => {
assertBoardOrAgent(req);
const decision = await getAccessibleResource(req, res, svc.get(req.params.id as string), "Decision not found");
if (!decision) return;
if (req.actor.type === "agent" && req.actor.agentId !== decision.originAgentId) { res.status(403).json({ error: "Only the origin agent may read this decision" }); return; }
res.json(await svc.outcome(decision.id));
});
router.post("/decisions/:id/decide", validate(decideSchema), async (req, res) => {
const userId = boardUserId(req);
const decision = await getAccessibleResource(req, res, svc.get(req.params.id as string), "Decision not found");
if (!decision) return;
res.json(await svc.decide({ id: decision.id, decidedByUserId: userId, userActor: req.actor, ...req.body }));View on GitHub (pinned to a7e689b3c3)
Solutions
- Fix the offending parameter to satisfy the constraint stated in the error message (type, range, or allowed values), then retry.
- Refer to the route's request validation in the named file and the shared validators for the accepted format.
Defensive patterns
Strategy: validation
When it happens
Trigger: Thrown at server/src/routes/decisions.ts:167 when the library encounters an invalid state.
Common situations: See trigger scenarios.
AI-assisted analysis of paperclipai/paperclip@a7e689b3c3 (2026-08-18).
Data as JSON: /api/errors/93822ed375237ecd.
Report an issue: GitHub.