Skip to content

A repeated ?filter= answers two different error codes depending on which data route received it #8001

Description

@hotlong

Found while implementing #7390 (PR pending). Filed unassigned per Prime Directive #10; not fixed there, because #7390's ruling names the code for its own route and the sibling route is a different surface.

The divergence

Two sibling routes in packages/rest/src/rest-server.ts both refuse a repeated ?filter=, and they answer differently:

route code envelope gate
GET /data/:object INVALID_FILTER flat { error, code, object } (mapDataError) assertFilterParamSuppliedOnce (#7390)
GET /data/:object/export VALIDATION_ERROR nested { error: { code, message } } refuseRepeatedQueryParams (#6877)

So ?filter=a&filter=b is one caller mistake with two machine-readable answers, decided by which path it was sent to. A client that branches on error.code has to know both.

Why each is currently right on its own terms

Neither is an oversight, which is why this is a fork rather than a bug with an obvious fix:

Both rules are "one condition, one answer" — they just draw the condition's boundary differently. The list route reads it as the filter slot failed; the export route reads it as a single-valued parameter was repeated.

The options, if this is worth closing

  1. Move the export route's filter slot onto the same gate — repeated filter there becomes INVALID_FILTER too, and the other export parameters (format, limit, page, orderby, search, header) keep VALIDATION_ERROR. Consistent per-slot; splits the export route's own answer across two codes.
  2. Leave it. The boundary really is per-route-family, and each envelope matches the family its route already speaks.
  3. Move the list route onto VALIDATION_ERROR — consistent per-condition, but it contradicts the finding: a repeated ?filter= on GET /data/:object cannot be told from a filter AST, so it is diagnosed as a malformed filter (and, rarely, succeeds) #7390 ruling and would give one slot two codes on one route.

Not obviously worth paying for; recording it so the next person to touch either gate is choosing rather than discovering.

Dedup

Searched open issues for repeated-filter / VALIDATION_ERROR / INVALID_FILTER / error-code-inconsistency wording, and for the query-multiplicity and refuseRepeatedQueryParams identifiers. Hits: #7390 (the parent this came out of), #7534, #4436, #3948 — none covers the cross-route code divergence.


Generated by Claude Code

Activity

  1. hotlong commented on Aug 12, 2026

    @hotlong
    ContributorAuthor

    Triage: graded finding (held for the findings round), routed domain:cli (both routes live in packages/rest).

    Rationale: real but low-stakes cross-route divergence, and each side is individually correct by its own rule — the list route by the 2026-08-11 maintainer ruling on #7390, the export route by the shared #6877 helper. Nothing a user hits today beyond a confusing error-code split for one caller mistake. If promoted later, option 1 (move the export route's filter slot only onto the INVALID_FILTER gate) is the only shape that does not contradict the standing ruling; option 3 is foreclosed by it.


    Generated by Claude Code

  2. hotlong commented on Aug 12, 2026

    @hotlong
    ContributorAuthor

    Finding-grading round: closed, not planned.

    Premise re-verified (both gates live: query-multiplicity.ts:127/:240; list route calls the filter gate at rest-server.ts:6870), but each side is correct by its own standing rule — list = the 2026-08-11 maintainer ruling on #7390, export = the shared #6877 helper — and option 3 is foreclosed by that ruling. Option 1 would fix a cross-route cosmetic by splitting the export route's own answer across two codes, which is locally worse. The filer's own "not obviously worth paying for" stands. Sibling divergence #8039 escalated separately.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions