shareAI-lab/learn-claude-code · error · WorkflowInputError

workflow name must be a string

Error message

workflow name must be a string

What it means

run_workflow is the model-facing adapter: it refuses non-string workflow names with WorkflowInputError('workflow name must be a string') before touching the trusted WORKFLOWS registry. Type-checking happens before the registry lookup, so an int/None/list name never reaches the name-matching code.

Source

Thrown at s16_workflow_runtime/code.py:747

}


def serialize_task(task):
    return {
        "taskId": task.task_id,
        "taskType": "local_workflow",
        "runId": task.run_id,
        "workflowName": task.meta["name"],
        "status": task.status,
        "usage": dict(task.usage),
        "progress": list(task.progress),
    }


async def run_workflow(name, args=None, resume_from_run_id=None):
    """Model-facing adapter: resolve trusted code from the host registry."""
    if not isinstance(name, str):
        raise WorkflowInputError("workflow name must be a string")
    if name not in WORKFLOWS:
        raise WorkflowInputError(f"unknown workflow '{name}'")
    if args is not None and not isinstance(args, dict):
        raise WorkflowInputError("workflow args must be an object")
    meta, script_fn = WORKFLOWS[name]
    out = await WorkflowTool().call(
        meta,
        script_fn,
        args=args,
        resume_from_run_id=resume_from_run_id,
    )
    return {
        "launched": out["launched"],
        "result": out["result"],
        "task": serialize_task(out["task"]),
    }

View on GitHub (pinned to 985456f4ad)

Solutions

  1. Pass the workflow name as a plain string literal matching a WORKFLOWS key.
  2. Tighten the tool's input schema (type: string) so the model cannot emit other types.
  3. Guard in the caller: isinstance(name, str) before invoking.

Example fix

# before
await run_workflow(name=workflow_obj)  # an object/None

# after
await run_workflow(name="review")
Defensive patterns

Strategy: type-guard

Validate before calling

if not isinstance(name, str) or not name:
    raise TypeError(f"workflow name must be a non-empty string, got {name!r}")
await run_workflow(name, args)

Type guard

def is_workflow_name(name) -> bool:
    return isinstance(name, str) and bool(WORKFLOW_NAME_RE.match(name))

Try / catch

try:
    await run_workflow(name, args)
except WorkflowInputError as e:
    if "must be a string" in str(e):
        name = str(name)  # or reject with a clear message upstream
        return await run_workflow(name, args)
    raise

Prevention

When it happens

Trigger: run_workflow(name=None), run_workflow(name=123), or any non-str value arriving from a JSON tool call whose schema failed to enforce the name's type.

Common situations: Tool-call arguments loosely typed (model emitting null for a forgotten field); programmatic callers passing an enum/object; schema drift between client and host.

Related errors


AI-assisted analysis of shareAI-lab/learn-claude-code@985456f4ad (2026-08-14). Data as JSON: /api/errors/b3b649b1e086254c. Report an issue: GitHub.