invoke-ai/InvokeAI · error · ValueError

Unexpected control_input type: ${type(control_input)}

Error message

Unexpected control_input type: ${type(control_input)}

What it means

prep_control_data accepts a single ControlNetPolysliderField, a list of them, or None. Any other type (str, dict, int, etc.) falls through to this raise because the function cannot interpret how to turn it into control data for the denoising loop.

Source

Thrown at invokeai/app/invocations/denoise_latents.py:471

    @staticmethod
    def prep_control_data(
        context: InvocationContext,
        control_input: ControlField | list[ControlField] | None,
        latents_shape: List[int],
        device: torch.device,
        exit_stack: ExitStack,
        do_classifier_free_guidance: bool = True,
    ) -> list[ControlNetData] | None:
        # Normalize control_input to a list.
        control_list: list[ControlField]
        if isinstance(control_input, ControlField):
            control_list = [control_input]
        elif isinstance(control_input, list):
            control_list = control_input
        elif control_input is None:
            control_list = []
        else:
            raise ValueError(f"Unexpected control_input type: {type(control_input)}")

        if len(control_list) == 0:
            return None

        # Assuming fixed dimensional scaling of LATENT_SCALE_FACTOR.
        _, _, latent_height, latent_width = latents_shape
        control_height_resize = latent_height * LATENT_SCALE_FACTOR
        control_width_resize = latent_width * LATENT_SCALE_FACTOR

        controlnet_data: list[ControlNetData] = []
        for control_info in control_list:
            control_model = exit_stack.enter_context(context.models.load(control_info.control_model))
            assert isinstance(control_model, ControlNetModel)

            control_image_field = control_info.image
            input_image = context.images.get_pil(control_image_field.image_name)
            # self.image.image_type, self.image.image_name
            # FIXME: still need to test with different widths, heights, devices, dtypes

View on GitHub (pinned to 0b6a024f2f)

Solutions

  1. Pass a ControlNetField/ControlField instance, a list of them, or None
  2. If you have a dict, construct the field: ControlField(**dict) before calling
  3. Inspect the upstream node output type and connect the correct controlnet invocation output

Example fix

// before
prep_control_data(control_input={"control_model": "canny"}, ...)
// after
control = ControlField(control_model="canny", image=img, control_weight=1.0)
prep_control_data(control_input=control, ...)  # or [control]
Defensive patterns

Strategy: type-guard

Validate before calling

if control_input is not None and not isinstance(control_input, (ControlField, list)):
    raise TypeError(f"control_input must be ControlField, list, or None, got {type(control_input)}")

Type guard

def is_valid_control_input(x: object) -> bool:
    if x is None:
        return True
    if isinstance(x, list):
        return all(isinstance(i, ControlField) for i in x)
    return isinstance(x, ControlField)

Try / catch

try:
    ctrl = prep_control_data(control_input=ci, ...)
except ValueError as e:
    if "Unexpected control_input type" in str(e):
        ci = ControlField(**ci) if isinstance(ci, dict) else None
        ctrl = prep_control_data(control_input=ci, ...)
    else:
        raise

Prevention

When it happens

Trigger: Calling prep_control_data (via _old_invoke or invoke) with `control_input` that is neither a ControlField, a list, nor None — commonly a dict or JSON-decoded object that was never converted to the field type.

Common situations: Scripted graph assembly passing raw dicts instead of ControlField instances; plugin code mis-wiring controlnet outputs into denoise inputs; schema changes between InvokeAI versions renaming the field type.

Related errors


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