AUTOMATIC1111/stable-diffusion-webui · error · HTTPException

Cannot have a selectable script in the always on scripts par

Error message

Cannot have a selectable script in the always on scripts params

What it means

HTTP 422 raised in initscript_args when a script resolved from request.alwayson_scripts has alwayson == False, i.e. it is a selectable script (appears in the Scripts dropdown) rather than an always-on one. The API enforces the split: selectable scripts go through script_name/script_args, always-on ones through alwayson_scripts, and mixing them is rejected.

Source

Thrown at modules/api/api.py:355

        if input_script_args is not None:
            for index, value in input_script_args.items():
                script_args[index] = value

        # position 0 in script_arg is the idx+1 of the selectable script that is going to be run when using scripts.scripts_*2img.run()
        if selectable_scripts:
            script_args[selectable_scripts.args_from:selectable_scripts.args_to] = request.script_args
            script_args[0] = selectable_idx + 1

        # Now check for always on scripts
        if request.alwayson_scripts:
            for alwayson_script_name in request.alwayson_scripts.keys():
                alwayson_script = self.get_script(alwayson_script_name, script_runner)
                if alwayson_script is None:
                    raise HTTPException(status_code=422, detail=f"always on script {alwayson_script_name} not found")
                # Selectable script in always on script param check
                if alwayson_script.alwayson is False:
                    raise HTTPException(status_code=422, detail="Cannot have a selectable script in the always on scripts params")
                # always on script with no arg should always run so you don't really need to add them to the requests
                if "args" in request.alwayson_scripts[alwayson_script_name]:
                    # min between arg length in scriptrunner and arg length in the request
                    for idx in range(0, min((alwayson_script.args_to - alwayson_script.args_from), len(request.alwayson_scripts[alwayson_script_name]["args"]))):
                        script_args[alwayson_script.args_from + idx] = request.alwayson_scripts[alwayson_script_name]["args"][idx]
        return script_args

    def apply_infotext(self, request, tabname, *, script_runner=None, mentioned_script_args=None):
        """Processes `infotext` field from the `request`, and sets other fields of the `request` according to what's in infotext.

        If request already has a field set, and that field is encountered in infotext too, the value from infotext is ignored.

        Additionally, fills `mentioned_script_args` dict with index: value pairs for script arguments read from infotext.
        """

        if not request.infotext:
            return {}

View on GitHub (pinned to 82a973c043)

Solutions

  1. Pass selectable scripts via the top-level script_name + script_args fields instead of alwayson_scripts
  2. If you own the script, set self.alwayson = True (and a name) in its Script class so it qualifies as always-on
  3. Pick a genuinely always-on script (e.g. controlnet-style extension scripts) for the alwayson_scripts map

Example fix

# before
json={'alwayson_scripts':{'X/Y/Z plot':{'args':[]}}}

# after
json={'script_name':'X/Y/Z plot','script_args':[...]}

# extension authors:
# class Script(scripts.Script):
#     def __init__(self):
#         self.alwayson = True
Defensive patterns

Strategy: validation

Validate before calling

selectable = set(scripts_api['txt2img']) | set(scripts_api['img2img'])
for name in payload.get('alwayson_scripts', {}):
    if name in selectable:
        payload['script_name'] = name
        payload['script_args'] = payload['alwayson_scripts'].pop(name).get('args', [])

Type guard

def is_selectable_script(name: str, selectable: set[str]) -> bool:
    return name in selectable

Try / catch

if resp.status_code == 422 and 'selectable script in the always on' in resp.json()['detail']:
    move_selectable_scripts_out_of_alwayson(payload)
    resp = requests.post(url, json=payload, auth=auth)

Prevention

When it happens

Trigger: POST /sdapi/v1/txt2img with {"alwayson_scripts": {"X/Y/Z plot": {...}}} (or any other dropdown script like Prompt matrix); the script exists and is found, but its alwayson flag is False because it was registered as selectable.

Common situations: Clients migrating from script_name usage trying to force-run dropdown scripts always-on; authors of custom extension scripts who forgot to set self.alwayson = True in the script class; script category changes between versions.

Related errors


AI-assisted analysis of AUTOMATIC1111/stable-diffusion-webui@82a973c043 (2026-08-14). Data as JSON: /api/errors/961f1f31dacd067f. Report an issue: GitHub.