mattermost-community/focalboard · error
cannot patch card %s: %w
Error message
cannot patch card %s: %w
What it means
PatchCard wraps any failure from PatchBlockAndNotify (the underlying block patch operation) with the card ID for context, using %w so the root cause remains inspectable via errors.Is/As. If this error surfaces, the card block could not be updated in the store — common causes are the block not existing, permission failures, or a store/database error. The original error from the patch layer is preserved as the wrapped cause.
Source
Thrown at server/app/cards.go:73
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)
}
newCard, err := model.Block2Card(newBlock)
if err != nil {
return nil, err
}
return newCard, nil
}
func (a *App) GetCardByID(cardID string) (*model.Card, error) {
cardBlock, err := a.GetBlockByID(cardID)
if err != nil {
return nil, err
}
card, err := model.Block2Card(cardBlock)
if err != nil {View on GitHub (pinned to a84bbb65e3)
Solutions
- Verify the cardID exists (GetCardByID) before patching and handle ErrNotFound distinctly
- Check the wrapped cause with errors.Is/As to distinguish not-found from permission vs store errors
- Verify database connectivity and that the store service is healthy
- Confirm the acting userID has edit permission on the parent board
Example fix
// before
newCard, err := app.PatchCard(patch, cardID, userID, false)
if err != nil {
return err
}
// after
newCard, err := app.PatchCard(patch, cardID, userID, false)
if err != nil {
if errors.Is(err, model.ErrNotFound) {
return fmt.Errorf("card %s not found", cardID)
}
return fmt.Errorf("patch card %s: %w", cardID, err)
} Defensive patterns
Strategy: validation
Validate before calling
if _, err := app.GetCardByID(cardID); err != nil {
return fmt.Errorf("card %s not patchable: %w", cardID, err)
}
// also validate patch
cardPatch := &model.CardPatch{...}
if _, err := model.CardPatch2BlockPatch(cardPatch); err != nil {
return err
} Type guard
func isCardPatchNotFound(err error) bool {
return errors.Is(err, model.ErrNotFound)
} Try / catch
newCard, err := app.PatchCard(patch, cardID, userID, false)
if err != nil {
switch {
case errors.Is(err, model.ErrNotFound):
// stale ID: refresh card list
default:
return fmt.Errorf("patch card %s: %w", cardID, err)
}
} Prevention
- Fetch the card (GetCardByID) before patching to confirm it exists
- Always inspect the wrapped cause with errors.Is/As instead of matching the message string
- Keep client-side card state fresh to avoid patching deleted cards
- Check the acting user's board permissions before issuing patches
When it happens
Trigger: Calling App.PatchCard with a cardID that does not exist in the store; the underlying block store returns an error during the patch (DB down, connection refused); the userID lacks permission to modify the block; or CardPatch2BlockPatch produced an invalid patch that PatchBlockAndNotify rejects.
Common situations: Client holds a stale card ID after the card was deleted by another user; concurrent modification conflicts; database migrations or connectivity issues in self-hosted deployments; REST API consumers patching cards via /api/v1/cards/{cardID} with an invalid or deleted ID.
Related errors
- Board IDs do not match
- patch updates cards that are limited
- block fields size limit exceeded
- at least one board is required
- at least one block is required
AI-assisted analysis of mattermost-community/focalboard@a84bbb65e3 (2026-08-30).
Data as JSON: /api/errors/97fc3ffd7cb0802c.
Report an issue: GitHub.