invoke-ai/InvokeAI · error · InvalidWorkflowInputError

call_saved_workflow input

Error message

call_saved_workflow input '{input_name}' is not exposed by the selected workflow

What it means

When applying call_saved_workflow dynamic inputs to a saved child workflow, an input name was supplied that the workflow does not expose as an exposed/public input. The builder only allows setting fields that the workflow author explicitly exposed, to prevent tampering with internal graph structure.

Solutions

  1. Open the saved workflow in the workflow editor and mark the target field as exposed (field value / exposed input).
  2. Fix the input key in the calling code to exactly match an exposed input name (parse_call_saved_workflow_dynamic_input format).
  3. Remove stale inputs from the batch/prompt config for fields that no longer exist in the workflow.

Example fix

// before
inputs = {"internal_hidden_field": "x"}
// after: use an exposed input name
inputs = {"call_saved_workflow:13:positive_prompt": "a cat"}
Defensive patterns

Strategy: validation

Validate before calling

from invokeai.app.services.shared.workflow_graph_builder import get_exposed_workflow_input_names
allowed = set(get_exposed_workflow_input_names(workflow))
bad = set(workflow_inputs) - allowed
if bad:
    raise ValueError(f"inputs not exposed by workflow: {sorted(bad)}")

Type guard

def inputs_are_exposed(workflow: dict, inputs: dict) -> bool:
    allowed = set(get_exposed_workflow_input_names(workflow))
    return all(name in allowed for name in inputs)

Try / catch

from invokeai.app.services.shared.workflow_graph_builder import InvalidWorkflowInputError
try:
    results = build_child_workflow_session_results(workflow, workflow_inputs)
except InvalidWorkflowInputError as e:
    log.warning("dropping invalid workflow input: %s", e)
    results = build_child_workflow_session_results(workflow, {})  # fallback: run without dynamic inputs

Prevention

When it happens

Trigger: Calling apply_workflow_inputs_to_workflow (directly or via build_child_workflow_session_results / build_batch_child_workflow_session_results / apply_workflow_inputs_to_graph) with a workflow_inputs key not returned by get_exposed_workflow_input_names(workflow).

Common situations: Caller renamed or un-exposed a field in the saved workflow but batch/config still passes the old input name; typo in the input key; targeting a node's internal field that was never checked 'exposed' in the workflow editor.

Related errors


AI-assisted analysis of invoke-ai/InvokeAI@0b6a024f2f (2026-08-29). Data as JSON: /api/errors/921fa7a343cb2340. Report an issue: GitHub.

Appendix: source

Thrown at invokeai/app/services/shared/workflow_graph_builder.py:120

    for field in workflow_exposed_fields:
        if not _is_mapping(field):
            continue
        node_id = field.get("nodeId")
        field_name = field.get("fieldName")
        if isinstance(node_id, str) and isinstance(field_name, str):
            fallback_inputs.add(_build_dynamic_input_name(node_id, field_name))

    return fallback_inputs


def apply_workflow_inputs_to_workflow(workflow: MutableMapping[str, Any], workflow_inputs: Mapping[str, Any]) -> None:
    if not workflow_inputs:
        return

    allowed_inputs = get_exposed_workflow_input_names(workflow)
    for input_name, value in workflow_inputs.items():
        if input_name not in allowed_inputs:
            raise InvalidWorkflowInputError(
                f"call_saved_workflow input '{input_name}' is not exposed by the selected workflow"
            )

        node_id, field_name = parse_call_saved_workflow_dynamic_input(input_name)
        workflow_nodes = workflow.get("nodes", [])
        if not isinstance(workflow_nodes, list):
            raise InvalidWorkflowInputError(
                f"call_saved_workflow input '{input_name}' targets missing child workflow node '{node_id}'"
            )
        matching_node = next(
            (
                node
                for node in workflow_nodes
                if _is_mapping(node)
                and _is_mapping(node.get("data"))
                and node.get("id") == node_id
                and node["data"].get("id") == node_id
            ),

View on GitHub (pinned to 0b6a024f2f)