bug(labels): by-name label resolution ignores org-level labels — "unknown label" for a label the API accepts by ID #12
Labels
No labels
breaking
bug
documentation
enhancement
epic
good first issue
help wanted
refactoring
resolution/duplicate
resolution/invalid
resolution/wontfix
security
severity/critical
severity/high
severity/low
severity/medium
status/abandoned
status/blocked
status/needs-decision
status/needs-info
status/needs-verification
testing
upstream
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference
sixfold-space/madtea#12
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Observed (2026-07-10, dogfooding the new org
status/label scope)madt_issues action=edit add_labels=["status/needs-decision"]failed withunknown 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 withadd_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}/labelsand neverGET /orgs/{org}/labels.Expected
By-name resolution (everywhere labels are accepted by name:
madt_issuescreate/edit add_labels/remove_labels,madt_labelsassign/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
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"]).Acceptance
add_labels=["status/needs-decision"]on a repo in an org carrying that org label attaches it (no ID needed).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-decisionsilently 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.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(branchmain) andmirror-org/codeberg.org-forgejo-forgejo(branchforgejo; theforgejo-forgejomirror 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.
ListIssues(routers/api/v1/repo/issue.go) resolveslabels=names solely viaGetLabelIDsInRepoByNames—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").ListIssuesadds exactly the missing branch:if ctx.Repo().Owner.IsOrganization() { orgLabelIDs, err := issues_model.GetLabelIDsInOrgByNames(...) }— repo + org, appended.ListIssuesin 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 viaGetLabelIDsInRepoByNames+GetLabelIDsInOrgByNames. So our housestatus/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
SearchIssuesis unaffected in both (uses the name-globalGetLabelIDsByNames).