wavetermdev/waveterm · error

error deleting subblock %s: %w

Error message

error deleting subblock %s: %w

What it means

DeleteBlock recursively deletes a block's SubBlockIds before deleting the block itself. If any recursive DeleteBlock call on a subblock fails, the error is wrapped with the subblock id as "error deleting subblock %s: %w". The whole delete aborts so no orphaned subblocks are left behind.

Source

Thrown at pkg/wcore/block.go:169

}

// Must delete all blocks individually first.
// Also deletes LayoutState.
// recursive: if true, will recursively close parent tab, window, workspace, if they are empty.
// Returns new active tab id, error.
func DeleteBlock(ctx context.Context, blockId string, recursive bool) error {
	block, err := wstore.DBGet[*waveobj.Block](ctx, blockId)
	if err != nil {
		return fmt.Errorf("error getting block: %w", err)
	}
	if block == nil {
		return nil
	}
	if len(block.SubBlockIds) > 0 {
		for _, subBlockId := range block.SubBlockIds {
			err := DeleteBlock(ctx, subBlockId, recursive)
			if err != nil {
				return fmt.Errorf("error deleting subblock %s: %w", subBlockId, err)
			}
		}
	}
	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)

View on GitHub (pinned to a4447c1563)

Solutions

  1. Unwrap the chain to find the innermost cause and failing subblock id.
  2. Delete the offending subblock's own subblocks first (or call DeleteBlock again — it recurses).
  3. Retry the whole delete; the operation is safe to repeat for already-deleted ids.
  4. If a corrupted tree keeps failing, manually remove subblock ids from the parent block's SubBlockIds via the store.

Example fix

// before
if err := wclient.DeleteBlock(ctx, blockId, false); err != nil {
    // parent still has subblocks -> "block has subblocks" bubbled up
}
// after
if err := wclient.DeleteBlock(ctx, blockId, true); err != nil { // recursive=true clears subblocks first
    log.Printf("subblock delete failed: %v", err)
}
Defensive patterns

Strategy: retry

Validate before calling

block, _ := wstore.DBGet[*waveobj.Block](ctx, blockId)
if block != nil && len(block.SubBlockIds) > 0 {
    recursive = true
}

Try / catch

err := wcore.DeleteBlock(ctx, subBlockId, true)
for attempts := 0; err != nil && attempts < 3; attempts++ {
    time.Sleep(50 * time.Millisecond)
    err = wcore.DeleteBlock(ctx, subBlockId, true)
}

Prevention

When it happens

Trigger: Any failure that DeleteBlock can return, triggered while deleting a child subblock: DB read failure, deleteBlockObj failure (e.g. the subblock itself still has subblocks), or recursive tab cleanup failure.

Common situations: Deeply nested block trees where an inner delete fails; a subblock id referencing a block that still has its own subblocks due to a prior partial/interrupted delete.

Related errors


AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01). Data as JSON: /api/errors/0748460711d12a2e. Report an issue: GitHub.