crewAIInc/crewAI · error · ValueError
action='replace' requires 'replacement'.
Error message
action='replace' requires 'replacement'.
What it means
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.
Source
Thrown at lib/crewai-tools/src/crewai_tools/tools/daytona_sandbox_tool/daytona_file_tool.py:214
if self.action == "find" and not self.pattern:
raise ValueError(
"action='find' requires 'pattern' (text to search for inside files)."
)
if self.action == "search" and not self.pattern:
raise ValueError("action='search' requires 'pattern' (glob, e.g. '*.py').")
if self.action == "chmod" and not (self.mode or self.owner or self.group):
raise ValueError(
"action='chmod' requires at least one of 'mode', 'owner', or 'group'."
)
if self.action == "replace":
if not self.paths:
raise ValueError(
"action='replace' requires 'paths' (list of file paths)."
)
if not self.pattern:
raise ValueError("action='replace' requires 'pattern'.")
if self.replacement is None:
raise ValueError("action='replace' requires 'replacement'.")
return self
class DaytonaFileTool(DaytonaBaseTool):
"""Read, write, and manage files inside a Daytona sandbox.
Notes:
- Most useful with `persistent=True` or an explicit `sandbox_id`. With the
default ephemeral mode, files disappear when this tool call finishes.
"""
name: str = "Daytona Sandbox Files"
description: str = (
"Perform filesystem operations inside a Daytona sandbox: read, "
"write, append, list, delete, mkdir, info, exists, move, find "
"(content grep), search (filename glob), chmod (permissions/owner/"
"group), and replace (bulk find-and-replace across files). "
"For files larger than a few KB, create the file with action='write' "View on GitHub (pinned to 754d7323be)
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.
Example fix
# before tool.run(action="replace", paths=["/workspace/a.py"], pattern="foo") # ValueError # after tool.run(action="replace", paths=["/workspace/a.py"], pattern="foo", replacement="foo2") # or to delete matches: tool.run(action="replace", paths=["/workspace/a.py"], pattern="foo", replacement="")
Defensive patterns
Strategy: validation
Validate before calling
def normalize_replacement(replacement: str | None) -> str:
# deletion must be explicit in the tool call; make it explicit at your boundary
return "" if replacement is None else replacement
tool.run(action="replace", paths=ps, pattern=pat, replacement=normalize_replacement(rep)) Try / catch
try:
tool.run(action="replace", paths=ps, pattern=pat, replacement=rep)
except ValidationError as e:
if "requires 'replacement'" in str(e):
tool.run(action="replace", paths=ps, pattern=pat, replacement="") # explicit delete
else:
raise Prevention
- 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.
When it happens
Trigger: Calling DaytonaFileTool with action='replace', paths and pattern set, but replacement omitted; attempting a find-only dry run (not supported by this schema).
Common situations: 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.
Related errors
- action={self.action!r} requires 'path'.
- action='append' requires 'content'. Pass the chunk to append
- action='move' requires 'destination'.
- action='replace' requires 'paths' (list of file paths).
- action='replace' requires 'pattern'.
AI-assisted analysis of crewAIInc/crewAI@754d7323be (2026-08-15).
Data as JSON: /api/errors/a12274c5254bd4af.
Report an issue: GitHub.