---
key: ci.gitea-milestone-filter-noop
title: 2026-07-21 — Gitea's `?milestones=` issue filter silently no-ops on names containing `:` or `+` (#542)
status: active
since: '2026-07-21'
supersedes: none
superseded-by: none
rule: Never filter issues with the server-side `?milestones=<name>` parameter — fetch all open issues once and filter LOCALLY on each issue's `.milestone.title`.
signals: '`GET /issues?milestones=` · milestone name filter · returns the whole open-issue list · `:` or `+` in a milestone title · mis-tiered issues · count looks like "all open issues" · paths: `scripts/select-queue.sh` · issues: #542, #77, #72'
mechanics: '`?state=open&type=issues&limit=50`, then filter in the client, e.g. `[i for i in issues if (i[''milestone''] or {}).get(''title'') == NAME]`. `scripts/select-queue.sh` already does this; the trap is for anyone writing a *new* query.'
---

The filter does not error on a name it cannot round-trip — it returns the **entire open-issue list** as
though every issue matched. Milestone titles here routinely contain the offending characters (e.g.
`Scheduling: refactor + distribution`), so the failure is the normal case, not an edge case.

Anything trusting that response mis-tiers issues: during #77 selection it made unmilestoned #72 look
like a member of the milestone and hid #77's true sibling set. **The tell is a result count suspiciously
equal to the total number of open issues** — if a filtered query returns everything, the filter silently
failed; re-derive membership locally rather than reasoning about the result.

The dependencies API is unaffected: `POST /issues/{n}/dependencies` with `{"owner","repo","index"}` sets
blocked-by correctly, though the bare `{index}` form returns 201 without reliably attaching — verify
with the GET. (That last sentence is carried forward from an earlier revision of the handoff doc,
commit `f93458c7`, where it was dropped by a later prune rather than disproved.)
