mattermost-community/focalboard · error

Block2Card fail: %w

Error message

Block2Card fail: %w

What it means

GetCardsForBoard fetches all card blocks of a board and converts each Block back into a Card with model.Block2Card. If any single block fails conversion (e.g. it is not a card-type block or its fields do not unmarshal into Card), this error wraps the cause and the whole call fails, returning no cards.

Source

Thrown at server/app/cards.go:57

func (a *App) GetCardsForBoard(boardID string, page int, perPage int) ([]*model.Card, error) {
	opts := model.QueryBlocksOptions{
		BoardID:   boardID,
		BlockType: model.TypeCard,
		Page:      page,
		PerPage:   perPage,
	}

	blocks, err := a.store.GetBlocks(opts)
	if err != nil {
		return nil, err
	}

	cards := make([]*model.Card, 0, len(blocks))
	for _, blk := range blocks {
		b := blk
		if card, err := model.Block2Card(b); err != nil {
			return nil, fmt.Errorf("Block2Card fail: %w", err)
		} else {
			cards = append(cards, card)
		}
	}
	return cards, nil
}

func (a *App) PatchCard(cardPatch *model.CardPatch, cardID string, userID string, disableNotify bool) (*model.Card, error) {
	blockPatch, err := model.CardPatch2BlockPatch(cardPatch)
	if err != nil {
		return nil, err
	}

	newBlock, err := a.PatchBlockAndNotify(cardID, blockPatch, userID, disableNotify)
	if err != nil {
		return nil, fmt.Errorf("cannot patch card %s: %w", cardID, err)
	}

View on GitHub (pinned to a84bbb65e3)

Solutions

  1. Unwrap the error to identify which block/field failed conversion; fix or remove the malformed block row.
  2. Skip-and-log malformed blocks instead of failing the whole listing (requires a source change to continue the loop on error).
  3. Verify no non-card block types are being returned by the store query filtering.
  4. Run a data-integrity check/migration if boards were imported from an older version.

Example fix

// before
if card, err := model.Block2Card(b); err != nil {
    return nil, fmt.Errorf("Block2Card fail: %w", err)
} else {
    cards = append(cards, card)
}
// after
card, err := model.Block2Card(b)
if err != nil {
    a.logger.Warn("skipping invalid card block", mlog.String("blockID", b.ID), mlog.Err(err))
    continue
}
cards = append(cards, card)
Defensive patterns

Strategy: try-catch

Validate before calling

// ensure blocks are card-typed before conversion
for _, blk := range blocks {
    if blk.Type != model.TypeCard {
        return fmt.Errorf("non-card block %s (%s) in card query", blk.ID, blk.Type)
    }
}

Type guard

func isCardBlock(b *model.Block) bool {
    return b != nil && b.Type == model.TypeCard
}

Try / catch

cards, err := app.GetCardsForBoard(boardID, viewID)
if err != nil {
    if strings.Contains(err.Error(), "Block2Card fail") {
        return fmt.Errorf("board %s contains a malformed card block: %w", boardID, errors.Unwrap(err))
    }
    return err
}

Prevention

When it happens

Trigger: App.GetCardsForBoard on a board whose block list contains a block that cannot be converted: wrong block type slipped into card-type filtering, corrupted/legacy block JSON missing required card fields, or schema drift after version upgrades.

Common situations: Boards imported from older Focalboard versions with legacy block payloads; manual DB edits producing invalid card blocks; a bug storing non-card blocks under card queries; corrupted fields after failed migrations.

Related errors


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