jamiepine/voicebox · warning · HTTPException

Binding not found

Error message

Binding not found

What it means

404 from DELETE /mcp/bindings/{client_id}. The route queries MCPClientBinding by client_id; if no row matches (the client was never bound, or was already deleted) it raises HTTPException(404, 'Binding not found'). client_id is the same value the MCP client sends in X-Voicebox-Client-Id or the stdio shim reads from VOICEBOX_CLIENT_ID.

Solutions

  1. Treat 404 as success if your goal is 'ensure this binding is gone' (the end state is what you wanted).
  2. Before deleting, GET /mcp/bindings and confirm the client_id is present.
  3. Verify the exact client_id string (case-sensitive) matches what was used in the PUT that created it.
  4. Guard the UI against double-submit (disable the button after the first request).

Example fix

// before
await api.delete(`/mcp/bindings/${clientId}`)  // throws on 404
// after
const res = await api.delete(`/mcp/bindings/${clientId}`).catch(e => e.response)
if (res?.status === 404) { /* already gone — fine */ }
Defensive patterns

Strategy: validation

Validate before calling

async function deleteBinding(clientId: string) {
  const list = await (await fetch('/mcp/bindings')).json();
  const exists = list.items.some(b => b.client_id === clientId);
  if (!exists) return { deleted: clientId, alreadyGone: true }; // treat as success
  const res = await fetch(`/mcp/bindings/${encodeURIComponent(clientId)}`, {method:'DELETE'});
  if (!res.ok && res.status !== 404) throw new Error(`delete failed: ${res.status}`);
  return { deleted: clientId };
}

Try / catch

try {
  await api.delete(`/mcp/bindings/${clientId}`);
} catch (e) {
  if (e.response?.status !== 404) throw e;
  // 404 is the desired end state — binding is gone
}

Prevention

When it happens

Trigger: DELETE /mcp/bindings/ClaudeCode when no row exists for that client_id; calling delete twice (idempotency not implemented — the second call 404s); passing a client_id with different casing/whitespace than the one stored at PUT /mcp/bindings.

Common situations: UI delete fired twice on a quick double-click; client_id mismatch after renaming an MCP client; stale frontend state holding a binding id that a coworker already removed; race between two browser tabs editing bindings.

Related errors


AI-assisted analysis of jamiepine/voicebox@51f49dea19 (2026-08-12). Data as JSON: /api/errors/8c9c5e9e940f41e6. Report an issue: GitHub.

Appendix: source

Thrown at backend/routes/mcp_bindings.py:76

    row.default_personality = data.default_personality
    row.updated_at = datetime.now(timezone.utc)
    db.commit()
    db.refresh(row)
    return models.MCPClientBindingResponse.model_validate(row)


@router.delete("/mcp/bindings/{client_id}")
async def delete_mcp_binding(
    client_id: str,
    db: Session = Depends(get_db),
):
    row = (
        db.query(MCPClientBinding)
        .filter(MCPClientBinding.client_id == client_id)
        .first()
    )
    if row is None:
        raise HTTPException(status_code=404, detail="Binding not found")
    db.delete(row)
    db.commit()
    return {"deleted": client_id}

View on GitHub (pinned to 51f49dea19)