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
- Verify the tab id exists before creating the block (wstore.DBGet[*waveobj.Tab] or an RPC tab lookup).
- Refresh the tab list and use the current active tab id instead of a cached one.
- Check that the tab was not deleted concurrently (e.g. by DeleteBlock with recursive=true removing empty tabs).
- 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
- Always resolve tab ids from a fresh tab-list query, never long-lived caches.
- Validate the id's OType is Tab before use (waveobj.ParseORef).
- Handle tab-deleted events to invalidate stale tab references in UI state.
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
- no WAVETERM_TABID env var set
- create block failed: %v
- no WAVETERM_TABID env var set
- running view command: %w
- no WAVETERM_TABID env var set
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/33ce1cb7a0bdea5d.
Report an issue: GitHub.