Found while fixing #7300 (PR #7350) — same file, different routes, so filed rather than fixed there (that card's declared surface is the GET /:name/runs branch). Observation-class, finding disposition, unassigned.
Mechanism
packages/runtime/src/domains/automation.ts, the two descriptor routes (relocate by content, the lines will drift):
// GET /actions
if (query?.paradigm) {
actions = actions.filter((a: any) => Array.isArray(a?.paradigms) && a.paradigms.includes(query.paradigm));
}
if (query?.source) { actions = actions.filter((a: any) => a?.source === query.source); }
if (query?.category) { actions = actions.filter((a: any) => a?.category === query.category); }
// GET /connectors
if (query?.type) { connectors = connectors.filter((c) => c?.type === query.type); }
Every one of these compares a raw query value against a string field. A repeated parameter arrives as an array from every query parser these routes run behind, and an array is never === any string and is never a member of paradigms[], so ?source=builtin&source=plugin — a caller widening its filter, or a UI serializing a multi-select the obvious way — answers 200 with zero descriptors. A structured value (?type[$ne]=x) does the same. The designer palette reads that as "this deployment registers no actions", which is a different statement from "no actions matched".
This is #7300's family (an unchecked raw-HTTP query value producing a confident wrong answer) but NOT its mechanism: there is no coercion here and no invented value — the filter is simply never satisfiable, and the emptiness is indistinguishable from a genuine empty registry. That is why it is filed separately rather than folded into #7300's fix.
Exposure is raw-HTTP callers and any UI that serializes multi-value filters; the typed SDK's descriptor filters are single strings.
Suggested shape, if promoted
The refusal these want already exists and is now shared: parseStringParam(param, raw) in packages/runtime/src/query-param.ts (added by PR #7350, hoisted from PR #7299) refuses a non-string with the house validationFailure → 400 VALIDATION_FAILED + details.fields[] carrying an ADR-0114 invalid_type. Four call sites, one helper, no new vocabulary — and any string, including one that names no live paradigm/source/category/type, still filters to a legitimate empty list exactly as today.
Worth deciding at the same time whether ?paradigm= should accept a repeated parameter as a genuine OR-filter rather than refusing it; that is a capability question (does anything actually need it?) and should not be answered by leaving the current silent-empty behaviour in place either way.
Dedup
Searched open issues for automation descriptor filter query paradigm, domains/automation.ts query and getActionDescriptors filter — no hits. #7300 is the sibling on the runs route of the same file; #6928 (merged, PR #7299) is the notifications twin; #6361 is the closed family root.
Generated by Claude Code
Found while fixing #7300 (PR #7350) — same file, different routes, so filed rather than fixed there (that card's declared surface is the
GET /:name/runsbranch). Observation-class,findingdisposition, unassigned.Mechanism
packages/runtime/src/domains/automation.ts, the two descriptor routes (relocate by content, the lines will drift):Every one of these compares a raw query value against a string field. A repeated parameter arrives as an array from every query parser these routes run behind, and an array is never
===any string and is never a member ofparadigms[], so?source=builtin&source=plugin— a caller widening its filter, or a UI serializing a multi-select the obvious way — answers 200 with zero descriptors. A structured value (?type[$ne]=x) does the same. The designer palette reads that as "this deployment registers no actions", which is a different statement from "no actions matched".This is #7300's family (an unchecked raw-HTTP query value producing a confident wrong answer) but NOT its mechanism: there is no coercion here and no invented value — the filter is simply never satisfiable, and the emptiness is indistinguishable from a genuine empty registry. That is why it is filed separately rather than folded into #7300's fix.
Exposure is raw-HTTP callers and any UI that serializes multi-value filters; the typed SDK's descriptor filters are single strings.
Suggested shape, if promoted
The refusal these want already exists and is now shared:
parseStringParam(param, raw)inpackages/runtime/src/query-param.ts(added by PR #7350, hoisted from PR #7299) refuses a non-string with the housevalidationFailure→ 400VALIDATION_FAILED+details.fields[]carrying an ADR-0114invalid_type. Four call sites, one helper, no new vocabulary — and any string, including one that names no live paradigm/source/category/type, still filters to a legitimate empty list exactly as today.Worth deciding at the same time whether
?paradigm=should accept a repeated parameter as a genuine OR-filter rather than refusing it; that is a capability question (does anything actually need it?) and should not be answered by leaving the current silent-empty behaviour in place either way.Dedup
Searched open issues for
automation descriptor filter query paradigm,domains/automation.ts queryandgetActionDescriptors filter— no hits. #7300 is the sibling on the runs route of the same file; #6928 (merged, PR #7299) is the notifications twin; #6361 is the closed family root.Generated by Claude Code