bug(labels): by-name label resolution ignores org-level labels — "unknown label" for a label the API accepts by ID #12

Closed
opened 2026-07-11 21:15:15 +00:00 by hexajon · 2 comments
hexajon commented 2026-07-11 21:15:15 +00:00 (Migrated from codeberg.org)

Observed (2026-07-10, dogfooding the new org status/ label scope)

madt_issues action=edit add_labels=["status/needs-decision"] failed with unknown label "status/needs-decision"; available labels: bug, code-smell, … — the suggestion list contains only repo labels. The label exists as a sixfold_space org-level label (id 280), and the same call with add_labels=["280"] succeeds: Gitea attaches the org label to the issue fine (verified — the issue's labels array carries the org-label URL …/api/v1/orgs/sixfold_space/labels/280).

So the API supports org labels on issues; madtea's name→ID resolver only consults GET /repos/{owner}/{repo}/labels and never GET /orgs/{org}/labels.

Expected

By-name resolution (everywhere labels are accepted by name: madt_issues create/edit add_labels/remove_labels, madt_labels assign/set, the CLI equivalents) resolves against repo labels first, then the owning org's labels when the repo belongs to an org. The "unknown label" suggestion list should include both sets. Repo label shadows a same-named org label (more-specific wins) — note the tie-break in the resolver comment.

Notes

  • The org status/ scope is now the house standard (documented in the org handbook), so every triage pass hits this until fixed; the workaround is applying by ID in string form (add_labels=["280"]).
  • Check gh behavior for the suggestion-list shape only (GitHub has no org-level issue labels, so there is no gh-parity constraint on the resolution itself).
  • COMPARISON/COMPAT rows if any user-visible flag text changes (probably none — pure resolver widening).

Acceptance

  • add_labels=["status/needs-decision"] on a repo in an org carrying that org label attaches it (no ID needed).
  • Unknown-label error lists repo + org labels as candidates.
  • Repo-over-org shadowing on name collisions, tested.
  • Hermetic tests for resolver + suggestion list; existing repo-label behavior regression-pinned.
## Observed (2026-07-10, dogfooding the new org `status/` label scope) `madt_issues action=edit add_labels=["status/needs-decision"]` failed with `unknown label "status/needs-decision"; available labels: bug, code-smell, …` — the suggestion list contains only **repo** labels. The label exists as a sixfold_space **org-level** label (id 280), and the same call with `add_labels=["280"]` succeeds: Gitea attaches the org label to the issue fine (verified — the issue's labels array carries the org-label URL `…/api/v1/orgs/sixfold_space/labels/280`). So the API supports org labels on issues; madtea's name→ID resolver only consults `GET /repos/{owner}/{repo}/labels` and never `GET /orgs/{org}/labels`. ## Expected By-name resolution (everywhere labels are accepted by name: `madt_issues` create/edit add_labels/remove_labels, `madt_labels` assign/set, the CLI equivalents) resolves against repo labels first, then the owning org's labels when the repo belongs to an org. The "unknown label" suggestion list should include both sets. Repo label shadows a same-named org label (more-specific wins) — note the tie-break in the resolver comment. ## Notes - The org `status/` scope is now the house standard (documented in the org handbook), so every triage pass hits this until fixed; the workaround is applying by ID in string form (`add_labels=["280"]`). - Check gh behavior for the suggestion-list shape only (GitHub has no org-level issue labels, so there is no gh-parity constraint on the resolution itself). - COMPARISON/COMPAT rows if any user-visible flag text changes (probably none — pure resolver widening). ## Acceptance - `add_labels=["status/needs-decision"]` on a repo in an org carrying that org label attaches it (no ID needed). - Unknown-label error lists repo + org labels as candidates. - Repo-over-org shadowing on name collisions, tested. - Hermetic tests for resolver + suggestion list; existing repo-label behavior regression-pinned.
hexajon commented 2026-07-11 22:08:53 +00:00 (Migrated from codeberg.org)

More evidence, this time server-side: the Forgejo list endpoint's labels= filter has the same org-label blindness. GET /repos/sixfold_space/madtea/issues?state=open&labels=status/needs-decision silently ignored the filter and returned all 32 open issues — including issues with no labels at all — because the server's by-name lookup only consults repo labels. The label is genuinely attached to issues (e.g. an existing issue carries it, org-label id 280), and filtering/assigning by numeric ID works fine.

Implication for the fix here: after widening the client-side resolver, prefer passing label IDs (not names) to upstream labels= query params — or filter client-side — since the server's by-name filter can't be trusted for org labels and fails open (no-op) rather than erroring.

More evidence, this time server-side: the Forgejo list endpoint's `labels=` filter has the same org-label blindness. `GET /repos/sixfold_space/madtea/issues?state=open&labels=status/needs-decision` silently ignored the filter and returned **all 32 open issues** — including issues with no labels at all — because the server's by-name lookup only consults repo labels. The label is genuinely attached to issues (e.g. an existing issue carries it, org-label id 280), and filtering/assigning by numeric ID works fine. Implication for the fix here: after widening the client-side resolver, prefer passing **label IDs** (not names) to upstream `labels=` query params — or filter client-side — since the server's by-name filter can't be trusted for org labels and fails open (no-op) rather than erroring.
hexajon commented 2026-07-11 22:08:54 +00:00 (Migrated from codeberg.org)

Upstream verdict: Gitea bug (fixed in Forgejo); org labels are used correctly — and madtea's resolver is narrower than BOTH upstreams

Source-verified 2026-07-10 against the mirrors mirror-org/go-gitea-gitea (branch main) and mirror-org/codeberg.org-forgejo-forgejo (branch forgejo; the forgejo-forgejo mirror is empty/broken — 0 KB, last synced 112 days ago, worth deleting or re-pointing).

1. The per-repo list filter blindness is a Gitea-only oversight, already fixed in Forgejo.

  • Gitea ListIssues (routers/api/v1/repo/issue.go) resolves labels= names solely via GetLabelIDsInRepoByNamesWhere("repo_id = ?", repoID).In("name", labelNames), no org fallback, and fail-open by documented design ("Non existent labels are discarded" in the swagger comment; the helper "silently ignores label names that do not belong to the repository").
  • Forgejo's ListIssues adds exactly the missing branch: if ctx.Repo().Owner.IsOrganization() { orgLabelIDs, err := issues_model.GetLabelIDsInOrgByNames(...) } — repo + org, appended.
  • No comment or swagger note anywhere documents an intent to exclude org labels; the sibling attach code has the org branch, ListIssues in Gitea simply lacks it.

2. Org labels are first-class for name-based use — the ATTACH path in BOTH products resolves org labels by name. prepareForReplaceOrAdd (routers/api/v1/repo/issue_label.go) accepts IDs or names and resolves names via GetLabelIDsInRepoByNames + GetLabelIDsInOrgByNames. So our house status/ org scope is not misuse; the platform expects it.

3. Consequence for madtea: our client-side resolver is strictly narrower than the server it talks to. The observed unknown label "status/needs-decision" on edit/assign was madtea refusing client-side; had we passed the raw name through, the server (Gitea AND Forgejo) would have attached the org label fine. The fix here brings us up to parity with the server's own attach semantics (repo first, then owning org, repo shadows org — same as Forgejo's resolution order).

4. Filter caveat stands: for labels= on per-repo list, keep passing IDs (or filter client-side) — Gitea servers fail open on org-label names (return the full unfiltered list), and that's worse than an error. Forgejo servers handle names; add the divergence as a COMPAT.md row with the fix (Forgejo: per-repo list filter resolves org-label names; Gitea: repo labels only, unknown names silently discarded).

Cross-repo SearchIssues is unaffected in both (uses the name-global GetLabelIDsByNames).

## Upstream verdict: Gitea bug (fixed in Forgejo); org labels are used correctly — and madtea's resolver is narrower than BOTH upstreams Source-verified 2026-07-10 against the mirrors `mirror-org/go-gitea-gitea` (branch `main`) and `mirror-org/codeberg.org-forgejo-forgejo` (branch `forgejo`; the `forgejo-forgejo` mirror is empty/broken — 0 KB, last synced 112 days ago, worth deleting or re-pointing). **1. The per-repo list filter blindness is a Gitea-only oversight, already fixed in Forgejo.** - Gitea `ListIssues` (routers/api/v1/repo/issue.go) resolves `labels=` names solely via `GetLabelIDsInRepoByNames` — `Where("repo_id = ?", repoID).In("name", labelNames)`, no org fallback, and fail-open by documented design ("Non existent labels are discarded" in the swagger comment; the helper "silently ignores label names that do not belong to the repository"). - Forgejo's `ListIssues` adds exactly the missing branch: `if ctx.Repo().Owner.IsOrganization() { orgLabelIDs, err := issues_model.GetLabelIDsInOrgByNames(...) }` — repo + org, appended. - No comment or swagger note anywhere documents an intent to exclude org labels; the sibling attach code has the org branch, `ListIssues` in Gitea simply lacks it. **2. Org labels are first-class for name-based use — the ATTACH path in BOTH products resolves org labels by name.** `prepareForReplaceOrAdd` (routers/api/v1/repo/issue_label.go) accepts IDs or names and resolves names via `GetLabelIDsInRepoByNames` + `GetLabelIDsInOrgByNames`. So our house `status/` org scope is not misuse; the platform expects it. **3. Consequence for madtea: our client-side resolver is strictly narrower than the server it talks to.** The observed `unknown label "status/needs-decision"` on edit/assign was madtea refusing client-side; had we passed the raw name through, the server (Gitea AND Forgejo) would have attached the org label fine. The fix here brings us up to parity with the server's own attach semantics (repo first, then owning org, repo shadows org — same as Forgejo's resolution order). **4. Filter caveat stands:** for `labels=` on per-repo list, keep passing IDs (or filter client-side) — Gitea servers fail open on org-label names (return the full unfiltered list), and that's worse than an error. Forgejo servers handle names; add the divergence as a COMPAT.md row with the fix (Forgejo: per-repo list filter resolves org-label names; Gitea: repo labels only, unknown names silently discarded). Cross-repo `SearchIssues` is unaffected in both (uses the name-global `GetLabelIDsByNames`).
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
sixfold-space/madtea#12
No description provided.