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

  1. Verify the cardID exists (GetCardByID) before patching and handle ErrNotFound distinctly
  2. Check the wrapped cause with errors.Is/As to distinguish not-found from permission vs store errors
  3. Verify database connectivity and that the store service is healthy
  4. 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

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


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