{"record":{"id":"8fbe6ec378625030","repo":"RustPython/RustPython","slug":"cmd-must-be-a-string","errorCode":null,"errorMessage":"cmd must be a string","messagePattern":"cmd must be a string","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"Lib/asyncio/base_events.py","lineNumber":1754,"sourceCode":"        if stdout is not None and stderr == subprocess.STDOUT:\n            info.append(f'stdout=stderr={_format_pipe(stdout)}')\n        else:\n            if stdout is not None:\n                info.append(f'stdout={_format_pipe(stdout)}')\n            if stderr is not None:\n                info.append(f'stderr={_format_pipe(stderr)}')\n        logger.debug(' '.join(info))\n\n    async def subprocess_shell(self, protocol_factory, cmd, *,\n                               stdin=subprocess.PIPE,\n                               stdout=subprocess.PIPE,\n                               stderr=subprocess.PIPE,\n                               universal_newlines=False,\n                               shell=True, bufsize=0,\n                               encoding=None, errors=None, text=None,\n                               **kwargs):\n        if not isinstance(cmd, (bytes, str)):\n            raise ValueError(\"cmd must be a string\")\n        if universal_newlines:\n            raise ValueError(\"universal_newlines must be False\")\n        if not shell:\n            raise ValueError(\"shell must be True\")\n        if bufsize != 0:\n            raise ValueError(\"bufsize must be 0\")\n        if text:\n            raise ValueError(\"text must be False\")\n        if encoding is not None:\n            raise ValueError(\"encoding must be None\")\n        if errors is not None:\n            raise ValueError(\"errors must be None\")\n\n        protocol = protocol_factory()\n        debug_log = None\n        if self._debug:\n            # don't log parameters: they may contain sensitive information\n            # (password) and may be too long","sourceCodeStart":1736,"sourceCodeEnd":1772,"githubUrl":"https://github.com/RustPython/RustPython/blob/aaeab4f754b4f40efc0c8ab39cf7c4a3c35a8cfd/Lib/asyncio/base_events.py#L1736-L1772","documentation":"loop.subprocess_shell() only accepts the command as a single str or bytes value, because it is executed via '/bin/sh -c'. Anything else (typically a list) raises ValueError('cmd must be a string') before any process is spawned. Argument lists belong to loop.subprocess_exec(), which takes the executable and argv without a shell — mirroring the stdlib subprocess shell=True convention.","triggerScenarios":"loop.subprocess_shell(make_protocol, ['ls', '-l']); forwarding an argv list built for subprocess.run() into the shell API; passing a tuple or other non-str/bytes command object.","commonSituations":"Porting subprocess.run(cmd_list, shell=True) code to asyncio; generic process wrappers that must serve both shell and exec modes and default to shell; commands assembled at runtime from lists.","solutions":["Switch to loop.subprocess_exec(protocol_factory, *cmd_list) for list-style commands","Join into one shell string with shlex.join(cmd) and keep subprocess_shell (only if a shell is genuinely required)","Validate the cmd type at the wrapper boundary and select shell vs exec mode accordingly"],"exampleFix":"# before\nproc = await loop.subprocess_shell(proto, ['ffmpeg', '-i', in_path])\n\n# after\nproc = await loop.subprocess_exec(proto, 'ffmpeg', '-i', in_path)","handlingStrategy":"type-guard","validationCode":"def spawn(loop, proto_factory, cmd):\n    if isinstance(cmd, (list, tuple)):\n        return loop.subprocess_exec(proto_factory, *cmd)\n    return loop.subprocess_shell(proto_factory, cmd)","typeGuard":"def is_shell_command(cmd) -> bool:\n    return isinstance(cmd, (str, bytes))","tryCatchPattern":null,"preventionTips":["Reserve list argv for subprocess_exec and single strings for subprocess_shell","Prefer subprocess_exec for structured commands — it avoids shell quoting bugs entirely","Validate the command type once at the API boundary of your process wrapper"],"tags":["asyncio","subprocess","shell","type-error","argument-validation"],"backgroundTag":"invalid-command-type","analyzedSha":"aaeab4f754b4f40efc0c8ab39cf7c4a3c35a8cfd","analyzedAt":"2026-08-17T00:37:52.100Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}