fix(forge): validate list's label + milestone filters, and paginate both

A filter value the forge cannot resolve is DISCARDED, not rejected, so a
typo does not narrow the result set -- it returns the unfiltered one.
That does not waste a query, it inverts the answer: "is anything open in
this milestone" comes back as every open issue and reads as yes, and a
duplicate check gets a list that never narrowed.

`list` now resolves both before querying. Labels reuse the write side's
resolver; the ids are discarded because this endpoint filters by name, so
resolution here is a spell-check rather than a lookup -- reusing it keeps
the message identical to the one the write side has always produced.
Milestones accept a title or an id and are checked against the ALL-state
set: filtering on a closed milestone is a normal query, and validating
against open-only would reject exactly the retrospective ones.

Both fetchers paginate. `repo_labels` asked for one page of 100 and
treated it as the population -- the inverse of the trailer bug, same
root: a valid label past the cut fails to resolve, and the error then
prints an "available labels" list that is itself truncated, so the
message argues for the typo.

`--assignee` / `--author` stay unvalidated on purpose: someone who has
left still legitimately appears on old issues, so a login that is not a
current member is not necessarily a typo.

Also drops the docs paragraph claiming unknown labels are silently
dropped on the write side; that has not been true since the resolver
landed.
This commit is contained in:
atlas 2026-08-05 22:06:12 +02:00
commit 0aa9a854bc
4 changed files with 206 additions and 36 deletions

View file

@ -276,20 +276,25 @@ to discover valid label names before triaging or to audit the label set.
- Do NOT use raw `curl` for forge access -- the CLI handles auth,
error checking, and output formatting.
- `issue-create --label <name>` / `pr-create --label <name>` are
repeatable and take the same spelling `labels <n> add` does. Unknown
names are silently dropped (matching `labels add`'s existing
behavior) rather than erroring, so a typo just means the label
doesn't land — check `hive-forge repo-labels` if one's missing. On
repeatable and take the same spelling `labels <n> add` does. **An
unknown name is an error, not a silent drop** — the command fails
listing the names that didn't resolve plus every label the repo has,
so it's fixable from the message without a second call. On
`pr-create --agit`, labels are applied as a follow-up call once the
PR number is parsed back out of the push output (the AGit push
itself has no label field), so they're silently skipped if that
parse fails — same fallback as the deferred multi-line body.
- `list --milestone <name>` takes a milestone **name or id**, is
repeatable, and the forge *discards* one it doesn't recognise. So a
typo returns the **unfiltered** list rather than an empty one — the
failure looks like "this milestone contains everything", not like an
error. Confirm the spelling with `hive-forge milestone`. (Same
silent-discard shape as unknown labels above.)
- `list --label <name>` / `list --milestone <name>` are repeatable and
validated the same way, for a sharper reason: the forge **discards** a
filter value it doesn't recognise, so a typo returns the
**unfiltered** list rather than an empty one. That doesn't waste a
query, it inverts the answer — "is anything open in this milestone"
comes back as every open issue. `--milestone` takes a title or an id,
and closed milestones count as valid (filtering on a shipped one is a
normal query).
- ⚠️ `--assignee` / `--author` are deliberately **not** validated: someone
who has left still legitimately appears on old issues, so a login that
isn't a current member is not necessarily a typo.
- `list --limit N` is a *request*: the forge clamps page size to its own
`api.MAX_RESPONSE_ITEMS` (50 by default), so `--limit 400` returns at
most 50 rows. The stderr trailer reports the real total from the