{"record":{"id":"a12274c5254bd4af","repo":"crewAIInc/crewAI","slug":"action-replace-requires-replacement","errorCode":null,"errorMessage":"action='replace' requires 'replacement'.","messagePattern":"action='replace' requires '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":214,"sourceCode":"        if self.action == \"find\" and not self.pattern:\n            raise ValueError(\n                \"action='find' requires 'pattern' (text to search for inside files).\"\n            )\n        if self.action == \"search\" and not self.pattern:\n            raise ValueError(\"action='search' requires 'pattern' (glob, e.g. '*.py').\")\n        if self.action == \"chmod\" and not (self.mode or self.owner or self.group):\n            raise ValueError(\n                \"action='chmod' requires at least one of 'mode', 'owner', or 'group'.\"\n            )\n        if self.action == \"replace\":\n            if not self.paths:\n                raise ValueError(\n                    \"action='replace' requires 'paths' (list of file paths).\"\n                )\n            if not self.pattern:\n                raise ValueError(\"action='replace' requires 'pattern'.\")\n            if self.replacement is None:\n                raise ValueError(\"action='replace' requires 'replacement'.\")\n        return self\n\n\nclass DaytonaFileTool(DaytonaBaseTool):\n    \"\"\"Read, write, and manage files inside a Daytona sandbox.\n\n    Notes:\n      - Most useful with `persistent=True` or an explicit `sandbox_id`. With the\n        default ephemeral mode, files disappear when this tool call finishes.\n    \"\"\"\n\n    name: str = \"Daytona Sandbox Files\"\n    description: str = (\n        \"Perform filesystem operations inside a Daytona sandbox: read, \"\n        \"write, append, list, delete, mkdir, info, exists, move, find \"\n        \"(content grep), search (filename glob), chmod (permissions/owner/\"\n        \"group), and replace (bulk find-and-replace across files). \"\n        \"For files larger than a few KB, create the file with action='write' \"","sourceCodeStart":196,"sourceCodeEnd":232,"githubUrl":"https://github.com/crewAIInc/crewAI/blob/754d7323beb2fd042e33444a115ea2d5a47193f0/lib/crewai-tools/src/crewai_tools/tools/daytona_sandbox_tool/daytona_file_tool.py#L196-L232","documentation":"The final check in the action='replace' branch of DaytonaFileToolSchema._validate_action_args requires 'replacement' (the new text). replacement is Optional[str] with a None default, so the validator explicitly rejects its absence — you may pass an empty string (to delete matches) but not omit the field.","triggerScenarios":"Calling DaytonaFileTool with action='replace', paths and pattern set, but replacement omitted; attempting a find-only dry run (not supported by this schema).","commonSituations":"Agent tool call drops the last argument; caller wants to delete matched text and assumes omission means empty string — it must be explicit ''; misunderstanding that replace always performs the substitution.","solutions":["Always supply replacement: tool.run(action='replace', paths=[...], pattern='x', replacement='y').","To delete all occurrences, pass replacement='' explicitly.","If you only want to locate text, use action='find' instead."],"exampleFix":"# before\ntool.run(action=\"replace\", paths=[\"/workspace/a.py\"], pattern=\"foo\")  # ValueError\n\n# after\ntool.run(action=\"replace\", paths=[\"/workspace/a.py\"], pattern=\"foo\", replacement=\"foo2\")\n# or to delete matches:\ntool.run(action=\"replace\", paths=[\"/workspace/a.py\"], pattern=\"foo\", replacement=\"\")","handlingStrategy":"validation","validationCode":"def normalize_replacement(replacement: str | None) -> str:\n    # deletion must be explicit in the tool call; make it explicit at your boundary\n    return \"\" if replacement is None else replacement\n\ntool.run(action=\"replace\", paths=ps, pattern=pat, replacement=normalize_replacement(rep))","typeGuard":null,"tryCatchPattern":"try:\n    tool.run(action=\"replace\", paths=ps, pattern=pat, replacement=rep)\nexcept ValidationError as e:\n    if \"requires 'replacement'\" in str(e):\n        tool.run(action=\"replace\", paths=ps, pattern=pat, replacement=\"\")  # explicit delete\n    else:\n        raise","preventionTips":["Treat (pattern, replacement) as an atomic pair in call sites.","Use '' explicitly for deletion; never omit the field.","For find-only previews, use action='find', not replace."],"tags":["daytona","pydantic","validation","input-validation","filesystem"],"backgroundTag":null,"analyzedSha":"754d7323beb2fd042e33444a115ea2d5a47193f0","analyzedAt":"2026-08-15T04:06:56.746Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}