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

  1. Inspect the wrapped cause with errors.Unwrap / errors.Is to distinguish not-found from a real store failure.
  2. If the block was just deleted, treat this as expected: skip the notification instead of surfacing the error.
  3. Verify the blockID against the board's blocks before operating on it.
  4. 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

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


AI-assisted analysis of mattermost-community/focalboard@a84bbb65e3 (2026-08-30). Data as JSON: /api/errors/7b3856c1f995fafb. Report an issue: GitHub.