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

  1. Verify the userId is an Azure AD object ID (GUID format) and matches a value previously returned by list_channel_members.
  2. If you only have UPN/email, resolve it via /users first and use the returned id.
  3. Parse the tool's return string for 'User is not a member' and treat as a no-op success rather than a hard failure.
  4. Refactor to move the lookup outside try or to a typed Result so callers can distinguish 'not a member' from a real Graph error.
  5. 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

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


AI-assisted analysis of FlowiseAI/Flowise@abe4a8601a (2026-08-12). Data as JSON: /api/errors/2453a18a0d2df9e7. Report an issue: GitHub.