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

  1. 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.
  2. Match on the wrapped message string as a fallback, or wrap the guard to inspect recoverVal yourself before calling PanicHandler.
  3. 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

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.