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
- Reproduce with the extension's debug/dev mode to get the script stack, then fix the offending script input or binding usage
- If you maintain the host/injected binding, make it return an error instead of panicking with a non-error value
- 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
- Never register a Go binding that panics with a non-error value; panic(err) keeps the stack
- Validate goja.FunctionCall arguments in host bindings before use
- Run extensions in dev mode first to surface panics with full stacks
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
- panic: %v
- promise.then is not callable
- promise.then is not callable
- panic: %v
- blob runtime is not available
AI-assisted analysis of GopeedLab/gopeed@7b7327ffb3 (2026-08-16).
Data as JSON: /api/errors/89533483aef15dd4.
Report an issue: GitHub.