Stirling-Tools/Stirling-PDF · error · Error
Member has no team to be removed from
Error message
Member has no team to be removed from
What it means
Thrown by the SaaS usersBackend removeMember() when the Member object passed in has a null teamId. The DELETE endpoint requires a concrete teamId in the URL path (/api/v1/team/{teamId}/members/{id}), so without it the request cannot be constructed.
Source
Thrown at frontend/editor/src/saas/portal/usersBackend.ts:245
const tid = teamId ?? (await resolveTeam())?.teamId;
if (tid == null) throw new Error("No team to invite to");
await apiClient.local.json(`/api/v1/team/invite`, {
method: "POST",
body: { teamId: tid, email },
});
return { successCount: 1, failureCount: 0 };
},
async renameTeam(teamId: number, newName: string): Promise<void> {
await apiClient.local.json(`/api/v1/team/${teamId}/rename`, {
method: "POST",
body: { newName },
});
},
async removeMember(member: Member): Promise<void> {
if (member.teamId == null) {
throw new Error("Member has no team to be removed from");
}
await apiClient.local.json(
`/api/v1/team/${member.teamId}/members/${member.id}`,
{ method: "DELETE" },
);
},
async cancelInvitation(invitationId: number): Promise<void> {
await apiClient.local.json(`/api/v1/team/invitations/${invitationId}`, {
method: "DELETE",
});
},
};
View on GitHub (pinned to 9ef20dcab8)
Solutions
- Filter the member list to exclude entries with null teamId before rendering remove buttons
- Validate member.teamId in the UI component before enabling the remove action
- Ensure the backend member query always includes teamId for every row
- Use cancelInvitation() instead of removeMember() for pending invitations
Example fix
// before
async removeMember(member: Member): Promise<void> {
if (member.teamId == null) {
throw new Error("Member has no team to be removed from");
}
await apiClient.local.json(
`/api/v1/team/${member.teamId}/members/${member.id}`,
{ method: "DELETE" },
);
}
// after (in the UI caller)
const membersWithTeam = members.filter((m) => m.teamId != null);
// only render remove buttons for membersWithTeam Defensive patterns
Strategy: validation
Validate before calling
// Filter member list to only removable members const removableMembers = members.filter((m) => m.teamId != null); // Only render remove buttons for removableMembers
Type guard
function isRemovableMember(member: Member): member is Member & { teamId: number } {
return member.teamId != null;
} Try / catch
try {
await removeMember(member);
} catch (e) {
if (e instanceof Error && e.message.includes('no team')) {
// Use cancelInvitation for pending members instead
if (member.invitationId) await cancelInvitation(member.invitationId);
} else {
throw e;
}
} Prevention
- Filter member lists to exclude entries with null teamId before rendering remove actions
- Ensure the backend member query always includes teamId for accepted members
- Use cancelInvitation() for pending invitations rather than removeMember()
When it happens
Trigger: The member list returned by the backend includes an entry where teamId was not populated — e.g. a pending invitation row, a data inconsistency, or a member from a team that was since deleted. The UI attempts to remove this member.
Common situations: Member list includes invited-but-not-yet-accepted users whose teamId is null; stale member data after a team merge or migration; backend query omits teamId for certain member types.
Related errors
- No team to invite to
- No current team
- No team resolved yet
- SaaS API not configured — set VITE_SAAS_API_URL to enable po
- No SaaS session — admin must link an account before attended
AI-assisted analysis of Stirling-Tools/Stirling-PDF@9ef20dcab8 (2026-08-13).
Data as JSON: /api/errors/c640a1dd1413f1ae.
Report an issue: GitHub.