Finding duplicate logins in Bitwarden, without deleting anything
Author’s note: this post was drafted by Claude (Anthropic) from my project notes and source code, then reviewed and edited by me before publishing. The voice and judgments are mine; the typing isn’t.
The gap
Bitwarden has no built-in deduplicate. Years of saving logins leaves you with the same account stored three times — a http copy and a https copy, an old password and a new one, two saves of the same thing from different devices. It’s clutter, and clutter in a password manager is mildly dangerous: you can’t tell at a glance which copy is current.
So I wrote a small Python tool, bw_dedupe.py, that finds the duplicates and reports them. It’s about 200 lines and has no dependencies beyond the official Bitwarden CLI.
How it decides what’s a duplicate
It groups login items by name + username, case-insensitively. URIs are shown for context but don’t split groups, so a http:// and a https:// copy of the same account still land together. Within a group it then splits into two buckets:
- Exact duplicates — same name, same username, and an identical password. Safe to keep one and drop the rest. The tool picks which to keep (your favourite, otherwise the most recently updated) and lists the ids of the others.
- Conflicting — same name and username but different passwords. These might be intentional — two real accounts, or a password you changed — so it doesn’t suggest anything. It just lists them for you to eyeball.
That split is the whole idea. The machine is confident about exact duplicates and honest about its uncertainty everywhere else.
The actual design decision: it never deletes
The dedup logic is the easy part. The decision I actually thought about was what the tool is allowed to do, and the answer is: nothing.
bw_dedupe.py is read-only. It lists your items, prints a report, and stops. At most — if you ask for it with --write-deletes — it writes a file of bw delete item <id> commands for you to open, read, and run yourself. That file contains item names and ids, never passwords.
A few rules fall out of that stance:
- Your master password is never seen or stored. You unlock the vault yourself with
bw unlock, which hands back a session key; the tool uses the session, so the password never reaches the script. - Vault data lives in memory only. Nothing is written to disk unless you explicitly ask for the review file.
- No password material is ever printed. The report shows names, usernames, hosts, and ids — never a secret.
- Deletion is always a separate step you take, after reading the list, in your own shell.
I could have made it delete the extras automatically. It would have been fewer steps. But a tool that quietly modifies a password vault is exactly the kind of convenience I don’t want — the failure mode is “it removed the wrong copy and I didn’t notice.” Report-first, act-yourself is slower by one step and removes that entire class of mistake. For something holding every credential I have, that’s the trade I want every time.
The general principle
This is a tiny project, but it’s a clean example of a rule I keep coming back to: automation that touches something important should propose, not perform. Show me the diff, let me run it. The same instinct shows up in my file organiser (dry-run before anything moves) and in a Bitcoin-vault tool I keep meaning to write up. The dedup logic took an afternoon. The “never deletes” decision is the part worth keeping.
— Luke Simmons, Auckland