wavetermdev/waveterm · error
panic in %s: %v
Error message
panic in %s: %v
What it means
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.
Source
Thrown at tsunami/util/util.go:34
)
// PanicHandler handles panic recovery and logging.
// It can be called directly with recover() without checking for nil first.
// Example usage:
//
// defer func() {
// util.PanicHandler("operation name", recover())
// }()
func PanicHandler(debugStr string, recoverVal any) error {
if recoverVal == nil {
return nil
}
log.Printf("[panic] in %s: %v\n", debugStr, recoverVal)
debug.PrintStack()
if err, ok := recoverVal.(error); ok {
return fmt.Errorf("panic in %s: %w", debugStr, err)
}
return fmt.Errorf("panic in %s: %v", debugStr, recoverVal)
}
func GetHomeDir() string {
homeVar, err := os.UserHomeDir()
if err != nil {
return "/"
}
return homeVar
}
func ExpandHomeDir(pathStr string) (string, error) {
if pathStr != "~" && !strings.HasPrefix(pathStr, "~/") && (!strings.HasPrefix(pathStr, `~\`) || runtime.GOOS != "windows") {
return filepath.Clean(pathStr), nil
}
homeDir := GetHomeDir()
if pathStr == "~" {
return homeDir, nil
}View on GitHub (pinned to a4447c1563)
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.
Example fix
// before
panic("item not found: " + id)
// after
panic(fmt.Errorf("item not found: %s", id)) // enables %w wrapping and errors.As Defensive patterns
Strategy: try-catch
Type guard
func isStringPanic(v any) (string, bool) {
s, ok := v.(string)
return s, ok
} Try / catch
err := util.PanicHandler("handler", recover())
if err != nil && strings.Contains(err.Error(), "panic in handler: item not found") {
return ErrNotFound // map string panics to sentinels by message
} Prevention
- 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
When it happens
Trigger: Code under a PanicHandler guard calls panic("some string") or panic(someNonErrorValue); the deferred handler converts it to a fmt.Errorf using %v.
Common situations: 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.
Related errors
AI-assisted analysis of wavetermdev/waveterm@a4447c1563 (2026-09-01).
Data as JSON: /api/errors/a8aeca17b1de7bf6.
Report an issue: GitHub.