FlowiseAI/Flowise · warning · Error
User is not a member of this channel
Error message
User is not a member of this channel
What it means
Thrown INSIDE the try block of RemoveChannelMemberTool._call (core.ts:525) when the members list returned by Graph contains no entry whose userId matches the requested userId. Because it is inside try, the catch at core.ts:539 swallows it into a formatResponse error string — the tool returns a string containing 'Error removing channel member: Error: User is not a member of this channel', it does NOT reject. The lookup uses strict equality on m.userId, so a UPN/email passed as userId will never match Graph's GUID-shaped userId field even if the user is a member.
Source
Thrown at packages/components/nodes/tools/MicrosoftTeams/core.ts:526
super({ ...toolInput, accessToken: args.accessToken, defaultParams: args.defaultParams })
}
protected async _call(arg: any): Promise<string> {
const params = { ...arg, ...this.defaultParams }
const { teamId, channelId, userId } = params
if (!teamId || !channelId || !userId) {
throw new Error('Team ID, Channel ID, and User ID are all required')
}
try {
// First get the membership ID
const membersEndpoint = `/teams/${teamId}/channels/${channelId}/members`
const membersResult = await this.makeTeamsRequest(membersEndpoint)
const member = membersResult.value?.find((m: any) => m.userId === userId)
if (!member) {
throw new Error('User is not a member of this channel')
}
const endpoint = `/teams/${teamId}/channels/${channelId}/members/${member.id}`
await this.makeTeamsRequest(endpoint, 'DELETE')
return this.formatResponse(
{
success: true,
message: 'Member removed from channel successfully'
},
params
)
} catch (error) {
return this.formatResponse(`Error removing channel member: ${error}`, params)
}
}
}
View on GitHub (pinned to abe4a8601a)
Solutions
- Verify the userId is an Azure AD object ID (GUID format) and matches a value previously returned by list_channel_members.
- If you only have UPN/email, resolve it via /users first and use the returned id.
- Parse the tool's return string for 'User is not a member' and treat as a no-op success rather than a hard failure.
- Refactor to move the lookup outside try or to a typed Result so callers can distinguish 'not a member' from a real Graph error.
- Consider idempotent semantics: removing a non-member should be 204, not an error.
Example fix
// before — strict GUID equality, throws and gets swallowed
const member = membersResult.value?.find((m: any) => m.userId === userId)
if (!member) {
throw new Error('User is not a member of this channel')
}
// after — return a structured no-op result, also fall back to email/UPN match
const member = membersResult.value?.find(
(m: any) => m.userId === userId || m.email === userId || m.user?.id === userId
)
if (!member) {
return this.formatResponse(
{ success: true, message: 'User was not a member; nothing to remove', idempotent: true },
params
)
} Defensive patterns
Strategy: validation
Validate before calling
// Resolve the membership ID BEFORE calling remove, so the tool call is a no-op-safe
async function resolveMembershipId(tool, teamId: string, channelId: string, userId: string): Promise<string | null> {
const raw = await tool.listChannelMembers({ teamId, channelId })
const members = JSON.parse(raw.split(TOOL_ARGS_PREFIX)[0]).members as Array<{ userId?: string; id: string; email?: string }>
const m = members.find((x) => x.userId === userId || x.email === userId)
return m?.id ?? null
}
if (await resolveMembershipId(tools, teamId, channelId, userId) === null) {
return { success: true, message: 'User was not a member; nothing to remove', idempotent: true }
} Type guard
function isChannelMemberShape(m: unknown): m is { userId: string; id: string; email?: string } {
return typeof m === 'object' && m !== null &&
typeof (m as any).id === 'string' &&
typeof (m as any).userId === 'string'
} Try / catch
// This error is swallowed into a response string — parse it
const raw = await removeChannelMemberTool.invoke(input)
const body = JSON.parse(raw.split(TOOL_ARGS_PREFIX)[0])
if (/User is not a member/.test(JSON.stringify(body))) {
// idempotent success — user already removed
return { success: true, idempotent: true }
}
if (!body.success) throw new Error(body.message ?? 'remove failed') Prevention
- Always resolve membership via list_channel_members first and pass the verified GUID.
- Treat 'not a member' as idempotent success, not an error.
- Remember this throw is swallowed by the tool's catch — it appears inside the returned string, not as a rejection.
- Guard against TOCTOU by treating a missing member as a successful no-op.
When it happens
Trigger: userId provided is not a current channel member; userId is a UPN/email while Graph returns GUIDs; membership was removed between the list and the delete (TOCTOU); defaultParams.userId stale from a previous context; the channel is a standard channel and the user is a member only transitively via team membership — Graph still lists them, but the userId field may differ.
Common situations: Agent reusing a userId from a different channel's member list; UI showing a user who left the channel; test fixtures using fake GUIDs; tenant sync lag.
Related errors
- Team ID, Channel ID, and User ID are all required
- At least one field to update must be provided
- Members list is required to create a chat
- User ID field cannot be empty when "Use Flowise Chat ID" is
- Mem0: "Use Flowise Chat ID" is ON, but no runtime chat ID (o
AI-assisted analysis of FlowiseAI/Flowise@abe4a8601a (2026-08-12).
Data as JSON: /api/errors/2453a18a0d2df9e7.
Report an issue: GitHub.