projectdiscovery/nuclei · warning

invalid workflow with no templates or tags

Error message

invalid workflow with no templates or tags

What it means

parseWorkflow (pkg/templates/workflows.go:29) requires every node of a `workflows:` definition to carry either a `template:` path or a non-empty `tags:` list; with both empty the node cannot resolve to any template and the error is returned. Subtemplates are parsed recursively by the same function, so nested entries must satisfy the same rule. Callers only log a warning ('Could not parse workflow ...') and skip the file/entry, so the scan proceeds without those templates rather than aborting.

Source

Thrown at pkg/templates/workflows.go:29

	"github.com/projectdiscovery/nuclei/v3/pkg/workflows"
)

// compileWorkflow compiles the workflow for execution
func compileWorkflow(path string, preprocessor Preprocessor, options *protocols.ExecutorOptions, workflow *workflows.Workflow, loader model.WorkflowLoader) {
	for _, workflow := range workflow.Workflows {
		if err := parseWorkflow(preprocessor, workflow, options, loader); err != nil {
			gologger.Warning().Msgf("Could not parse workflow %s: %v\n", path, err)
			continue
		}
	}
}

// parseWorkflow parses and compiles all templates in a workflow recursively
func parseWorkflow(preprocessor Preprocessor, workflow *workflows.WorkflowTemplate, options *protocols.ExecutorOptions, loader model.WorkflowLoader) error {
	shouldNotValidate := false

	if workflow.Template == "" && workflow.Tags.IsEmpty() {
		return errors.New("invalid workflow with no templates or tags")
	}
	if len(workflow.Subtemplates) > 0 || len(workflow.Matchers) > 0 {
		shouldNotValidate = true
	}
	if err := parseWorkflowTemplate(workflow, preprocessor, options, loader, shouldNotValidate); err != nil {
		return err
	}
	for _, subtemplates := range workflow.Subtemplates {
		if err := parseWorkflow(preprocessor, subtemplates, options, loader); err != nil {
			gologger.Warning().Msgf("Could not parse workflow: %v\n", err)
			continue
		}
	}
	for _, matcher := range workflow.Matchers {
		if len(matcher.Name.ToSlice()) > 0 {
			if err := matcher.Compile(); err != nil {
				return errors.Wrap(err, "could not compile workflow matcher")
			}

View on GitHub (pinned to 265b3a3dec)

Solutions

  1. Add either `template: path/to/template.yaml` or `tags: ['some','tags']` to the offending entry
  2. Fix indentation so every subtemplate sits under a parent that itself has template or tags
  3. Run `nuclei -validate` against the workflow to pinpoint the file and entry
  4. Confirm tags is a real YAML list, not an empty scalar

Example fix

# before
workflows:
  - subtemplates:
      - tags: rce

# after
workflows:
  - template: cves/2021/CVE-2021-xxx.yaml
    subtemplates:
      - tags: rce
Defensive patterns

Strategy: validation

Validate before calling

// before running a workflow, assert every node resolves
func workflowNodesValid(wfs []workflows.WorkflowTemplate) error {
    for _, w := range wfs {
        if w.Template == "" && w.Tags.IsEmpty() {
            return fmt.Errorf("workflow node needs template or tags")
        }
        if err := workflowNodesValid(w.Subtemplates); err != nil {
            return err
        }
    }
    return nil
}

Prevention

When it happens

Trigger: A workflow YAML whose `workflows:` entry or nested `subtemplates:` entry has neither `template:` nor `tags:`; indentation errors that detach subtemplates from their parent, leaving an effectively empty node; tags given as an empty list.

Common situations: Hand-written workflows missing the template line; copy-paste from docs that drops one line; YAML reformatting that shifts indentation of subtemplates/matchers.

Related errors


AI-assisted analysis of projectdiscovery/nuclei@265b3a3dec (2026-08-15). Data as JSON: /api/errors/3985a23a818dd25a. Report an issue: GitHub.