wavetermdev/waveterm · error
error deleting tab %s: %w
Error message
error deleting tab %s: %w
What it means
In the recursive path, after finding the workspace DeleteBlock calls DeleteTab to close the now-empty parent tab. Any error from DeleteTab is wrapped as "error deleting tab %s: %w" with the tab id. The block deletion itself already succeeded; only the tab cascade failed.
Source
Thrown at pkg/wcore/block.go:189
}
}
parentBlockCount, err := deleteBlockObj(ctx, blockId)
if err != nil {
return fmt.Errorf("error deleting block: %w", err)
}
log.Printf("DeleteBlock: parentBlockCount: %d", parentBlockCount)
parentORef := waveobj.ParseORefNoErr(block.ParentORef)
if recursive && parentORef.OType == waveobj.OType_Tab && parentBlockCount == 0 {
// if parent tab has no blocks, delete the tab
log.Printf("DeleteBlock: parent tab has no blocks, deleting tab %s", parentORef.OID)
parentWorkspaceId, err := wstore.DBFindWorkspaceForTabId(ctx, parentORef.OID)
if err != nil {
return fmt.Errorf("error finding workspace for tab to delete %s: %w", parentORef.OID, err)
}
newActiveTabId, err := DeleteTab(ctx, parentWorkspaceId, parentORef.OID, true)
if err != nil {
return fmt.Errorf("error deleting tab %s: %w", parentORef.OID, err)
}
SendActiveTabUpdate(ctx, parentWorkspaceId, newActiveTabId)
}
sendBlockCloseEvent(blockId)
return nil
}
// returns the updated block count for the parent object
func deleteBlockObj(ctx context.Context, blockId string) (int, error) {
return wstore.WithTxRtn(ctx, func(tx *wstore.TxWrap) (int, error) {
block, err := wstore.DBGet[*waveobj.Block](tx.Context(), blockId)
if err != nil {
return -1, fmt.Errorf("error getting block: %w", err)
}
if block == nil {
return -1, fmt.Errorf("block not found: %q", blockId)
}
if len(block.SubBlockIds) > 0 {View on GitHub (pinned to a4447c1563)
Solutions
- Read the wrapped DeleteTab cause for the specific failure.
- Re-check the tab's block count; if it is no longer empty, the cascade is unnecessary and can be skipped.
- Retry after resolving the store issue; the block is already deleted, only the tab remains.
- Handle the failure gracefully — sendBlockCloseEvent is skipped, so emit block-close UI updates manually.
Example fix
// before
err := wcore.DeleteBlock(ctx, blockId, true) // tab cascade failure aborts
// after
if err := wcore.DeleteBlock(ctx, blockId, true); err != nil && strings.Contains(err.Error(), "error deleting tab") {
// block was deleted; clean up the empty tab separately
wcore.DeleteTab(ctx, wsId, tabId, false)
} Defensive patterns
Strategy: try-catch
Validate before calling
tab, _ := wstore.DBGet[*waveobj.Tab](ctx, tabId) blocks, _ := wstore.DBGetBlocksForTab(ctx, tabId) cascade := tab != nil && len(blocks) == 1 // only this block remains
Try / catch
err := wcore.DeleteBlock(ctx, blockId, true)
if err != nil && strings.Contains(err.Error(), "error deleting tab") {
// block already deleted; handle leftover tab separately
log.Printf("tab cascade failed: %v", err)
return nil
} Prevention
- Treat block deletion and tab cascade as separable outcomes when handling errors.
- Avoid concurrent edits to the tab while a recursive delete runs.
- After a cascade failure, re-check whether the tab still exists before retrying.
When it happens
Trigger: DeleteTab fails during recursive block deletion of the last block in a tab — e.g. tab lookup failure, tab still has other blocks, or the tab delete transaction fails.
Common situations: Race where another client added a block to the tab between the count check and DeleteTab; store/transaction failure; invalid workspace id passed into DeleteTab.
Related errors
- error finding workspace for tab to delete %s: %w
- error getting block: %w
- error deleting subblock %s: %w
- error deleting block: %w
- unable to get layout id for given tab id %s: %w
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/7ea6060462153504.
Report an issue: GitHub.