wavetermdev/waveterm · error

tab not found: %q

Error message

tab not found: %q

What it means

createBlockObj wraps all block creation in a write transaction. Before inserting the new Block it loads the parent Tab by tabId; if no Tab row exists for that id it aborts the transaction with "tab not found". The library throws this to enforce referential integrity — a Block must always hang off an existing Tab via its ParentORef.

Source

Thrown at pkg/wcore/block.go:137

	defer cancelFn()
	telemetry.UpdateActivity(tctx, wshrpc.ActivityUpdate{
		Renderers: map[string]int{blockView: 1},
	})
	telemetry.RecordTEvent(tctx, &telemetrydata.TEvent{
		Event: "action:createblock",
		Props: telemetrydata.TEventProps{
			BlockView:       blockView,
			BlockController: blockController,
			BlockSubBlock:   subBlock,
		},
	})
}

func createBlockObj(ctx context.Context, tabId string, blockDef *waveobj.BlockDef, rtOpts *waveobj.RuntimeOpts) (*waveobj.Block, error) {
	return wstore.WithTxRtn(ctx, func(tx *wstore.TxWrap) (*waveobj.Block, error) {
		tab, _ := wstore.DBGet[*waveobj.Tab](tx.Context(), tabId)
		if tab == nil {
			return nil, fmt.Errorf("tab not found: %q", tabId)
		}
		blockId := uuid.NewString()
		blockData := &waveobj.Block{
			OID:         blockId,
			ParentORef:  waveobj.MakeORef(waveobj.OType_Tab, tabId).String(),
			RuntimeOpts: rtOpts,
			Meta:        blockDef.Meta,
		}
		wstore.DBInsert(tx.Context(), blockData)
		tab.BlockIds = append(tab.BlockIds, blockId)
		wstore.DBUpdate(tx.Context(), tab)
		return blockData, nil
	})
}

// Must delete all blocks individually first.
// Also deletes LayoutState.
// recursive: if true, will recursively close parent tab, window, workspace, if they are empty.

View on GitHub (pinned to a4447c1563)

Solutions

  1. Verify the tab id exists before creating the block (wstore.DBGet[*waveobj.Tab] or an RPC tab lookup).
  2. Refresh the tab list and use the current active tab id instead of a cached one.
  3. Check that the tab was not deleted concurrently (e.g. by DeleteBlock with recursive=true removing empty tabs).
  4. Confirm you are passing the tab OID, not a workspace/window/layout id.

Example fix

// before
block, _ := wclient.CreateBlock(ctx, staleTabId, blockDef, opts) // panics/errors
// after
tab, _ := wstore.DBGet[*waveobj.Tab](ctx, tabId)
if tab == nil {
    tabId, _ = wclient.CreateTab(ctx, workspaceId)
}
block, err := wclient.CreateBlock(ctx, tabId, blockDef, opts)
Defensive patterns

Strategy: validation

Validate before calling

tab, _ := wstore.DBGet[*waveobj.Tab](ctx, tabId)
if tab == nil {
    return fmt.Errorf("cannot create block: tab %s does not exist", tabId)
}

Type guard

func tabExists(ctx context.Context, tabId string) bool {
    tab, err := wstore.DBGet[*waveobj.Tab](ctx, tabId)
    return err == nil && tab != nil
}

Prevention

When it happens

Trigger: Calling CreateBlock (or any path that reaches createBlockObj) with a tabId that does not exist in the wstore DB — e.g. a stale/deleted tab id, a mistyped id, or a tab id from a different workspace.

Common situations: Frontend holding a cached tab id after the tab was closed or the tab was deleted by a recursive DeleteBlock; passing a workspace or window id where a tab id is expected; racing tab deletion with block creation.

Related errors


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