{"record":{"id":"20198b178edb0b69","repo":"crewAIInc/crewAI","slug":"action-replace-requires-paths-pattern-and","errorCode":null,"errorMessage":"action='replace' requires 'paths', 'pattern', and 'replacement'","messagePattern":"action='replace' requires 'paths', 'pattern', and 'replacement'","errorType":"validation","errorClass":"ValueError","httpStatus":null,"severity":"error","filePath":"lib/crewai-tools/src/crewai_tools/tools/daytona_sandbox_tool/daytona_file_tool.py","lineNumber":309,"sourceCode":"                if path is None or destination is None:\n                    raise ValueError(\"action='move' requires 'path' and 'destination'\")\n                sandbox.fs.move_files(path, destination)\n                return {\"status\": \"moved\", \"from\": path, \"to\": destination}\n            if action == \"find\":\n                if path is None or pattern is None:\n                    raise ValueError(\"action='find' requires 'path' and 'pattern'\")\n                return self._find(sandbox, path, pattern)\n            if action == \"search\":\n                if path is None or pattern is None:\n                    raise ValueError(\"action='search' requires 'path' and 'pattern'\")\n                return self._search(sandbox, path, pattern)\n            if action == \"chmod\":\n                if path is None:\n                    raise ValueError(\"action='chmod' requires 'path'\")\n                return self._chmod(sandbox, path, mode=mode, owner=owner, group=group)\n            if action == \"replace\":\n                if paths is None or pattern is None or replacement is None:\n                    raise ValueError(\n                        \"action='replace' requires 'paths', 'pattern', and \"\n                        \"'replacement'\"\n                    )\n                return self._replace(sandbox, paths, pattern, replacement)\n            raise ValueError(f\"Unknown action: {action}\")\n        finally:\n            self._release_sandbox(sandbox, should_delete)\n\n    def _read(self, sandbox: Any, path: str, *, binary: bool) -> dict[str, Any]:\n        data: bytes = sandbox.fs.download_file(path)\n        if binary:\n            return {\n                \"path\": path,\n                \"encoding\": \"base64\",\n                \"content\": base64.b64encode(data).decode(\"ascii\"),\n            }\n        try:\n            return {\"path\": path, \"encoding\": \"utf-8\", \"content\": data.decode(\"utf-8\")}","sourceCodeStart":291,"sourceCodeEnd":327,"githubUrl":"https://github.com/crewAIInc/crewAI/blob/754d7323beb2fd042e33444a115ea2d5a47193f0/lib/crewai-tools/src/crewai_tools/tools/daytona_sandbox_tool/daytona_file_tool.py#L291-L327","documentation":"action='replace' in DaytonaFileTool._run is a bulk find-and-replace and requires three arguments: 'paths' (a list of files), 'pattern', and 'replacement'. If any of the three is None it raises ValueError before calling self._replace. Note it takes 'paths' (plural list), unlike most other actions that take a single 'path'.","triggerScenarios":"Calling _run(action='replace', path='/a.txt', pattern='x', replacement='y') — using singular 'path' is not read, so paths stays None; omitting 'replacement'; passing pattern only.","commonSituations":"Users carrying over the singular 'path' kwarg habit from other actions; agents emitting a single string instead of a list for paths; JSON tool calls that nest the three fields under a 'params' object.","solutions":["Pass all three, with paths as a list: _run(action='replace', paths=['/a.txt', '/b.txt'], pattern='TODO', replacement='DONE').","Pre-validate that replace calls include paths, pattern, and replacement.","Highlight the paths-vs-path distinction in the agent-facing tool description."],"exampleFix":"# before\nfile_tool._run(action='replace', path='/a.txt', pattern='TODO', replacement='DONE')\n\n# after\nfile_tool._run(action='replace', paths=['/a.txt'], pattern='TODO', replacement='DONE')","handlingStrategy":"validation","validationCode":"def replace_in_files(file_tool, paths: list[str] | None, pattern: str | None, replacement: str | None):\n    if not paths or pattern is None or replacement is None:\n        raise ValueError(\"action='replace' requires 'paths', 'pattern', and 'replacement'\")\n    if isinstance(paths, str):\n        paths = [paths]  # accept a single file string\n    return file_tool._run(action='replace', paths=paths, pattern=pattern, replacement=replacement)","typeGuard":"def is_paths_list(value: object) -> bool:\n    return isinstance(value, list) and bool(value) and all(isinstance(p, str) and p for p in value)","tryCatchPattern":"try:\n    file_tool._run(action='replace', paths=paths, pattern=pattern, replacement=replacement)\nexcept ValueError as e:\n    if 'replace' in str(e):\n        paths = paths or [last_written_path]  # recover from singular-path mistake\n        file_tool._run(action='replace', paths=paths, pattern=pattern, replacement=replacement)\n    else:\n        raise","preventionTips":["Remember replace takes 'paths' (a list), not 'path' — encode this in a wrapper signature.","Coerce single strings to one-element lists before calling.","Add a JSON-schema check for replace calls in the agent dispatcher."],"tags":["daytona","argument-validation","file-tool","replace","find-and-replace"],"backgroundTag":null,"analyzedSha":"754d7323beb2fd042e33444a115ea2d5a47193f0","analyzedAt":"2026-08-15T04:06:56.746Z","schemaVersion":2},"datasetVersion":"2026-08-15T17:31:12.345Z"}