{"record":{"id":"a8aeca17b1de7bf6","repo":"wavetermdev/waveterm","slug":"panic-in-s-v","errorCode":null,"errorMessage":"panic in %s: %v","messagePattern":"panic in (.+?): (.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"tsunami/util/util.go","lineNumber":34,"sourceCode":")\n\n// PanicHandler handles panic recovery and logging.\n// It can be called directly with recover() without checking for nil first.\n// Example usage:\n//\n//\tdefer func() {\n//\t    util.PanicHandler(\"operation name\", recover())\n//\t}()\nfunc PanicHandler(debugStr string, recoverVal any) error {\n\tif recoverVal == nil {\n\t\treturn nil\n\t}\n\tlog.Printf(\"[panic] in %s: %v\\n\", debugStr, recoverVal)\n\tdebug.PrintStack()\n\tif err, ok := recoverVal.(error); ok {\n\t\treturn fmt.Errorf(\"panic in %s: %w\", debugStr, err)\n\t}\n\treturn fmt.Errorf(\"panic in %s: %v\", debugStr, recoverVal)\n}\n\nfunc GetHomeDir() string {\n\thomeVar, err := os.UserHomeDir()\n\tif err != nil {\n\t\treturn \"/\"\n\t}\n\treturn homeVar\n}\n\nfunc ExpandHomeDir(pathStr string) (string, error) {\n\tif pathStr != \"~\" && !strings.HasPrefix(pathStr, \"~/\") && (!strings.HasPrefix(pathStr, `~\\`) || runtime.GOOS != \"windows\") {\n\t\treturn filepath.Clean(pathStr), nil\n\t}\n\thomeDir := GetHomeDir()\n\tif pathStr == \"~\" {\n\t\treturn homeDir, nil\n\t}","sourceCodeStart":16,"sourceCodeEnd":52,"githubUrl":"https://github.com/wavetermdev/waveterm/blob/a4447c1563b2df285ab89e76c82f91e1a1a49c1e/tsunami/util/util.go#L16-L52","documentation":"Non-error variant of PanicHandler: when the recovered panic value is not an error (a string, an int, a struct, etc.), it formats it with %v. Since %v loses the original value's identity, errors.Is/errors.As cannot be used on the result — only string matching.","triggerScenarios":"Code under a PanicHandler guard calls panic(\"some string\") or panic(someNonErrorValue); the deferred handler converts it to a fmt.Errorf using %v.","commonSituations":"Legacy or third-party code that panics with strings instead of errors; assertion-style panics in internal helpers now wrapped by PanicHandler at an RPC boundary.","solutions":["Search the codebase for panic(<non-error>) at the site named in debugStr and change it to return errors or panic with an error value.","Match on the wrapped message string as a fallback, or wrap the guard to inspect recoverVal yourself before calling PanicHandler.","Add tests around the panicking path so the string panic is surfaced during development."],"exampleFix":"// before\npanic(\"item not found: \" + id)\n// after\npanic(fmt.Errorf(\"item not found: %s\", id)) // enables %w wrapping and errors.As","handlingStrategy":"try-catch","validationCode":null,"typeGuard":"func isStringPanic(v any) (string, bool) {\n    s, ok := v.(string)\n    return s, ok\n}","tryCatchPattern":"err := util.PanicHandler(\"handler\", recover())\nif err != nil && strings.Contains(err.Error(), \"panic in handler: item not found\") {\n    return ErrNotFound // map string panics to sentinels by message\n}","preventionTips":["Panic only with error values (panic(fmt.Errorf(...))) so wrapping preserves identity","Replace string panics in internal code with returned errors","Grep the codebase for panic(\" to find non-error panic sites"],"tags":["panic","recovery","go"],"backgroundTag":"recovered-panic","analyzedSha":"a4447c1563b2df285ab89e76c82f91e1a1a49c1e","analyzedAt":"2026-09-01T15:26:23.972Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}