Skip to content

Keys UX: unassign and delete are indistinguishable, and both say "removed" #191

Description

@NimbleCoAI

The problem

There are two controls that look similar, live in different places, and have radically
different blast radius — and the safe one reports itself using the same word as the
destructive one.

Unassign — the X in harness detail → Keys
(app/(dashboard)/harnesses/[id]/page.tsx:316 unassignKey)

  • PUT /api/keys/{id} with assignedTo filtered to drop just this harness
  • The key survives and stays attached to every other agent
  • Correctly recreates this agent so the credential is stripped from the running
    process (the inline comment citing D3 explains why recreate and not quick
    good, that part is right)
  • But the toast says "{provider} key removed"

Delete — the Keys page (app/(dashboard)/keys/page.tsx:461 → confirm → deleteKey,
handler at app/api/keys/[id]/route.ts:50)

  • services.keys.remove(id) drops it from the store and strips the credential from
    every assigned agent's .env
  • Then recreates every affected agent
  • Irreversible: the secret value is gone

Why it matters

An operator who clicks the X and reads "key removed" has no way to know whether they
just detached one agent or destroyed a credential shared across the fleet. That ambiguity
was hit for real today: a gh key (nimbleco-interim) was removed and the operator could
not tell afterwards whether it had been deleted everywhere or merely unassigned.

The only way to find out is to go and look at whether the key is still listed on the Keys
page — which is a diagnosis step that shouldn't be necessary.

Suggested fixes

  1. Change the unassign toast to name the scope: "{provider} key unassigned from {agent}", and if the key is still attached elsewhere, "still assigned to N other agent(s)". Never the bare word removed for a non-destructive action.
  2. Make the delete confirmation state the blast radius before it happens: "Delete
    {provider} key? This removes the stored secret and strips it from N agent(s), which
    will be recreated. This cannot be undone."
    Currently the confirm button carries no
    count.
  3. Distinguish the affordances — the per-agent control should read Unassign (or
    carry an unlink icon), not a bare X that reads as a delete.
  4. Optional: show the assignment count next to each key on the harness Keys list, so the
    shared-credential case is visible at the point of action.

Note

The underlying behaviour looks correct in both paths — this is a labelling and
disclosure defect, not a logic bug. The recreate-not-restart handling in both paths is
right and worth keeping.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions