{"record":{"id":"89533483aef15dd4","repo":"GopeedLab/gopeed","slug":"panic-v","errorCode":null,"errorMessage":"panic: %v","messagePattern":"panic: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/download/engine/engine.go","lineNumber":85,"sourceCode":"\t\terr   error\n\t}\n\tch := make(chan result, 1)\n\tok := e.loop.RunOnLoop(func(runtime *goja.Runtime) {\n\t\tvar once sync.Once\n\t\tsendResult := func(res result) {\n\t\t\tonce.Do(func() {\n\t\t\t\tch <- res\n\t\t\t})\n\t\t}\n\t\tdefer func() {\n\t\t\tif r := recover(); r != nil {\n\t\t\t\tswitch v := r.(type) {\n\t\t\t\tcase error:\n\t\t\t\t\tsendResult(result{err: v})\n\t\t\t\tcase goja.Value:\n\t\t\t\t\tsendResult(result{err: exportJSError(v)})\n\t\t\t\tdefault:\n\t\t\t\t\tsendResult(result{err: fmt.Errorf(\"panic: %v\", r)})\n\t\t\t\t}\n\t\t\t}\n\t\t}()\n\t\tvalue, err := fn(runtime)\n\t\tif err != nil {\n\t\t\tsendResult(result{err: err})\n\t\t\treturn\n\t\t}\n\t\tif p, ok := value.Export().(*goja.Promise); ok {\n\t\t\tswitch p.State() {\n\t\t\tcase goja.PromiseStateFulfilled:\n\t\t\t\tsendResult(result{value: exportJSValue(p.Result())})\n\t\t\t\treturn\n\t\t\tcase goja.PromiseStateRejected:\n\t\t\t\tsendResult(result{err: exportJSError(p.Result())})\n\t\t\t\treturn\n\t\t\t}\n\t\t\tpromiseObj := value.ToObject(runtime)","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/GopeedLab/gopeed/blob/7b7327ffb30816273a74b142cccc0bc10c5a4c67/pkg/download/engine/engine.go#L67-L103","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"// before (host binding registered on the runtime)\nruntime.Set(\"parseHeader\", func(call goja.FunctionCall) goja.Value {\n    panic(\"bad input\") // surfaces as 'panic: bad input'\n})\n// after\nruntime.Set(\"parseHeader\", func(call goja.FunctionCall) goja.Value {\n    v, err := parseHeader(call.Argument(0).String())\n    if err != nil {\n        panic(err) // error branch keeps the real message and stack\n    }\n    return runtime.ToValue(v)\n})","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"value, err := engine.CallFunction(fn, args...)\nif err != nil {\n    if strings.Contains(err.Error(), \"panic:\") {\n        // recovered host-side panic: capture message, disable the offending extension, keep downloading\n        log.Warn().Err(err).Msg(\"extension script panicked\")\n    }\n    return err\n}","preventionTips":["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"],"tags":["goja","javascript","panic","extension","engine"],"backgroundTag":null,"analyzedSha":"7b7327ffb30816273a74b142cccc0bc10c5a4c67","analyzedAt":"2026-08-16T02:51:03.250Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}