{"record":{"id":"f8cb9637577763f9","repo":"CoplayDev/unity-mcp","slug":"commands-must-be-a-non-empty-list-of-command-spe","errorCode":null,"errorMessage":"'commands' must be a non-empty list of command specifications","messagePattern":"'commands' must be a non-empty list of command specifications","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Server/src/services/tools/batch_execute.py","lineNumber":91,"sourceCode":"        title=\"Batch Execute\",\n        destructiveHint=True,\n    ),\n)\nasync def batch_execute(\n    ctx: Context,\n    commands: Annotated[list[dict[str, Any]], \"List of commands with 'tool' and 'params' keys.\"],\n    parallel: Annotated[bool | None,\n                        \"Attempt to run read-only commands in parallel\"] = None,\n    fail_fast: Annotated[bool | None,\n                         \"Stop processing after the first failure\"] = None,\n    max_parallelism: Annotated[int | None,\n                               \"Hint for the maximum number of parallel workers\"] = None,\n) -> dict[str, Any]:\n    \"\"\"Proxy the batch_execute tool to the Unity Editor transporter.\"\"\"\n    unity_instance = await get_unity_instance_from_context(ctx)\n\n    if not isinstance(commands, list) or not commands:\n        raise ValueError(\n            \"'commands' must be a non-empty list of command specifications\")\n\n    max_commands = await _get_max_commands_from_editor_state(ctx)\n    if len(commands) > max_commands:\n        raise ValueError(\n            f\"batch_execute supports up to {max_commands} commands (configured in Unity); received {len(commands)}\"\n        )\n\n    normalized_commands: list[dict[str, Any]] = []\n    for index, command in enumerate(commands):\n        if not isinstance(command, dict):\n            raise ValueError(\n                f\"Command at index {index} must be an object with 'tool' and 'params' keys\")\n\n        tool_name = command.get(\"tool\")\n        params = command.get(\"params\", {})\n\n        if not tool_name or not isinstance(tool_name, str):","sourceCodeStart":73,"sourceCodeEnd":109,"githubUrl":"https://github.com/CoplayDev/unity-mcp/blob/c21bf496bca87d54e75bad048563c3adb1782081/Server/src/services/tools/batch_execute.py#L73-L109","documentation":"Input validation in the batch_execute MCP tool: the `commands` argument must be a non-empty Python list. The check `not isinstance(commands, list) or not commands` rejects None, dicts, tuples, strings, and empty lists. batch_execute proxies a list of {tool, params} sub-commands to Unity's batch transporter, so an empty list has nothing to execute and a non-list is a caller contract violation.","triggerScenarios":"An AI assistant or caller passes commands=null, commands={}, commands=\"tool_name\", or commands=[]; a JSON-RPC payload where 'commands' is omitted (None after default) or explicitly empty; a caller that builds the list dynamically and it ended up empty due to a filtering bug.","commonSituations":"An LLM generates an empty batch by mistake; a programmatic caller passes a single tool name as a string instead of a list of dicts; a pipeline that filters commands down to zero entries before calling batch_execute; a caller confusing batch_execute with a single-command tool.","solutions":["Ensure commands is a list with at least one {\"tool\": \"...\", \"params\": {...}} entry.","If building the list dynamically, guard against empty before calling: only call batch_execute when len(commands) > 0.","Use the correct shape: [{\"tool\": \"manage_gameobject\", \"params\": {\"action\": \"create\", \"name\": \"Cube\"}}]."],"exampleFix":"// before (caller)\nawait batch_execute(ctx, commands=[])\n// after\nawait batch_execute(ctx, commands=[{\"tool\": \"manage_gameobject\", \"params\": {\"action\": \"create\", \"name\": \"Cube\"}}])","handlingStrategy":"validation","validationCode":"# Validate commands before calling batch_execute\ndef validate_batch_commands(commands: list) -> None:\n    if not isinstance(commands, list) or not commands:\n        raise ValueError(\"commands must be a non-empty list\")\n    for i, cmd in enumerate(commands):\n        if not isinstance(cmd, dict):\n            raise ValueError(f\"Command {i} must be a dict\")\n        if not cmd.get(\"tool\") or not isinstance(cmd.get(\"tool\"), str):\n            raise ValueError(f\"Command {i} missing valid 'tool'\")\n        params = cmd.get(\"params\", {})\n        if params is None:\n            params = {}\n        if not isinstance(params, dict):\n            raise ValueError(f\"Command {i} params must be a dict\")\n\nvalidate_batch_commands(commands)","typeGuard":"def is_valid_command_list(commands: object) -> bool:\n    if not isinstance(commands, list) or len(commands) == 0:\n        return False\n    return all(\n        isinstance(c, dict)\n        and isinstance(c.get(\"tool\"), str)\n        and c.get(\"tool\", \"\") != \"\"\n        and (c.get(\"params\", {}) is None or isinstance(c.get(\"params\"), dict))\n        for c in commands\n    )","tryCatchPattern":null,"preventionTips":["Always build commands as a list of {\"tool\": str, \"params\": dict} objects.","Guard dynamically-built lists: only call batch_execute when len(commands) > 0.","Validate the full command shape before submission to catch errors early."],"tags":["validation","batch-execute","input-shape","mcp-tool"],"backgroundTag":null,"analyzedSha":"c21bf496bca87d54e75bad048563c3adb1782081","analyzedAt":"2026-08-13T17:36:56.095Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}