gofiber/fiber · error

%v

Error message

%v

What it means

Returned by DefaultPanicHandler when the recovered panic value is not an error. The handler converts any non-error panic (string, int, struct) into an error via fmt.Errorf("%v", r) so fiber's error chain can carry it. The '%v' deliberately avoids a 'panic: ' prefix that breaks gotestsum reruns.

Source

Thrown at middleware/recover/recover.go:22

	"fmt"
	"os"
	"runtime/debug"

	"github.com/gofiber/fiber/v3"
)

// Must not start with "panic: ": the panic was recovered, and that exact prefix
// makes gotestsum treat the whole run as crashed and skip --rerun-fails.
func defaultStackTraceHandler(_ fiber.Ctx, e any) {
	fmt.Fprintf(os.Stderr, "recovered panic: %v\n\n%s\n", e, debug.Stack())
}

// DefaultPanicHandler returns r directly if it's an error, and creates a new one with the %v verb otherwise.
func DefaultPanicHandler(_ fiber.Ctx, r any) error {
	if err, ok := r.(error); ok {
		return err
	}
	return fmt.Errorf("%v", r)
}

// New creates a new middleware handler
func New(config ...Config) fiber.Handler {
	// Set default config
	cfg := configDefault(config...)

	// Return new handler
	return func(c fiber.Ctx) (err error) { //nolint:nonamedreturns // Uses recover() to overwrite the error
		// Don't execute middleware if Next returns true
		if cfg.Next != nil && cfg.Next(c) {
			return c.Next()
		}

		// Catch panics
		defer func() {
			if r := recover(); r != nil {
				if cfg.EnableStackTrace {

View on GitHub (pinned to 9a4c7e57fe)

Solutions

  1. Fix the underlying panic at its source.
  2. If you control the panicking code, panic with a proper error so the original value is preserved unchanged.
  3. Provide a custom Config.PanicHandler to render non-error panics however you need.

Example fix

// before: panicking with a string loses structure
panic("invalid state")

// after: panic with a typed error
var ErrInvalidState = errors.New("invalid state")
panic(ErrInvalidState)
Defensive patterns

Strategy: try-catch

Type guard

// isError narrows a recovered panic value to the error interface so you
// can preserve it verbatim instead of re-wrapping via %v.
func isError(r any) (error, bool) {
    e, ok := r.(error)
    return e, ok
}

Try / catch

// Install the recover middleware; DefaultPanicHandler wraps non-error
// panics into an error automatically.
app.Use(recover.New(recover.Config{
    EnableStackTrace: true,
    PanicHandler: func(c fiber.Ctx, r any) error {
        if e, ok := r.(error); ok {
            return e // preserve typed errors unchanged
        }
        return fiber.NewError(fiber.StatusInternalServerError,
            fmt.Sprintf("panic: %v", r))
    },
}))

Prevention

When it happens

Trigger: A handler panics with a non-error value (panic("boom"), panic(42), panic(SomeStruct{})) and the recover middleware's default PanicHandler wraps it at recover.go:22.

Common situations: Third-party code panicking with a string; a nil-pointer dereference recovered as a runtime.Error (this branch only triggers for non-error values); legacy code using panic with primitives.

Related errors


AI-assisted analysis of gofiber/fiber@9a4c7e57fe (2026-08-04). Data as JSON: /data/errors/105df1e6ca09b1bb.json. Report an issue: GitHub.