zylon-ai/private-gpt · error · RuntimeError

Unable to replace text

Error message

Unable to replace text

What it means

RuntimeError from the str_replace tool: the session's str_replace(path, old_str, new_str) returned success=False. The raised message is result.error if the session provided detail, else 'Unable to replace text'. Typical session-side causes: file not found, old_str not present or appearing multiple times (ambiguous replacement).

Source

Thrown at private_gpt/components/tools/builders/text_editor_tool_builder.py:142

            ),
        )

    async def build_str_replace_tool(
        self,
        config: CodeExecutionSessionConfig,
        name: str = TEXT_EDITOR_STR_REPLACE_TOOL_NAME,
        type: str = TEXT_EDITOR_STR_REPLACE_TOOL_NAME + "_v1",
        description: str = TEXT_EDITOR_STR_REPLACE_TOOL_FN.metadata.description,
    ) -> ToolSpec:
        async def str_replace(
            path: str,
            old_str: str,
            new_str: str,
        ) -> list[ResultContentBlockType]:
            session = await self._session(config)
            result = await session.str_replace(path, old_str, new_str)
            if not result.success:
                raise RuntimeError(result.error or "Unable to replace text")
            output = _truncated(
                result.output, self._settings.code_execution.max_output_bytes
            )
            return [TextEditorCodeExecutionStrReplaceResultBlock(lines=[output])]

        return ToolSpec.from_defaults(
            name=name,
            type=type,
            runtime="server",
            event_adapter=TextEditorCodeExecutionEventAdapter,
            description=description,
            async_fn=str_replace,
            requirements=[ToolRequirements.SANDBOX],
            execution_metadata=build_rebuild_metadata(
                rebuild_text_editor_str_replace_tool,
                {
                    "config": config,
                    "name": name,

View on GitHub (pinned to 4a030776a3)

Solutions

  1. Read the current file content first (view tool) and copy old_str verbatim, preserving whitespace.
  2. If the error message is generic, capture result.error to get the session's reason (not found vs no match vs multiple matches).
  3. Recreate the session if it expired, then retry; ensure the path is the same one used to create/read the file.
Defensive patterns

Strategy: try-catch

Validate before calling

# best pre-check: read the file first and confirm old_str occurs exactly once
blocks = await view(path=path)
content = blocks[0].content if blocks else ""
assert content.count(old_str) == 1, "old_str must match exactly once before str_replace"

Try / catch

try:
    blocks = await str_replace(path=path, old_str=old_str, new_str=new_str)
except RuntimeError as e:
    detail = str(e) if str(e) != "Unable to replace text" else "replace failed without detail"
    if "not found" in detail or "match" in detail:
        # re-read the file and regenerate old_str from current content
        pass
    raise

Prevention

When it happens

Trigger: Calling str_replace on a path that does not exist in the sandbox, with an old_str that does not exactly match the file content (whitespace/indentation), or an old_str that occurs more than once and the provider requires uniqueness.

Common situations: LLM-hallucinated snippet that does not match the file; tabs-vs-spaces mismatch after a view/read of the file; editing a file in an expired or restarted sandbox session; race with another tool having rewritten the file.

Related errors


AI-assisted analysis of zylon-ai/private-gpt@4a030776a3 (2026-08-15). Data as JSON: /api/errors/109d2d4082291a47. Report an issue: GitHub.