mattermost-community/focalboard · error
cannot get block %s: %w
Error message
cannot get block %s: %w
What it means
getBoardForBlock resolves the board owning a block, used by notifySubscriptionChanged. This error wraps the failure of App.GetBlockByID(blockID): the block could not be loaded, so the parent board cannot be determined. It propagates the underlying store error (e.g. not-found, permission denied, database failure) under a contextual message.
Source
Thrown at server/app/boards.go:80
if err != nil {
return nil, nil, err
}
boardMetadata := model.BoardMetadata{
BoardID: boardID,
DescendantFirstUpdateAt: earliestTime,
DescendantLastUpdateAt: latestTime,
CreatedBy: board.CreatedBy,
LastModifiedBy: lastModifiedBy,
}
return board, &boardMetadata, nil
}
// getBoardForBlock returns the board that owns the specified block.
func (a *App) getBoardForBlock(blockID string) (*model.Board, error) {
block, err := a.GetBlockByID(blockID)
if err != nil {
return nil, fmt.Errorf("cannot get block %s: %w", blockID, err)
}
board, err := a.GetBoard(block.BoardID)
if err != nil {
return nil, fmt.Errorf("cannot get board %s: %w", block.BoardID, err)
}
return board, nil
}
func (a *App) getBoardHistory(boardID string, latest bool) (*model.Board, error) {
opts := model.QueryBoardHistoryOptions{
Limit: 1,
Descending: latest,
}
boards, err := a.store.GetBoardHistory(boardID, opts)
if err != nil {
return nil, fmt.Errorf("could not get history for board: %w", err)View on GitHub (pinned to a84bbb65e3)
Solutions
- Inspect the wrapped cause with errors.Unwrap / errors.Is to distinguish not-found from a real store failure.
- If the block was just deleted, treat this as expected: skip the notification instead of surfacing the error.
- Verify the blockID against the board's blocks before operating on it.
- Check database health and store logs if the cause is not 'not found'.
Example fix
// before
block, err := a.GetBlockByID(blockID)
if err != nil {
return nil, fmt.Errorf("cannot get block %s: %w", blockID, err)
}
// after
block, err := a.GetBlockByID(blockID)
if err != nil {
if errors.Is(err, store.ErrNotFound) {
return nil, nil // block already deleted; nothing to notify
}
return nil, fmt.Errorf("cannot get block %s: %w", blockID, err)
} Defensive patterns
Strategy: try-catch
Validate before calling
if blockID == "" {
return errors.New("skip subscription notification: empty blockID")
}
block, err := app.GetBlockByID(blockID)
if err != nil {
// block gone or store failure; skip notify
return nil
} Type guard
func isNotFound(err error) bool {
return errors.Is(err, store.ErrNotFound) || errors.Is(err, sql.ErrNoRows)
} Try / catch
board, err := app.getBoardForBlock(blockID)
if err != nil {
if isNotFound(errors.Unwrap(err)) {
return nil // block deleted; nothing to notify
}
return fmt.Errorf("subscription notify failed: %w", err)
} Prevention
- Delete blocks and cancel pending notifications atomically where possible.
- Check errors.Is on the unwrapped cause to separate not-found from infra errors.
- Log the raw blockID and cause before swallowing, to debug orphaned notifications.
When it happens
Trigger: notifySubscriptionChanged(blockID) fires after a block change but GetBlockByID fails: the block was deleted before the notification step, the blockID is invalid, or the store/database errors out.
Common situations: Rapid delete-then-update sequences where the subscription notification runs after the block row is gone; database connectivity problems; webhook/plugin code passing a wrong blockID.
Related errors
- cannot get board %s: %w
- could not get history for board: %w
- could not get blocks history descendants for board: %w
- %w userID: %s
- cannot create card: %w
AI-assisted analysis of mattermost-community/focalboard@a84bbb65e3 (2026-08-30).
Data as JSON: /api/errors/7b3856c1f995fafb.
Report an issue: GitHub.