mattermost-community/focalboard · error
store addUpdateCategoryBoard: failed to upsert user-board-ca
Error message
store addUpdateCategoryBoard: failed to upsert user-board-category userID: %s, categoryID: %s, board_count: %d, error: %w
What it means
Error returned by SQLStore.addUpdateCategoryBoard (server/services/store/sqlstore/category_boards.go:110) when the upsert into the category_boards table fails at query.Exec(). The store uses INSERT ... ON CONFLICT (user_id, board_id) DO UPDATE to reassign boards to a category; any database-level failure of that statement is wrapped with userID, categoryID, and the number of boards being moved.
Source
Thrown at server/services/store/sqlstore/category_boards.go:110
0,
false,
)
}
if s.dbType == model.MysqlDBType {
query = query.Suffix(
"ON DUPLICATE KEY UPDATE category_id = ?",
categoryID,
)
} else {
query = query.Suffix(
`ON CONFLICT (user_id, board_id)
DO UPDATE SET category_id = EXCLUDED.category_id, update_at = EXCLUDED.update_at`,
)
}
if _, err := query.Exec(); err != nil {
return fmt.Errorf(
"store addUpdateCategoryBoard: failed to upsert user-board-category userID: %s, categoryID: %s, board_count: %d, error: %w",
userID, categoryID, len(boardIDs), err,
)
}
return nil
}
func (s *SQLStore) categoryBoardsFromRows(rows *sql.Rows) ([]model.CategoryBoardMetadata, error) {
metadata := []model.CategoryBoardMetadata{}
for rows.Next() {
datum := model.CategoryBoardMetadata{}
err := rows.Scan(&datum.BoardID, &datum.Hidden)
if err != nil {
s.logger.Error("categoryBoardsFromRows row scan error", mlog.Err(err))
return nil, errView on GitHub (pinned to a84bbb65e3)
Solutions
- Read the wrapped %w error to get the exact DB cause (FK violation, deadlock, 'ON CONFLICT DO UPDATE command cannot affect row a second time').
- Deduplicate boardIDs before calling AddUpdateCategoryBoard.
- Verify the categoryID exists in the categories table before the batch upsert.
- Retry on transient errors (deadlock/serialization) with backoff; consider chunking large boardID batches.
- Run schema migrations so category_boards matches the expected columns and constraints.
Example fix
// before
boardIDs := []string{"board-1", "board-1", "board-2"}
err := store.AddUpdateCategoryBoard(userID, categoryID, boardIDs)
// after
seen := map[string]bool{}
unique := boardIDs[:0]
for _, id := range boardIDs {
if !seen[id] {
seen[id] = true
unique = append(unique, id)
}
}
err := store.AddUpdateCategoryBoard(userID, categoryID, unique) Defensive patterns
Strategy: validation
Validate before calling
func validateCategoryUpdate(categoryID string, boardIDs []string) error {
if len(boardIDs) == 0 { return errors.New("boardIDs empty") }
seen := map[string]struct{}{}
for _, id := range boardIDs {
if _, dup := seen[id]; dup { return fmt.Errorf("duplicate boardID %s", id) }
seen[id] = struct{}{}
}
return nil
} Type guard
func isCategoryUpsertError(err error) bool {
return err != nil && strings.Contains(err.Error(), "addUpdateCategoryBoard")
} Try / catch
err := store.AddUpdateCategoryBoard(userID, categoryID, dedupedBoardIDs)
if err != nil {
var pqErr *pq.Error
if errors.As(errors.Unwrap(err), &pqErr) && pqErr.Code.Name() == "unique_violation" {
// dedupe/re-fetch state and retry once
}
return err
} Prevention
- Deduplicate boardIDs client-side before batch updates
- Verify the category still exists before reassigning boards
- Refresh category state after concurrent edits from other sessions
- Retry transient DB errors (deadlocks) with exponential backoff
When it happens
Trigger: Calling AddUpdateCategoryBoard with a batch of boardIDs when the upsert fails: invalid categoryID (foreign-key violation if category was deleted), duplicate entries in boardIDs conflicting on (user_id, board_id) within the same statement, oversized batch, schema mismatch on category_boards, or a DB error (deadlock, connection loss) during Exec.
Common situations: Client sends a category update after the category was deleted in another session; batch contains the same board twice (ON CONFLICT DO UPDATE ... cannot affect row a second time in some DBs); migrations left category_boards schema stale; database under heavy write load causing deadlocks.
Related errors
- cannot save member %s while inserting board %s: %w
- cannot upsert notification hint: %w
- insertBoard error occurred while fetching existing board %s:
- insertBoard error occurred while updating existing board %s:
- cannot get migration state: %w
AI-assisted analysis of mattermost-community/focalboard@a84bbb65e3 (2026-08-30).
Data as JSON: /api/errors/f8ce2104ec8054cf.
Report an issue: GitHub.