{"record":{"id":"73040486c289bd4a","repo":"mattermost-community/focalboard","slug":"cannot-fetch-board-s-for-duplicateblock-w","errorCode":null,"errorMessage":"cannot fetch board %s for DuplicateBlock: %w","messagePattern":"cannot fetch board (.+?) for DuplicateBlock: %w","errorType":"error_code","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/app/blocks.go","lineNumber":37,"sourceCode":"\n\tif blockType != \"\" && parentID != \"\" {\n\t\treturn a.store.GetBlocksWithParentAndType(boardID, parentID, blockType)\n\t}\n\n\tif blockType != \"\" {\n\t\treturn a.store.GetBlocksWithType(boardID, blockType)\n\t}\n\n\treturn a.store.GetBlocksWithParent(boardID, parentID)\n}\n\nfunc (a *App) DuplicateBlock(boardID string, blockID string, userID string, asTemplate bool) ([]*model.Block, error) {\n\tboard, err := a.GetBoard(boardID)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\tif board == nil {\n\t\treturn nil, fmt.Errorf(\"cannot fetch board %s for DuplicateBlock: %w\", boardID, err)\n\t}\n\n\tblocks, err := a.store.DuplicateBlock(boardID, blockID, userID, asTemplate)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\terr = a.CopyAndUpdateCardFiles(boardID, userID, blocks, asTemplate)\n\tif err != nil {\n\t\treturn nil, err\n\t}\n\n\ta.blockChangeNotifier.Enqueue(func() error {\n\t\tfor _, block := range blocks {\n\t\t\ta.wsAdapter.BroadcastBlockChange(board.TeamID, block)\n\t\t}\n\t\treturn nil\n\t})","sourceCodeStart":19,"sourceCodeEnd":55,"githubUrl":"https://github.com/mattermost-community/focalboard/blob/a84bbb65e32edf972856b329417096ac413518e9/server/app/blocks.go#L19-L55","documentation":"DuplicateBlock fetches the owning board via GetBoard before duplicating a block. This message is returned when GetBoard succeeds (err == nil) but the returned board is nil, meaning the boardID does not resolve to a board. Note the format verb wraps a nil error here, so the trailing ': %!w(<nil>)' may appear in the rendered message; the meaningful part is 'cannot fetch board <id> for DuplicateBlock'.","triggerScenarios":"Calling App.DuplicateBlock(boardID, blockID, userID, asTemplate) with a boardID that does not exist in the store, or a board that was deleted concurrently, or a board the store layer filters out (e.g. soft-deleted/permission-filtered), so GetBoard returns (nil, nil).","commonSituations":"Client caches a stale boardID after the board was deleted by another user; test fixtures use a fabricated board ID without creating the board; race between listing boards and duplicating a block in one.","solutions":["Verify the boardID exists via App.GetBoard(boardID) before calling DuplicateBlock.","Refresh the client's board list; the board was likely deleted or the ID is stale.","Fix the source bug: this branch wraps a nil err; it should return a sentinel like model.ErrBoardNotFound (or `errors.New` with the boardID) instead of %w on nil.","If the ID came from an API payload, validate it against the team's board list server-side."],"exampleFix":"// before\nif board == nil {\n    return nil, fmt.Errorf(\"cannot fetch board %s for DuplicateBlock: %w\", boardID, err)\n}\n// after\nif board == nil {\n    return nil, fmt.Errorf(\"cannot fetch board %s for DuplicateBlock: %w\", boardID, model.ErrBoardNotFound)\n}","handlingStrategy":"validation","validationCode":"board, err := app.GetBoard(boardID)\nif err != nil || board == nil {\n    return fmt.Errorf(\"board %s not available for duplication\", boardID)\n}\n// safe to call app.DuplicateBlock(boardID, blockID, userID, asTemplate)","typeGuard":"func boardExists(b *model.Board) bool { return b != nil && b.ID != \"\" }","tryCatchPattern":"blocks, err := app.DuplicateBlock(boardID, blockID, userID, asTemplate)\nif err != nil {\n    return fmt.Errorf(\"duplicate block failed (is board %s still valid?): %w\", boardID, err)\n}","preventionTips":["Re-fetch the board immediately before duplication instead of trusting a cached ID.","Handle board-deleted events in the client to invalidate stale boardIDs.","Watch for the '%!w(<nil>)' artifact in logs — it signals this nil-wrap branch, i.e. a missing board rather than a store failure."],"tags":["board","not-found","nil-wrap","blocks"],"backgroundTag":"board-not-found","analyzedSha":"a84bbb65e32edf972856b329417096ac413518e9","analyzedAt":"2026-08-30T09:22:20.720Z","schemaVersion":2},"datasetVersion":"2026-08-30T13:17:10.514Z"}