← All requests
0

canonical label identity — one stable workspace-scoped label id referenced by tasks

Openthriz0 (GitHub) · · edited

Problem Statement

Today a label's identity is resolved by its mutable row fields — name and (task_id, name) for
assignments — instead of a stable canonical identifier. The label table already scopes labels to
a workspace (workspace_id on every row), but the identity itself is implicit and inconsistent
across surfaces. Concretely:

  1. Task-level-only labels bypass the management UI entirely. Labels created through the API/MCP
    with a taskId (and no workspace definition) create task-level rows that never appear under
    Settings > Workspace > Labels (the page keeps only taskId === null). This is the primary
    management point of labels, yet those labels cannot be seen, renamed, recolored, or deleted
    from the web app. That is the biggest incoherence: some labels exist only as task-level rows.
  2. Edited source rows diverge on re-attach. After a task-level source row is renamed or
    recolored, attaching the same id again can insert a second, differently-named row next to an
    earlier copy, or return a stale-colored row — attach conflict resolution only matches by
    (task_id, current name). Reproduction: attach a label id, rename it, then attach the same id
    to another task — the second row carries the new name while the first task keeps the old one:
    one id, two differently-named rows. Reported by the Qodo review on #1730 as "Edited labels
    diverge when reattached".
  3. N tasks labelled "bug" produce N rows, and raw-API/CLI flows end up shaped around mutable
    row ids instead of a stable label identity.

Proposed Solution

One canonical identifier per label, scoped to its workspace. A label is identified by
(workspace_id, label id) — the id never changes, and the name/color become ordinary mutable
display fields. The workspace scoping already exists in the schema; the redesign changes the
identity, not the scope.

  • Assignments move to a task_labels join table (task_id, label_id); label responses expose
    the attached tasks as a taskIds array, or via a reverse GET /label/{id}/tasks, instead of
    holding independent name/color snapshots.
  • Rename and recolor become safe by construction: they update the canonical label's fields and
    follow it everywhere attached — no more cascade-vs-snapshot ambiguity, no stale-colored rows.
  • Attach/detach/idempotency resolve by the canonical label id, not by the mutable name.
  • Every label becomes visible and manageable in Settings > Workspace > Labels, including
    previously task-only ones (reconciled into workspace-scoped labels during migration).
  • Include the label-based task lookup proposed in #1490 (taskIds / a reverse
    GET /label/{id}/tasks), which this redesign makes trivial.
  • Automated tests for shared label identity, rename and recolor reattachment, and settings-page
    management of previously task-only labels, plus a guarded dedupe/reconcile migration for
    existing installs.

The trade-off to accept: this is a breaking data-model change (labels and assignments stop being
denormalized into one table), so it needs a migration, dedupe/reconcile scripts for existing
installations, and web/MCP/imports/webhooks updates in the same change. Two decisions the
migration carries: creating a label with a taskId becomes create-or-reuse the workspace
definition then attach (no more task-only creation), and dedupe collisions (same workspace name,
differing colors) need an explicit tie-break — default proposal: an existing workspace definition
wins, otherwise the first-created row wins.

Alternative Solutions

  • Relax only the settings-page filter (show task-level rows in Settings > Workspace > Labels):
    no API change and it fixes symptom 1's visibility, but nothing else — N duplicate rows still
    exist, renames still resolve by mutable name, and the page becomes a list of duplicates rather
    than labels. A stopgap, not a fix.
  • Keep the two-row storage and treat (workspace_id, name) as a natural key (additive, minor):
    every row id resolves to its logical label (workspace_id, name), attach resolves by that
    identity, task-only rows get promoted into definitions. Workable if renames are rare and cascade
    semantics are documented — but renames remain ambiguous precisely because the name is the key,
    which is the fragility this issue is about.
  • The minimal behavior fix in #1730 (attach adds instead of moves) is complementary and already
    removes the data-loss trap, but cannot fix this without the identity work above.

Relevant Context

Follow-up of #1490 — related to the minimal fix in #1730, which does not require this change to be correct.

  • Evidence collected while working on #1730 (minimal fix for #1490): the Qodo review finding
    "Edited labels diverge when reattached" (rename/recolor reattachment), the settings-page
    invisibility of task-only labels (described in the #1490 thread), and the storage analysis above.
  • Design constraints already visible at main: deleting a workspace definition cascades to
    same-name task-level rows (delete-label.ts), renaming a definition cascades by the old name
    (update-label.ts), issue imports prefer the workspace color, and migration 0020
    (gitea_dedup_guards.sql) deduplicated rows before adding the constraints. In other words,
    (workspace_id, name) is already treated as the logical key everywhere except the schema itself —
    the redesign would replace that implicit key with an explicit canonical id.
  • The lookup part (taskIds / a reverse GET /label/{id}/tasks / a list_label_tasks MCP tool)
    is already implemented on my side and can land as a PR once this is on the roadmap.

Does this feature align with Kaneo's focus on simplicity?

Yes — one obvious model (a label is a workspace-scoped entity with a stable id, tasks reference it)
instead of an implicit alias system where mutable names act as an identity and some labels are
unmanageable from the primary UI.


Originally requested by @thriz0 on 2026-09-16. Original GitHub request #1731.

Issue #1731

Discussion 1

Sign in to comment.

Keyboard shortcuts

Anywhere

Go to requests
gh
Go to changelog
gc
Go to notifications
gn
Next or previous page
norp
Leave a text field or close a menu
Esc
Show this list
?

Requests

Move to the next or previous request
jork
Open the selected request
oorEnter
Vote on the selected request
v
Search
/
New request
c

Request

Vote
v
Write a comment
c
Edit
e