GopeedLab/gopeed · error

panic: %v

Error message

panic: %v

What it means

Engine.runOnLoop wraps every JS execution on the goja event loop with a deferred recover so a Go panic cannot crash the downloader. This 'panic: %v' branch fires only when the recovered value is neither an error nor a goja.Value — i.e. native Go code panicked with a raw string, number, or custom type while JS was running. The message is the %v rendering of that panic value.

Source

Thrown at pkg/download/engine/engine.go:85

		err   error
	}
	ch := make(chan result, 1)
	ok := e.loop.RunOnLoop(func(runtime *goja.Runtime) {
		var once sync.Once
		sendResult := func(res result) {
			once.Do(func() {
				ch <- res
			})
		}
		defer func() {
			if r := recover(); r != nil {
				switch v := r.(type) {
				case error:
					sendResult(result{err: v})
				case goja.Value:
					sendResult(result{err: exportJSError(v)})
				default:
					sendResult(result{err: fmt.Errorf("panic: %v", r)})
				}
			}
		}()
		value, err := fn(runtime)
		if err != nil {
			sendResult(result{err: err})
			return
		}
		if p, ok := value.Export().(*goja.Promise); ok {
			switch p.State() {
			case goja.PromiseStateFulfilled:
				sendResult(result{value: exportJSValue(p.Result())})
				return
			case goja.PromiseStateRejected:
				sendResult(result{err: exportJSError(p.Result())})
				return
			}
			promiseObj := value.ToObject(runtime)

View on GitHub (pinned to 7b7327ffb3)

Solutions

  1. Reproduce with the extension's debug/dev mode to get the script stack, then fix the offending script input or binding usage
  2. If you maintain the host/injected binding, make it return an error instead of panicking with a non-error value
  3. Update the extension to match the current gopeed extension API

Example fix

// before (host binding registered on the runtime)
runtime.Set("parseHeader", func(call goja.FunctionCall) goja.Value {
    panic("bad input") // surfaces as 'panic: bad input'
})
// after
runtime.Set("parseHeader", func(call goja.FunctionCall) goja.Value {
    v, err := parseHeader(call.Argument(0).String())
    if err != nil {
        panic(err) // error branch keeps the real message and stack
    }
    return runtime.ToValue(v)
})
Defensive patterns

Strategy: try-catch

Try / catch

value, err := engine.CallFunction(fn, args...)
if err != nil {
    if strings.Contains(err.Error(), "panic:") {
        // recovered host-side panic: capture message, disable the offending extension, keep downloading
        log.Warn().Err(err).Msg("extension script panicked")
    }
    return err
}

Prevention

When it happens

Trigger: A native Go function registered into the goja runtime panics with a non-error value (panic("boom")) and is then reached from a running script; Engine.CallFunction / RunScript / Evaluator invocations that hit such a binding; goja-internal invariant panics surfaced as plain values.

Common situations: Extensions calling an injected host function whose Go implementation panics on unexpected input (nil map, index out of range with a custom sentinel, assert with a string); version mismatches between the host and an extension expecting a different binding signature.

Related errors


AI-assisted analysis of GopeedLab/gopeed@7b7327ffb3 (2026-08-16). Data as JSON: /api/errors/89533483aef15dd4. Report an issue: GitHub.