Skip to content

Optional inputs without defaults fail template resolution #47

@spinje

Description

@spinje

Summary

When a workflow input is declared with required: false but no default value, and the user doesn't provide a value at runtime, any template referencing that input fails with "Unresolved variables" error.

Expected behavior: Optional inputs should resolve to null when not provided, allowing templates to work.

Reproduction

Test case 1: Optional input without default (FAILS)

{
  "inputs": {
    "optional_name": {
      "type": "string",
      "required": false
    }
  },
  "nodes": [{
    "id": "greet",
    "type": "shell",
    "params": {
      "stdin": {"value": "${optional_name}"},
      "command": "jq ."
    }
  }],
  "edges": []
}
$ pflow workflow.json
❌ Workflow execution failed
Error: Unresolved variables in parameter 'stdin': ${optional_name}

Test case 2: Optional input WITH default (WORKS)

Same workflow but with "default": null added:

"optional_name": {
  "type": "string",
  "required": false,
  "default": null
}
$ pflow workflow.json
✓ Workflow completed
{"value": null}

Root Cause

In workflow_validator.py, the prepare_inputs() function handles optional inputs without defaults by doing nothing:

else:
    # Optional with no default key means it can be omitted entirely
    logger.debug(f"Optional input '{input_name}' not provided and has no default", ...)
    # NOTE: Nothing added to defaults dict!

This means the input key is never added to the context, so template resolution fails because variable_exists() returns False.

Secondary Issue: Misleading Error Messages

When a parameter contains multiple template variables and only some fail to resolve, the error message incorrectly lists ALL variables as unresolved:

{
  "stdin": {
    "has_value": "${provided}",
    "no_value": "${missing}"
  }
}
$ pflow workflow.json provided=hello
Error: Unresolved variables in parameter 'stdin': ${provided}, ${missing}

Available context keys:
  • provided (str): hello

The error says ${provided} is unresolved, but it's clearly in the context! This is confusing for debugging.

Impact

  1. Workaround tax: Users must add "default": null to every optional input, even when semantically unnecessary
  2. Confusing errors: Error messages suggest ALL templates failed when only some did
  3. Semantic inconsistency: Input validation says "optional inputs can be omitted entirely" but template validation requires them to exist

Proposed Fix

  1. In prepare_inputs(), add optional inputs without defaults to the context with value None:

    else:
        defaults[input_name] = None
  2. In _build_enhanced_template_error(), filter to only actually unresolved variables:

    variables = {v for v in all_variables if not TemplateResolver.variable_exists(v, context)}

Environment

  • pflow version: current main
  • Python: 3.13
  • OS: macOS

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions