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
- Unwrap the error to identify which block/field failed conversion; fix or remove the malformed block row.
- Skip-and-log malformed blocks instead of failing the whole listing (requires a source change to continue the loop on error).
- Verify no non-card block types are being returned by the store query filtering.
- 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
- Keep block payloads schema-valid across version upgrades; run migrations on upgrade.
- Never hand-edit block rows in the database.
- Filter strictly on card block type before conversion.
- On import, validate legacy blocks and repair or drop non-conforming ones.
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
- cannot create card: %w
- cannot get block %s: %w
- cannot get board %s: %w
- could not get history for board: %w
- could not get blocks history descendants for board: %w
AI-assisted analysis of mattermost-community/focalboard@a84bbb65e3 (2026-08-30).
Data as JSON: /api/errors/b46d48e7d16c223b.
Report an issue: GitHub.