{"record":{"id":"a7d306a47867cd67","repo":"shareAI-lab/learn-claude-code","slug":"workflow-args-must-be-an-object","errorCode":null,"errorMessage":"workflow args must be an object","messagePattern":"workflow args must be an object","errorType":"validation","errorClass":"WorkflowInputError","httpStatus":null,"severity":"error","filePath":"s16_workflow_runtime/code.py","lineNumber":751,"sourceCode":"    return {\n        \"taskId\": task.task_id,\n        \"taskType\": \"local_workflow\",\n        \"runId\": task.run_id,\n        \"workflowName\": task.meta[\"name\"],\n        \"status\": task.status,\n        \"usage\": dict(task.usage),\n        \"progress\": list(task.progress),\n    }\n\n\nasync def run_workflow(name, args=None, resume_from_run_id=None):\n    \"\"\"Model-facing adapter: resolve trusted code from the host registry.\"\"\"\n    if not isinstance(name, str):\n        raise WorkflowInputError(\"workflow name must be a string\")\n    if name not in WORKFLOWS:\n        raise WorkflowInputError(f\"unknown workflow '{name}'\")\n    if args is not None and not isinstance(args, dict):\n        raise WorkflowInputError(\"workflow args must be an object\")\n    meta, script_fn = WORKFLOWS[name]\n    out = await WorkflowTool().call(\n        meta,\n        script_fn,\n        args=args,\n        resume_from_run_id=resume_from_run_id,\n    )\n    return {\n        \"launched\": out[\"launched\"],\n        \"result\": out[\"result\"],\n        \"task\": serialize_task(out[\"task\"]),\n    }\n\n\nWORKFLOW_HANDLERS = {\"Workflow\": run_workflow}\nINHERITS_TOOLS_FROM = \"s15\"\n\n","sourceCodeStart":733,"sourceCodeEnd":769,"githubUrl":"https://github.com/shareAI-lab/learn-claude-code/blob/985456f4adea6f4df8fbad4112245dbd97444eae/s16_workflow_runtime/code.py#L733-L769","documentation":"run_workflow requires args to be either None or a JSON-style object; any other type (list, string, number) raises WorkflowInputError('workflow args must be an object'). Individual workflows then validate their own fields (see error 130); this check only enforces the outer shape the runtime and snapshot code depend on (args are stored/read as dicts and compared for resume).","triggerScenarios":"run_workflow('review', args=[\"diff text\"]) or args=\"diff text\" — passing a payload whose top level is not a dict.","commonSituations":"Models emitting a bare string or array for the args parameter; tool schemas not marking args as type:object; callers forwarding an unserialized struct.","solutions":["Wrap the payload in an object: args={'changes': '...'} instead of a bare list/string.","Pass args=None when there are no arguments (the workflow applies its defaults).","Enforce type:object for args in the tool-call schema the model sees."],"exampleFix":"# before\nawait run_workflow(\"review\", args=[diff])\n\n# after\nawait run_workflow(\"review\", args={\"changes\": diff})","handlingStrategy":"type-guard","validationCode":"if args is not None and not isinstance(args, dict):\n    raise TypeError(\"args must be an object or None\")\nawait run_workflow(name, args)","typeGuard":"def is_valid_args(args) -> bool:\n    return args is None or (isinstance(args, dict) and all(isinstance(k, str) for k in args))","tryCatchPattern":"try:\n    await run_workflow(name, args=args)\nexcept WorkflowInputError as e:\n    if \"args must be an object\" in str(e):\n        return await run_workflow(name, args={\"value\": args})\n    raise","preventionTips":["Always build args as a dict literal with named keys.","In the tool schema, mark args as type:object with named properties.","Pass None rather than an empty string/list when there are no arguments."],"tags":["workflow","args","type-validation","tool-input","api"],"backgroundTag":null,"analyzedSha":"985456f4adea6f4df8fbad4112245dbd97444eae","analyzedAt":"2026-08-14T22:02:26.028Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}