{"record":{"id":"86c8c7cdbaa99113","repo":"mattermost-community/focalboard","slug":"cannot-fetch-channel-member-for-user-s-w","errorCode":null,"errorMessage":"cannot fetch channel member for user %s: %w","messagePattern":"cannot fetch channel member for user (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/services/notify/plugindelivery/subscription_deliver.go","lineNumber":29,"sourceCode":"\n\tmm_model \"github.com/mattermost/mattermost/server/public/model\"\n)\n\nvar (\n\tErrUnsupportedSubscriberType = errors.New(\"invalid subscriber type\")\n)\n\n// SubscriptionDeliverSlashAttachments notifies a user that changes were made to a block they are subscribed to.\nfunc (pd *PluginDelivery) SubscriptionDeliverSlackAttachments(teamID string, subscriberID string, subscriptionType model.SubscriberType,\n\tattachments []*mm_model.SlackAttachment) error {\n\t// check subscriber is member of channel\n\t_, err := pd.api.GetUserByID(subscriberID)\n\tif err != nil {\n\t\tif model.IsErrNotFound(err) {\n\t\t\t// subscriber is not a member of the channel; fail silently.\n\t\t\treturn nil\n\t\t}\n\t\treturn fmt.Errorf(\"cannot fetch channel member for user %s: %w\", subscriberID, err)\n\t}\n\n\tchannelID, err := pd.getDirectChannelID(teamID, subscriberID, subscriptionType, pd.botID)\n\tif err != nil {\n\t\treturn err\n\t}\n\n\tpost := &mm_model.Post{\n\t\tUserId:    pd.botID,\n\t\tChannelId: channelID,\n\t}\n\n\tmm_model.ParseSlackAttachment(post, attachments)\n\n\t_, err = pd.api.CreatePost(post)\n\treturn err\n}\n","sourceCodeStart":11,"sourceCodeEnd":47,"githubUrl":"https://github.com/mattermost-community/focalboard/blob/a84bbb65e32edf972856b329417096ac413518e9/server/services/notify/plugindelivery/subscription_deliver.go#L11-L47","documentation":"SubscriptionDeliverSlackAttachments returns this error when verifying the subscriber via GetUserByID fails with an error other than not-found (not-found subscribers are silently skipped). The wrapped error indicates the subscriber's user record could not be retrieved from the Mattermost plugin API, so the subscription notification cannot be delivered. Note the message says 'channel member' but the actual call is a user lookup — the naming is misleading.","triggerScenarios":"Delivering a slack-attachment style notification to a subscriber whose GetUserByID call fails for reasons other than ErrNotFound — plugin API unreachable, deactivated user treated as a hard error, transient server error, or invalid subscriber ID format.","commonSituations":"Mattermost server restarts while notification deliveries are queued; corrupted subscription records with stale user IDs; rate limiting or permission errors on the plugin API; partially deleted users (subscriptions not cleaned up).","solutions":["Check the wrapped cause to distinguish transient API errors from persistent user problems","Clean up subscription records referencing deleted users","Retry delivery on transient plugin API failures","Verify plugin API connectivity and bot permissions"],"exampleFix":"// before\nif _, err := pd.api.GetUserByID(subscriberID); err != nil {\n\treturn fmt.Errorf(\"cannot fetch channel member for user %s: %w\", subscriberID, err)\n}\n// after\nif _, err := pd.api.GetUserByID(subscriberID); err != nil {\n\tif model.IsErrNotFound(err) {\n\t\treturn nil\n\t}\n\tif errors.Is(err, context.DeadlineExceeded) {\n\t\treturn err // transient; safe to retry\n\t}\n\treturn fmt.Errorf(\"cannot fetch channel member for user %s: %w\", subscriberID, err)\n}","handlingStrategy":"validation","validationCode":"// prune subscriptions pointing at deleted users before delivery runs\nsubs, err := appAPI.GetSubscribersForBlock(blockID)\nif err != nil {\n\treturn err\n}\nfor _, s := range subs {\n\tif _, err := pd.api.GetUserByID(s.UserID); err != nil {\n\t\t// remove or skip this subscription\n\t}\n}","typeGuard":"func subscriberExists(api UserAPI, subscriberID string) bool {\n\t_, err := api.GetUserByID(subscriberID)\n\treturn err == nil\n}","tryCatchPattern":"err := pd.SubscriptionDeliverSlackAttachments(evt, subscriptionType, subscriberID, extract)\nif err != nil {\n\tif model.IsErrNotFound(err) {\n\t\treturn nil // stale subscriber; drop\n\t}\n\t// transient API error: safe to retry\n\treturn retryDelivery(evt, subscriberID)\n}","preventionTips":["Delete subscriptions when users are removed","Distinguish not-found (skip) from transient errors (retry) using model.IsErrNotFound","Monitor plugin API error rates during notification bursts","Validate subscriber IDs when subscriptions are created"],"tags":["go","mattermost-plugin","subscriptions","notifications"],"backgroundTag":"user-lookup-failed","analyzedSha":"a84bbb65e32edf972856b329417096ac413518e9","analyzedAt":"2026-08-30T09:22:20.720Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}