slopus/happy · error
Username is already taken
Error message
Username is already taken
What it means
usernameUpdate() checks that no OTHER account already owns the requested username before writing it. If a row with that username exists for a different user id, it throws 'Username is already taken'. The comment notes this 'should never happen' — a race or missing earlier uniqueness check let a duplicate through.
Source
Thrown at packages/happy-server/sources/app/social/usernameUpdate.ts:18
import { db } from "@/storage/db";
import { Context } from "@/context";
import { allocateUserSeq } from "@/storage/seq";
import { buildUpdateAccountUpdate, eventRouter } from "@/app/events/eventRouter";
import { randomKeyNaked } from "@/utils/randomKeyNaked";
export async function usernameUpdate(ctx: Context, username: string): Promise<void> {
const userId = ctx.uid;
// Check if username is already taken
const existingUser = await db.account.findFirst({
where: {
username: username,
NOT: { id: userId }
}
});
if (existingUser) { // Should never happen
throw new Error('Username is already taken');
}
// Update username
await db.account.update({
where: { id: userId },
data: { username: username }
});
// Send account update to all user connections
const updSeq = await allocateUserSeq(userId);
const updatePayload = buildUpdateAccountUpdate(userId, { username: username }, updSeq, randomKeyNaked(12));
eventRouter.emitUpdate({
userId, payload: updatePayload,
recipientFilter: { type: 'user-scoped-only' }
});
}View on GitHub (pinned to b824cd0a46)
Solutions
- Catch the error and surface 'username taken' to the user, prompting a different name.
- Retry the availability check (SELECT) and the update in a transaction to narrow the race window.
- Add/verify a unique constraint on Account.username so the DB is the final arbiter and handle P2002 from Prisma.
- Re-check availability immediately before the update call after any user idle time (form open, payment, etc.).
Example fix
// before
await updateUsername(userId, username); // throws if taken
// after
if (await isUsernameAvailable(username, userId)) {
await updateUsername(userId, username);
} else {
show('Username is already taken, choose another');
} Defensive patterns
Strategy: try-catch
Validate before calling
const taken = await db.account.findFirst({
where: { username, NOT: { id: userId } }
});
if (taken) throw new Error('Username is already taken'); Try / catch
try {
await api.updateUsername(userId, username);
} catch (e) {
if (e.message === 'Username is already taken') {
showUsernameUnavailable(username);
return;
}
throw e;
} Prevention
- Always run an availability check against the server right before submit (not when the form opens).
- Add a DB unique constraint on username and also catch Prisma P2002.
- Debounce availability checks as the user types to catch most conflicts early.
- After any conflict, re-seed the form's availability state before retry.
When it happens
Trigger: Two users concurrently requesting the same username and both passing the initial availability check; a caller invoking usernameUpdate for a username another account just claimed; retrying an update after a partial failure without re-checking availability.
Common situations: Username-set flows racing between devices; check-then-update without a DB-level unique constraint enforcement at this step; user resubmitting a form with a username taken in the meantime.
Related errors
- Usage: happy acp <agent-name> or happy acp -- <command> [arg
- Missing command after "--". Usage: happy acp -- <command> [a
- ACP session is not started
- Resume session handler not available
- Unsupported Claude goal action
AI-assisted analysis of slopus/happy@b824cd0a46 (2026-08-31).
Data as JSON: /api/errors/0371788461d2be43.
Report an issue: GitHub.