Every Entry in Bugspot has two identifiers, and the two are doing different jobs.
In the database, an Entry’s primary key is a UUIDv7. UUIDv7 is sortable by time, so range scans on “recent entries” stay efficient, and we avoid the contention you get from a single sequence handing out IDs in a multi-tenant write path. Postgres 18 ships UUIDv7 generation natively, which means no custom function, no extension, no JIT-compiled bytea hack. uuidv7() and you’re done.
But UUIDs in URLs are awful. /entry/01934a8e-9b62-7c1a-9e3f-1a2b3c4d5e6f is not something a human reads out, types into a chat, or greps for in a ticket title. The standard trade-off is to give entries a per-project key — BUG-42, WEB-117, INFRA-3 — and use that in the URL, the CLI, commit messages, everything user-facing.
We did that, with one quiet wrinkle: the human key isn’t immutable.
When you move an entry between projects, and those projects use different key prefixes, the entry’s key changes. BUG-42 becomes WEB-3 (the next available number in the destination project’s keyspace). We could have made the key stick to the entry forever, but that turned BUG-42 into a lie — it would say BUG, but the entry would live in WEB, and the prefix-as-project-signal that makes the key useful in the first place would be gone.
So the rule we landed on: the UUID is the entry’s identity; the key is the entry’s address in its current project. Old keys redirect to the current one so bookmarks survive. Webhook payloads include both. The CLI accepts either.
Two identifiers, two jobs. The UUID is for the system; the key is for you.