{"record":{"id":"31e8eae92f3dc058","repo":"mattermost-community/focalboard","slug":"cannot-notify-block-subscribers-for-block-s-w","errorCode":null,"errorMessage":"cannot notify block subscribers for block %s: %w","messagePattern":"cannot notify block subscribers for block (.+?): %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/services/notify/notifysubscriptions/subscriptions_backend.go","lineNumber":153,"sourceCode":"\t}\n\n\t// notify card subscribers\n\tsubs, err = b.appAPI.GetSubscribersForBlock(evt.Card.ID)\n\tif err != nil {\n\t\tmerr.Append(fmt.Errorf(\"cannot fetch subscribers for card %s: %w\", evt.Card.ID, err))\n\t}\n\tif err = b.notifySubscribers(subs, evt.Card.ID, model.TypeCard, evt.ModifiedBy.UserID); err != nil {\n\t\tmerr.Append(fmt.Errorf(\"cannot notify card subscribers for card %s: %w\", evt.Card.ID, err))\n\t}\n\n\t// notify block subscribers (if/when other types can be subscribed to)\n\tif evt.Board.ID != evt.BlockChanged.ID && evt.Card.ID != evt.BlockChanged.ID {\n\t\tsubs, err := b.appAPI.GetSubscribersForBlock(evt.BlockChanged.ID)\n\t\tif err != nil {\n\t\t\tmerr.Append(fmt.Errorf(\"cannot fetch subscribers for block %s: %w\", evt.BlockChanged.ID, err))\n\t\t}\n\t\tif err := b.notifySubscribers(subs, evt.BlockChanged.ID, evt.BlockChanged.Type, evt.ModifiedBy.UserID); err != nil {\n\t\t\tmerr.Append(fmt.Errorf(\"cannot notify block subscribers for block %s: %w\", evt.BlockChanged.ID, err))\n\t\t}\n\t}\n\treturn merr.ErrorOrNil()\n}\n\n// notifySubscribers triggers a change notification for subscribers by writing a notification hint to the database.\nfunc (b *Backend) notifySubscribers(subs []*model.Subscriber, blockID string, idType model.BlockType, modifiedByID string) error {\n\tif len(subs) == 0 {\n\t\treturn nil\n\t}\n\n\thint := &model.NotificationHint{\n\t\tBlockType:    idType,\n\t\tBlockID:      blockID,\n\t\tModifiedByID: modifiedByID,\n\t}\n\n\thint, err := b.appAPI.UpsertNotificationHint(hint, b.getBlockUpdateFreq(idType))","sourceCodeStart":135,"sourceCodeEnd":171,"githubUrl":"https://github.com/mattermost-community/focalboard/blob/a84bbb65e32edf972856b329417096ac413518e9/server/services/notify/notifysubscriptions/subscriptions_backend.go#L135-L171","documentation":"BlockChanged aggregates notification failures into a multi-error (merr). This entry is appended when notifySubscribers fails to trigger change notifications for a block's subscribers — i.e., writing the notification hint to the database failed. The block ID and modified-by user are included; the underlying error is wrapped with %w. The block change itself succeeded; only subscriber notification delivery failed.","triggerScenarios":"A block change event (card/view/board update) fires BlockChanged for a block that has subscribers, and notifySubscribers fails — typically because UpsertNotificationHint returns a database error, or the notifier callback fails on the hint.","commonSituations":"Database outages or write contention on the notifications table during heavy board activity; blocks with many subscribers causing hint-write contention; misconfigured notification storage in self-hosted deployments.","solutions":["Check the wrapped cause (errors.As on the multi-error) for the underlying DB error from UpsertNotificationHint","Verify database connectivity and the notification hints table health","Re-trigger the block change or manually notify subscribers if delivery was lost","Inspect notifySubscribers and onNotifyHint logs for the specific failing subscriber"],"exampleFix":"// before\nif err := backend.BlockChanged(evt); err != nil {\n\treturn err\n}\n// after\nif err := backend.BlockChanged(evt); err != nil {\n\tvar merr *model.MultiError\n\tif errors.As(err, &merr) {\n\t\tlogger.Warn(\"partial notification failure\", \"errs\", merr.Error())\n\t}\n\treturn err\n}","handlingStrategy":"type-guard","validationCode":null,"typeGuard":"func unwrapMultiErrors(err error) []error {\n\tvar merr *model.MultiError\n\tif errors.As(err, &merr) {\n\t\treturn merr.Errors\n\t}\n\treturn []error{err}\n}\nfunc isNotifyHintFailure(err error) bool {\n\tfor _, e := range unwrapMultiErrors(err) {\n\t\tif strings.Contains(e.Error(), \"cannot notify block subscribers\") {\n\t\t\treturn true\n\t\t}\n\t}\n\treturn false\n}","tryCatchPattern":"if err := backend.BlockChanged(evt); err != nil {\n\tfor _, e := range unwrapMultiErrors(err) {\n\t\tlogger.Warn(\"block notification failure\", \"err\", e)\n\t}\n\t// block change already succeeded; log and continue\n}","preventionTips":["Treat notification errors as non-fatal to the block change itself","Monitor the notification hints table and database write latency","Unpack multi-errors with errors.As to see each subscriber-level failure","Keep subscriber lists clean (remove deleted users) to reduce hint-write failures"],"tags":["go","notifications","subscriptions","database"],"backgroundTag":"notification-hint-write-failed","analyzedSha":"a84bbb65e32edf972856b329417096ac413518e9","analyzedAt":"2026-08-30T09:22:20.720Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}