{"record":{"id":"b674094f5aeae12a","repo":"wavetermdev/waveterm","slug":"block-not-found-q","errorCode":null,"errorMessage":"block not found: %q","messagePattern":"block not found: %q","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pkg/wcore/block.go","lineNumber":205,"sourceCode":"\t\tnewActiveTabId, err := DeleteTab(ctx, parentWorkspaceId, parentORef.OID, true)\n\t\tif err != nil {\n\t\t\treturn fmt.Errorf(\"error deleting tab %s: %w\", parentORef.OID, err)\n\t\t}\n\t\tSendActiveTabUpdate(ctx, parentWorkspaceId, newActiveTabId)\n\t}\n\tsendBlockCloseEvent(blockId)\n\treturn nil\n}\n\n// returns the updated block count for the parent object\nfunc deleteBlockObj(ctx context.Context, blockId string) (int, error) {\n\treturn wstore.WithTxRtn(ctx, func(tx *wstore.TxWrap) (int, error) {\n\t\tblock, err := wstore.DBGet[*waveobj.Block](tx.Context(), blockId)\n\t\tif err != nil {\n\t\t\treturn -1, fmt.Errorf(\"error getting block: %w\", err)\n\t\t}\n\t\tif block == nil {\n\t\t\treturn -1, fmt.Errorf(\"block not found: %q\", blockId)\n\t\t}\n\t\tif len(block.SubBlockIds) > 0 {\n\t\t\treturn -1, fmt.Errorf(\"block has subblocks, must delete subblocks first\")\n\t\t}\n\t\tparentORef := waveobj.ParseORefNoErr(block.ParentORef)\n\t\tparentBlockCount := -1\n\t\tif parentORef != nil {\n\t\t\tif parentORef.OType == waveobj.OType_Tab {\n\t\t\t\ttab, _ := wstore.DBGet[*waveobj.Tab](tx.Context(), parentORef.OID)\n\t\t\t\tif tab != nil {\n\t\t\t\t\ttab.BlockIds = utilfn.RemoveElemFromSlice(tab.BlockIds, blockId)\n\t\t\t\t\twstore.DBUpdate(tx.Context(), tab)\n\t\t\t\t\tparentBlockCount = len(tab.BlockIds)\n\t\t\t\t}\n\t\t\t} else if parentORef.OType == waveobj.OType_Block {\n\t\t\t\tparentBlock, _ := wstore.DBGet[*waveobj.Block](tx.Context(), parentORef.OID)\n\t\t\t\tif parentBlock != nil {\n\t\t\t\t\tparentBlock.SubBlockIds = utilfn.RemoveElemFromSlice(parentBlock.SubBlockIds, blockId)","sourceCodeStart":187,"sourceCodeEnd":223,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/wcore/block.go#L187-L223","documentation":"Inside deleteBlockObj, if the block row does not exist (DBGet returns nil, nil) the transaction aborts with \"block not found: %q\". Unlike error 1296 this is a missing-object condition, not a storage failure. Note DeleteBlock normally guards nil blocks first, so this fires when the block vanishes between the outer check and the transaction (a race), or when deleteBlockObj is invoked directly.","triggerScenarios":"Block deleted concurrently after DeleteBlock's initial DBGet but before deleteBlockObj's transaction reads it; calling deleteBlockObj with a nonexistent/typo'd block id; retrying a delete that already succeeded.","commonSituations":"Double-click delete causing two rapid DeleteBlock calls; multiple windows deleting the same block; replayed RPC commands after reconnect.","solutions":["Treat it as success/idempotent — the block is already gone; ignore or log it.","Deduplicate delete requests client-side (skip if the block is no longer in state).","Retry-safe: catch this message and return nil from the calling command.","Verify the block id being passed is correct and current (re-fetch block list first)."],"exampleFix":"// before\nif err := wclient.DeleteBlock(ctx, blockId, false); err != nil {\n    return err // fails on double-delete\n}\n// after\nif err := wclient.DeleteBlock(ctx, blockId, false); err != nil && !strings.Contains(err.Error(), \"block not found\") {\n    return err\n}\nreturn nil // already deleted: OK","handlingStrategy":"type-guard","validationCode":"block, _ := wstore.DBGet[*waveobj.Block](ctx, blockId)\nif block == nil {\n    return nil // nothing to delete; skip the call entirely\n}\nwcore.DeleteBlock(ctx, blockId, false)","typeGuard":"func blockExists(ctx context.Context, blockId string) bool {\n    b, err := wstore.DBGet[*waveobj.Block](ctx, blockId)\n    return err == nil && b != nil\n}","tryCatchPattern":"if err := wcore.DeleteBlock(ctx, blockId, false); err != nil {\n    if strings.Contains(err.Error(), \"block not found\") {\n        return nil // idempotent success\n    }\n    return err\n}","preventionTips":["Treat block-not-found on delete as success (idempotent deletes).","Remove the block from client state immediately when issuing the delete to avoid double-submits.","Disable/delete buttons once a delete is in flight."],"tags":["wave-terminal","block-deletion","missing-block","race-condition","idempotency"],"backgroundTag":"object-not-found","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}