projectdiscovery/nuclei · error
output callback cannot be nil
Error message
output callback cannot be nil
What it means
FlowExecutor.Execute requires scan.ScanContext.OnResult to be set — every matched/misaligned event the flow produces is delivered through this callback. Before registering JS builtins, Execute checks ctx.OnResult == nil and returns 'output callback cannot be nil'. The CLI runner always wires OnResult, so this error is essentially an SDK/library misuse guard.
Source
Thrown at pkg/tmplexec/flow/flow_executor.go:259
for proto := range f.protoFunctions {
_ = runtime.GlobalObject().Delete(proto)
}
runtime.RemoveContextValue("executionId")
}()
// TODO(dwisiswant0): remove this once we get the RCA.
defer func() {
if ci.IsCI() {
return
}
if r := recover(); r != nil {
f.ctx.LogError(fmt.Errorf("panic occurred while executing flow: %v", r))
}
}()
if ctx.OnResult == nil {
return fmt.Errorf("output callback cannot be nil")
}
// before running register set of builtins
if err := runtime.Set("set", func(call goja.FunctionCall) goja.Value {
varName := call.Argument(0).Export()
varValue := call.Argument(1).Export()
f.options.GetTemplateCtx(f.ctx.Input.MetaInput).Set(types.ToString(varName), varValue)
return goja.Null()
}); err != nil {
return err
}
// also register functions that allow executing protocols from js
for proto, fn := range f.protoFunctions {
if err := runtime.Set(proto, fn); err != nil {
return err
}
}
// register template object
tmplObj := f.options.GetTemplateCtx(f.ctx.Input.MetaInput).GetAll()View on GitHub (pinned to 265b3a3dec)
Solutions
- Set ctx.OnResult before executing: scanCtx.OnResult = func(e *output.ResultEvent) { ... } (even a no-op sink satisfies the check)
- Prefer the nuclei lib's high-level APIs (nuclei.NewExecutor / ExecuteWithResults wrappers) which wire the callback for you
- Guard in shared code: if tmpl has a flow, assert OnResult != nil before dispatch
- Check the flow docs example (projectdiscovery/nuclei examples/) for the canonical ScanContext setup
Example fix
// before
scanCtx := scan.NewScanContext(input)
err := executer.Execute(scanCtx) // flow template: output callback cannot be nil
// after
scanCtx := scan.NewScanContext(input)
scanCtx.OnResult = func(r *output.ResultEvent) { results = append(results, r) }
err := executer.Execute(scanCtx) Defensive patterns
Strategy: validation
Validate before calling
// Always attach an OnResult sink before executing a flow template:
scanCtx := scan.NewScanContext(input)
scanCtx.OnResult = func(e *output.ResultEvent) {
// collect, forward to channel, or no-op — must be non-nil for flow
}
if err := executer.Execute(scanCtx); err != nil { ... } Type guard
func readyForFlow(ctx *scan.ScanContext) bool { return ctx != nil && ctx.OnResult != nil } Try / catch
if err := executer.Execute(scanCtx); err != nil {
if strings.Contains(err.Error(), "output callback cannot be nil") {
scanCtx.OnResult = func(*output.ResultEvent) {} // minimal sink, then retry once
return executer.Execute(scanCtx)
}
return err
} Prevention
- Standardize ScanContext construction in one helper that always sets OnResult
- Prefer nuclei lib high-level APIs that wire callbacks automatically
- Add a unit test executing one trivial flow template to catch missing-callback wiring early
When it happens
Trigger: SDK code that constructs a scan.ScanContext manually (e.g. scan.NewScanContext(input)) and passes it to a flow template's ExecuteWithResults/Execute without attaching OnResult. Non-flow templates tolerate this; flow templates hard-require it, so the difference appears only when a `flow:` template runs through custom harness code.
Common situations: Embedding nuclei as a library and collecting results only via the return value of ExecuteWithResults, forgetting the callback channel; migrating SDK code from plain templates to flow templates; test harnesses that stub out the output layer entirely.
Related errors
- interactsh client not initialized
- input cannot be empty file or folder expected
- cookie reuse enabled but cookie-jar is nil
- empty input provided for fuzzing
- could not create request: no request in request response
AI-assisted analysis of projectdiscovery/nuclei@265b3a3dec (2026-08-15).
Data as JSON: /api/errors/5e101349853677b0.
Report an issue: GitHub.