{"record":{"id":"f6c065388d717693","repo":"wavetermdev/waveterm","slug":"error-closing-tab-w","errorCode":null,"errorMessage":"error closing tab: %w","messagePattern":"error closing tab: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/service/workspaceservice/workspaceservice.go","lineNumber":230,"sourceCode":"\t\tArgNames:   []string{\"ctx\", \"workspaceId\", \"tabId\", \"fromElectron\"},\n\t\tReturnDesc: \"CloseTabRtn\",\n\t}\n}\n\n// returns the new active tabid\nfunc (svc *WorkspaceService) CloseTab(ctx context.Context, workspaceId string, tabId string, fromElectron bool) (*CloseTabRtnType, waveobj.UpdatesRtnType, error) {\n\tctx = waveobj.ContextWithUpdates(ctx)\n\ttab, err := wstore.DBGet[*waveobj.Tab](ctx, tabId)\n\tif err == nil && tab != nil {\n\t\tgo func() {\n\t\t\tfor _, blockId := range tab.BlockIds {\n\t\t\t\tblockcontroller.DestroyBlockController(blockId)\n\t\t\t}\n\t\t}()\n\t}\n\tnewActiveTabId, err := wcore.DeleteTab(ctx, workspaceId, tabId, true)\n\tif err != nil {\n\t\treturn nil, nil, fmt.Errorf(\"error closing tab: %w\", err)\n\t}\n\trtn := &CloseTabRtnType{}\n\tif newActiveTabId == \"\" {\n\t\trtn.CloseWindow = true\n\t} else {\n\t\trtn.NewActiveTabId = newActiveTabId\n\t}\n\tupdates := waveobj.ContextGetUpdatesRtn(ctx)\n\tgo func() {\n\t\tdefer func() {\n\t\t\tpanichandler.PanicHandler(\"WorkspaceService:CloseTab:SendUpdateEvents\", recover())\n\t\t}()\n\t\twps.Broker.SendUpdateEvents(updates)\n\t}()\n\treturn rtn, updates, nil\n}\n","sourceCodeStart":212,"sourceCodeEnd":247,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/pkg/service/workspaceservice/workspaceservice.go#L212-L247","documentation":"CloseTab deletes the tab via wcore.DeleteTab(ctx, workspaceId, tabId, true) and computes the next active tab. If deletion fails, the error is wrapped as \"error closing tab: %w\" and CloseTabRtn is left nil. The wrapping preserves the wcore-level cause (not found, DB error, etc.).","triggerScenarios":"Calling CloseTab(workspaceId, tabId) with a tabId not present in the workspace, a nonexistent workspaceId, or a wcore.DeleteTab storage failure.","commonSituations":"User closing a tab that was already closed in another window; stale frontend state; DB write failure during delete.","solutions":["Confirm the tabId still exists in the workspace before calling CloseTab","Unwrap the error to see wcore.DeleteTab's root cause","Refresh the workspace view in the client to drop stale tab references","Check DB health/writability"],"exampleFix":"// before\nwsSvc.CloseTab(wsId, tabId) // may fail if already closed\n// after\ntab, err := wstore.DBGet[*waveobj.Tab](ctx, tabId)\nif err != nil { return nil } // tab already gone\nwsSvc.CloseTab(wsId, tabId)","handlingStrategy":"validation","validationCode":"tab, err := wstore.DBGet[*waveobj.Tab](ctx, tabId)\nif err != nil {\n    return nil // already closed; nothing to do\n}\n_, _, err = svc.CloseTab(workspaceId, tabId)","typeGuard":null,"tryCatchPattern":"_, _, err := svc.CloseTab(workspaceId, tabId)\nif err != nil {\n    if errors.Is(errors.Unwrap(err), waveobj.ErrNotFound) { return nil } // treat as success\n    return err\n}","preventionTips":["Treat not-found on close as idempotent success in callers","Debounce duplicate close requests from the UI","Keep workspace state synced across windows"],"tags":["go","workspace","tab","close"],"backgroundTag":"wrapped-error-propagation","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}