wavetermdev/waveterm · error

error deleting block: %w

Error message

error deleting block: %w

What it means

After subblocks are cleared, DeleteBlock calls deleteBlockObj to remove the block row and compute the parent's remaining block count. If that transaction fails, the error is wrapped as "error deleting block: %w". This is the core record-deletion step for the block itself.

Source

Thrown at pkg/wcore/block.go:175

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)
		if err != nil {
			return fmt.Errorf("error deleting tab %s: %w", parentORef.OID, err)
		}
		SendActiveTabUpdate(ctx, parentWorkspaceId, newActiveTabId)
	}
	sendBlockCloseEvent(blockId)

View on GitHub (pinned to a4447c1563)

Solutions

  1. Delete all subblocks first (pass recursive=true to DeleteBlock).
  2. Read the wrapped cause to determine whether the DB read or the parent update failed.
  3. Retry after fixing the store state; missing blocks short-circuit earlier, so retries are safe.
  4. Check that no concurrent operation re-added subblocks between checks.

Example fix

// before
wclient.DeleteBlock(ctx, parentId, false) // subblocks remain -> fails
// after
wclient.DeleteBlock(ctx, parentId, true) // subblocks deleted before the parent
Defensive patterns

Strategy: try-catch

Validate before calling

block, _ := wstore.DBGet[*waveobj.Block](ctx, blockId)
ready := block != nil && len(block.SubBlockIds) == 0

Try / catch

if err := wcore.DeleteBlock(ctx, blockId, true); err != nil {
    if strings.Contains(err.Error(), "error deleting block") {
        log.Printf("core delete failed: %v", err)
    }
    return err
}

Prevention

When it happens

Trigger: deleteBlockObj returns an error: DBGet of the block fails, the block is unexpectedly nil, the block still has subblocks, or the parent lookup/update inside the transaction fails.

Common situations: Calling DeleteBlock on a parent without recursive=true while subblocks remain (though the wrapper usually surfaces the "subblocks first" message); transaction failure from store corruption or concurrent modification.

Related errors


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