The last client's name is still hiding in this codebase
I shipped a brand-new product, and the last client's name was still quietly running the auth flow and routing my alert emails.
I shipped what everyone agreed was a brand-new product, and the previous client’s name was still doing real work inside it. Not in a comment. In the auth flow, deciding whether your invite link let you in.
The build was a clone. A logistics platform I’d shipped for one company became the starting skeleton for the next, because that’s how you move fast when the bones are the same. I renamed the obvious things: the title bar, the package, the splash screen, the about page. It looked like a new product from every angle a person actually looks at. The old name was gone everywhere I thought to look, which is a very different statement from the old name is gone.
The fingerprints that pass review
The leftovers weren’t in the places a clone-and-rename teaches you to check. They were in the seams, where one system hands a value to another and nobody re-reads the string in between.
- The invite links broke. New users clicked through and got bounced, with no useful error, just a flat refusal. The auth token was namespaced under the previous product’s name, a key set once in the old codebase and read back under the new one. The old writer and the new reader disagreed about a string that no screen ever displayed. The token was technically present and completely unredeemable.
- The alert emails pointed at a ghost. Operational alerts for the new system went out with links to a hard-coded domain from the project before this one. Two clients ago. The mailer template had been copied forward so many times that it carried a domain belonging to a product I’d basically forgotten, cheerfully inviting people to click into nothing.
Each of these reads as correct in a pull request. The auth code is sound. The mailer is sound. The bug isn’t in any one file, it’s in the assumption that a value written under the old identity would mean the same thing under the new one. You can approve every diff and still ship a product wearing someone else’s name under its clothes.
Why rename is the wrong mental model
When you say “rename the product,” your brain pictures the visible surface: the things with the name printed on them. But a clone’s real identity isn’t its title. It’s a scatter of identifiers buried in config keys, storage namespaces, email templates, deep-link schemes, OAuth callback URLs, push-notification topics, analytics project names, and the occasional hard-coded URL someone typed in a hurry three projects ago. None of those render. All of them route.
So the old name doesn’t survive as a label you forgot to change. It survives as plumbing. And plumbing only announces itself when something doesn’t reach the other end.
A clone isn’t renamed when the screens stop showing the old name. It’s renamed when you’ve grepped the whole tree for it and found nothing.
Grep the whole tree before you call it new
The fix was not clever. It was a single search across the entire repository for the previous product’s name, every casing, every separator, the slug version and the spaced version and the one-word-smushed version. The matches were the bugs. Once I had them in front of me as a list, fixing them was an afternoon. Finding them by waiting for users to fall through was going to be a month.
So this is now a release step, not a vibe. Before a cloned product gets called new, I grep the tree for every prior client’s name and every prior product’s name, and I don’t trust “I renamed it” until that search comes back empty. A clone-and-rename always leaves a long tail of leftover identifiers, and they all pass review, because review reads files and the bug lives between them. The name on the splash screen is the easy one. The name in the auth namespace is the one that decides whether your first real user ever gets in.