wavetermdev/waveterm · warning

error bootstrapping layout, no windows exist

Error message

error bootstrapping layout, no windows exist

What it means

BootstrapStarterLayout requires at least one window on the Client record because the starter layout is applied to the active tab of the first window. This error is returned directly (no wrapped cause) when client.WindowIds is empty — a sentinel condition, not a DB failure.

Source

Thrown at pkg/wcore/layout.go:154

	err := QueueLayoutActionForTab(ctx, tabId, actions...)
	if err != nil {
		return fmt.Errorf("unable to queue layout actions for portable layout: %w", err)
	}

	return nil
}

func BootstrapStarterLayout(ctx context.Context) error {
	ctx, cancelFn := context.WithTimeout(ctx, 2*time.Second)
	defer cancelFn()
	client, err := wstore.DBGetSingleton[*waveobj.Client](ctx)
	if err != nil {
		log.Printf("unable to find client: %v\n", err)
		return fmt.Errorf("unable to find client: %w", err)
	}

	if len(client.WindowIds) < 1 {
		return fmt.Errorf("error bootstrapping layout, no windows exist")
	}

	windowId := client.WindowIds[0]

	window, err := wstore.DBMustGet[*waveobj.Window](ctx, windowId)
	if err != nil {
		return fmt.Errorf("error getting window: %w", err)
	}

	workspace, err := wstore.DBMustGet[*waveobj.Workspace](ctx, window.WorkspaceId)
	if err != nil {
		return fmt.Errorf("error getting workspace: %w", err)
	}

	tabId := workspace.ActiveTabId

	starterLayout := GetStarterLayout()
	err = ApplyPortableLayout(ctx, tabId, starterLayout, false)

View on GitHub (pinned to a4447c1563)

Solutions

  1. Create a window (and its workspace/tab) before calling BootstrapStarterLayout
  2. Ensure normal app startup (which opens a window) completes before AgreeTos triggers bootstrap
  3. If testing, seed the client with a valid WindowIds entry or skip bootstrap in headless mode

Example fix

// before
err := wcore.BootstrapStarterLayout(ctx) // client has no windows
// after
if len(client.WindowIds) == 0 {
    // create a window first, or skip
    return nil
}
err := wcore.BootstrapStarterLayout(ctx)
Defensive patterns

Strategy: validation

Validate before calling

client, err := wstore.DBGetSingleton[*waveobj.Client](ctx)
if err != nil { return err }
if len(client.WindowIds) < 1 {
    // create a window or skip bootstrap
    return nil
}

Type guard

func hasWindows(c *waveobj.Client) bool { return c != nil && len(c.WindowIds) > 0 }

Try / catch

if err := wcore.BootstrapStarterLayout(ctx); err != nil {
    if strings.Contains(err.Error(), "no windows exist") {
        // skip starter layout; normal startup will create a window
    }
}

Prevention

When it happens

Trigger: AgreeTos -> BootstrapStarterLayout while the client singleton has zero windows: no window has ever been created, or all windows were deleted from the store.

Common situations: Calling bootstrap too early in startup before the first window is created; a wiped/reset wave store where windows were removed but the client record persists; headless/CI environments where no window was opened.

Related errors


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