Stirling-Tools/Stirling-PDF · error · Error
Member has no backend identity
Error message
Member has no backend identity
What it means
Thrown by setMemberSuspended() when member.username is falsy. The suspension call hits /api/v1/user/admin/changeUserEnabled/{username}, which requires a real backend username. Members without a username are pure fixtures/invited-not-yet-provisioned rows that have no backend identity, so suspending them is impossible.
Source
Thrown at frontend/editor/src/portal/api/users.ts:397
return;
}
// target === "member": drop any leadership, and normalise a non-ROLE_USER authority.
if (holdsLeader && member.teamId) {
await apiClient.local.form("/api/v1/team/removeOwner", {
teamId: String(member.teamId),
userId: member.id,
});
}
if (holdsAdmin || holdsWebOnly)
await setAuthority(member.username, "ROLE_USER");
}
export async function setMemberSuspended(
member: Member,
suspended: boolean,
): Promise<void> {
if (!member.username) throw new Error("Member has no backend identity");
await apiClient.local.form(
`/api/v1/user/admin/changeUserEnabled/${encodeURIComponent(member.username)}`,
{ enabled: String(!suspended) },
);
}
export async function removeMember(member: Member): Promise<void> {
if (!member.username) throw new Error("Member has no backend identity");
await apiClient.local.json(
`/api/v1/user/admin/deleteUser/${encodeURIComponent(member.username)}`,
{ method: "POST" },
);
}
export interface ResetPasswordOptions {
/** The new password (client-generated or admin-typed). */
newPassword: string;
forcePasswordChange?: boolean;View on GitHub (pinned to 9ef20dcab8)
Solutions
- Check member.username (and member.status) before offering the suspend action; hide it for invited/fixture members.
- If the member should have a username, re-run provisioning/invitation acceptance first.
- Validate at the call site and surface 'this member has no backend account yet' to the admin.
Example fix
// before — throws for members with no backend identity
export async function setMemberSuspended(member: Member, suspended: boolean) {
if (!member.username) throw new Error("Member has no backend identity");
...
}
// after — typed error + UI gating on username presence
if (!member.username) throw new NoBackendIdentityError(member.id);
// UI: disable the Suspend button when !member.username Defensive patterns
Strategy: validation
Validate before calling
import type { Member } from "@portal/api/users";
function hasBackendIdentity(member: Member): boolean { return !!member.username; }
// before suspend: if (!hasBackendIdentity(member)) blockSuspend(); Type guard
function isNoBackendIdentity(e: unknown): boolean {
return e instanceof Error && /no backend identity/i.test(e.message);
} Try / catch
try { await setMemberSuspended(member, true); }
catch (e) { if (isNoBackendIdentity(e)) { notify('Member has no backend account yet.'); return; } throw e; } Prevention
- Hide/disable the Suspend action for members with no username (invited/fixture).
- Check member.username and member.status before offering backend actions.
- Re-provision invited members before allowing backend mutations.
When it happens
Trigger: An admin attempts to suspend a member whose username is undefined — an invited-but-not-provisioned member, a demo fixture, or a row that never completed backend provisioning.
Common situations: Member is in 'invited' status and never accepted (no backend user created); a fixture-only member loaded in a demo/mock context; the roster sync dropped the username field.
Related errors
- Member must belong to a team to become its owner
- No team to invite to
- Member has no team to be removed from
- Email missing for this user. Please contact support for manu
- No valid license found. Please purchase a license before acc
AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13).
Data as JSON: /api/errors/86467ab5adc307f8.
Report an issue: GitHub.