{"record":{"id":"70caa247dc0312e5","repo":"GopeedLab/gopeed","slug":"panic-v-70caa2","errorCode":null,"errorMessage":"panic: %v","messagePattern":"panic: (.+?)","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"pkg/download/engine/inject/stream/module.go","lineNumber":490,"sourceCode":"\t\terr   error\n\t}\n\tch := make(chan result, 1)\n\tok := loop.RunOnLoop(func(runtime *goja.Runtime) {\n\t\tsend := func(value goja.Value, err error) {\n\t\t\tselect {\n\t\t\tcase ch <- result{value: value, err: err}:\n\t\t\tdefault:\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\tsend(nil, v)\n\t\t\t\tcase goja.Value:\n\t\t\t\t\tsend(nil, exportJSError(v))\n\t\t\t\tdefault:\n\t\t\t\t\tsend(nil, 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\tsend(nil, err)\n\t\t\treturn\n\t\t}\n\t\tif promise, ok := value.Export().(*goja.Promise); ok {\n\t\t\tswitch promise.State() {\n\t\t\tcase goja.PromiseStateFulfilled:\n\t\t\t\tsend(promise.Result(), nil)\n\t\t\t\treturn\n\t\t\tcase goja.PromiseStateRejected:\n\t\t\t\tsend(nil, exportJSError(promise.Result()))\n\t\t\t\treturn\n\t\t\tdefault:\n\t\t\t\tthenVal := value.ToObject(runtime).Get(\"then\")","sourceCodeStart":472,"sourceCodeEnd":508,"githubUrl":"https://github.com/GopeedLab/gopeed/blob/7b7327ffb30816273a74b142cccc0bc10c5a4c67/pkg/download/engine/inject/stream/module.go#L472-L508","documentation":"The stream inject module runs the extension's fetch/stream script on the goja loop under a deferred recover; this 'panic: %v' branch is the fallback when the recovered value is neither an error nor a goja.Value. It indicates native code involved in the fetch/stream binding panicked with a raw value while executing the script, and the error is delivered to the waiting caller via a non-blocking send.","triggerScenarios":"An extension's fetch handler reaching an injected native binding (request builder, stream opener) that panics with a non-error value; scripts that put the goja runtime into an invalid state (deep recursion, prototype tampering) triggering an internal invariant panic.","commonSituations":"Extension calling fetch()/open() helpers with malformed option objects after a host update; long-running stream handlers racing a shutdown; a binding version mismatch between host and extension.","solutions":["Simplify the fetch script to isolate which call panics, then fix the arguments passed to the injected helper","Update/reinstall the extension so its expected binding signatures match the host version","If you own the binding, convert string panics into returned errors so callers get the structured branch instead"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"// inside the fetch/stream handler\ntry {\n  const res = await runFetchScript(script);\n} catch (e) {\n  if (String(e).includes(\"panic:\")) {\n    // native binding panicked while running the script: report script id, do not retry blindly\n    return { err: `fetch script panicked: ${e}` };\n  }\n  throw e;\n}","preventionTips":["Keep fetch scripts small and argument-checked before calling injected helpers","Pin extension versions known to match the host bindings","Treat panic-prefixed errors as non-retryable bugs, not transient failures"],"tags":["goja","javascript","panic","fetch","stream","extension"],"backgroundTag":null,"analyzedSha":"7b7327ffb30816273a74b142cccc0bc10c5a4c67","analyzedAt":"2026-08-16T02:51:03.250Z","schemaVersion":2},"datasetVersion":"2026-08-16T03:17:38.424Z"}