wavetermdev/waveterm · warning

block not found: %q

Error message

block not found: %q

What it means

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.

Source

Thrown at pkg/wcore/block.go:205

		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 {
			return -1, fmt.Errorf("block has subblocks, must delete subblocks first")
		}
		parentORef := waveobj.ParseORefNoErr(block.ParentORef)
		parentBlockCount := -1
		if parentORef != nil {
			if parentORef.OType == waveobj.OType_Tab {
				tab, _ := wstore.DBGet[*waveobj.Tab](tx.Context(), parentORef.OID)
				if tab != nil {
					tab.BlockIds = utilfn.RemoveElemFromSlice(tab.BlockIds, blockId)
					wstore.DBUpdate(tx.Context(), tab)
					parentBlockCount = len(tab.BlockIds)
				}
			} else if parentORef.OType == waveobj.OType_Block {
				parentBlock, _ := wstore.DBGet[*waveobj.Block](tx.Context(), parentORef.OID)
				if parentBlock != nil {
					parentBlock.SubBlockIds = utilfn.RemoveElemFromSlice(parentBlock.SubBlockIds, blockId)

View on GitHub (pinned to a4447c1563)

Solutions

  1. Treat it as success/idempotent — the block is already gone; ignore or log it.
  2. Deduplicate delete requests client-side (skip if the block is no longer in state).
  3. Retry-safe: catch this message and return nil from the calling command.
  4. Verify the block id being passed is correct and current (re-fetch block list first).

Example fix

// before
if err := wclient.DeleteBlock(ctx, blockId, false); err != nil {
    return err // fails on double-delete
}
// after
if err := wclient.DeleteBlock(ctx, blockId, false); err != nil && !strings.Contains(err.Error(), "block not found") {
    return err
}
return nil // already deleted: OK
Defensive patterns

Strategy: type-guard

Validate before calling

block, _ := wstore.DBGet[*waveobj.Block](ctx, blockId)
if block == nil {
    return nil // nothing to delete; skip the call entirely
}
wcore.DeleteBlock(ctx, blockId, false)

Type guard

func blockExists(ctx context.Context, blockId string) bool {
    b, err := wstore.DBGet[*waveobj.Block](ctx, blockId)
    return err == nil && b != nil
}

Try / catch

if err := wcore.DeleteBlock(ctx, blockId, false); err != nil {
    if strings.Contains(err.Error(), "block not found") {
        return nil // idempotent success
    }
    return err
}

Prevention

When it happens

Trigger: 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.

Common situations: Double-click delete causing two rapid DeleteBlock calls; multiple windows deleting the same block; replayed RPC commands after reconnect.

Related errors


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