{"record":{"id":"878cca8324f58aa1","repo":"wavetermdev/waveterm","slug":"error-bootstrapping-layout-no-windows-exist","errorCode":null,"errorMessage":"error bootstrapping layout, no windows exist","messagePattern":"error bootstrapping layout, no windows exist","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"warning","filePath":"pkg/wcore/layout.go","lineNumber":154,"sourceCode":"\terr := QueueLayoutActionForTab(ctx, tabId, actions...)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"unable to queue layout actions for portable layout: %w\", err)\n\t}\n\n\treturn nil\n}\n\nfunc BootstrapStarterLayout(ctx context.Context) error {\n\tctx, cancelFn := context.WithTimeout(ctx, 2*time.Second)\n\tdefer cancelFn()\n\tclient, err := wstore.DBGetSingleton[*waveobj.Client](ctx)\n\tif err != nil {\n\t\tlog.Printf(\"unable to find client: %v\\n\", err)\n\t\treturn fmt.Errorf(\"unable to find client: %w\", err)\n\t}\n\n\tif len(client.WindowIds) < 1 {\n\t\treturn fmt.Errorf(\"error bootstrapping layout, no windows exist\")\n\t}\n\n\twindowId := client.WindowIds[0]\n\n\twindow, err := wstore.DBMustGet[*waveobj.Window](ctx, windowId)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting window: %w\", err)\n\t}\n\n\tworkspace, err := wstore.DBMustGet[*waveobj.Workspace](ctx, window.WorkspaceId)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"error getting workspace: %w\", err)\n\t}\n\n\ttabId := workspace.ActiveTabId\n\n\tstarterLayout := GetStarterLayout()\n\terr = ApplyPortableLayout(ctx, tabId, starterLayout, false)","sourceCodeStart":136,"sourceCodeEnd":172,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/wcore/layout.go#L136-L172","documentation":"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.","triggerScenarios":"AgreeTos -> BootstrapStarterLayout while the client singleton has zero windows: no window has ever been created, or all windows were deleted from the store.","commonSituations":"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.","solutions":["Create a window (and its workspace/tab) before calling BootstrapStarterLayout","Ensure normal app startup (which opens a window) completes before AgreeTos triggers bootstrap","If testing, seed the client with a valid WindowIds entry or skip bootstrap in headless mode"],"exampleFix":"// before\nerr := wcore.BootstrapStarterLayout(ctx) // client has no windows\n// after\nif len(client.WindowIds) == 0 {\n    // create a window first, or skip\n    return nil\n}\nerr := wcore.BootstrapStarterLayout(ctx)","handlingStrategy":"validation","validationCode":"client, err := wstore.DBGetSingleton[*waveobj.Client](ctx)\nif err != nil { return err }\nif len(client.WindowIds) < 1 {\n    // create a window or skip bootstrap\n    return nil\n}","typeGuard":"func hasWindows(c *waveobj.Client) bool { return c != nil && len(c.WindowIds) > 0 }","tryCatchPattern":"if err := wcore.BootstrapStarterLayout(ctx); err != nil {\n    if strings.Contains(err.Error(), \"no windows exist\") {\n        // skip starter layout; normal startup will create a window\n    }\n}","preventionTips":["Only call BootstrapStarterLayout after a window exists","In headless/test environments, skip bootstrap instead of calling it blindly","Recreate the client record if a reset wiped its WindowIds"],"tags":["waveterm","window","bootstrap","precondition"],"backgroundTag":"missing-window","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T15:18:49.778Z"}