openclaw - 💡(How to fix) Fix fix(edit tool): edits[] format with unicode fails with misleading 'Missing required parameters' error [1 comments, 2 participants]

Official PRs (…)
ON THIS PAGE

Recommended Tools

×6

Utilities matched from this issue’s tags and category — try them while you read without losing context.

GitHub issue graph ai analysis

Paste a GitHub issue URL. We fetch that issue, discover linked issues from bodies/comments/timeline, collect linked pull requests, and produce a structured English report.

The report is written in English Markdown for sharing and archival.

Helpful · Quick feedback

Loading…
GitHub stats
openclaw/openclaw#58970Fetched 2026-04-08 02:30:29
View on GitHub
Comments
1
Participants
2
Timeline
1
Reactions
0
Author
Timeline (top)
commented ×1

The edit tool fails with Missing required parameters: oldText alias, newText alias when called with the native pi-coding-agent edits: [{oldText, newText}] array format. This is especially reproducible — and consistent — when oldText or newText contain unicode characters (em dashes U+2014, smart quotes, etc.), because those can also break JSON schema validation of the edits[] items, making edits come through as undefined.

Error Message

CLAUDE_PARAM_GROUPS.edit validates that flat top-level oldText/newText params are present. But Claude Sonnet 4.x sends the pi-coding-agent native format — edits: [{oldText, newText}] — not flat params. The validation fires unconditionally before the tool executes, producing a misleading error instead of the correct "Could not find the exact text in..." message from the underlying tool.

Root Cause

createHostWorkspaceEditTool and createSandboxedEditTool in src/agents/pi-tools.read.ts wrap the underlying edit tool with:

wrapToolParamNormalization(tool, CLAUDE_PARAM_GROUPS.edit)

CLAUDE_PARAM_GROUPS.edit validates that flat top-level oldText/newText params are present. But Claude Sonnet 4.x sends the pi-coding-agent native format — edits: [{oldText, newText}] — not flat params. The validation fires unconditionally before the tool executes, producing a misleading error instead of the correct "Could not find the exact text in..." message from the underlying tool.

Additionally, the wrapper strips the base tool's prepareArguments hook, so the backward-compat flat→array conversion in edit.js::prepareEditArguments never runs.

Fix Action

Fix

Add wrapEditToolParamNormalization to src/agents/pi-tools.read.ts:

export function wrapEditToolParamNormalization(tool: AnyAgentTool): AnyAgentTool {
  const patched = patchToolSchemaForClaudeCompatibility(tool);
  return {
    ...patched,
    execute: async (toolCallId, params, signal, onUpdate) => {
      const normalized = normalizeToolParams(params);
      const record =
        normalized ??
        (params && typeof params === "object" ? (params as Record<string, unknown>) : undefined);
      // Skip flat-param validation when the native edits[] format is present.
      // The underlying tool validates edits[] with clear messages ("Could not find...").
      const hasEditsArray =
        record != null &&
        Array.isArray((record as Record<string, unknown>).edits) &&
        ((record as Record<string, unknown>).edits as unknown[]).length > 0;
      if (!hasEditsArray) {
        assertRequiredParams(record, CLAUDE_PARAM_GROUPS.edit, tool.name);
      }
      return tool.execute(toolCallId, normalized ?? params, signal, onUpdate);
    },
  };
}

Then use it in createSandboxedEditTool and createHostWorkspaceEditTool instead of wrapToolParamNormalization(_, CLAUDE_PARAM_GROUPS.edit).

Code Example

wrapToolParamNormalization(tool, CLAUDE_PARAM_GROUPS.edit)

---

{
  "path": "some/file.ts",
  "edits": [{"oldText": "text with \u2014 em dash", "newText": "replacement"}]
}

---

export function wrapEditToolParamNormalization(tool: AnyAgentTool): AnyAgentTool {
  const patched = patchToolSchemaForClaudeCompatibility(tool);
  return {
    ...patched,
    execute: async (toolCallId, params, signal, onUpdate) => {
      const normalized = normalizeToolParams(params);
      const record =
        normalized ??
        (params && typeof params === "object" ? (params as Record<string, unknown>) : undefined);
      // Skip flat-param validation when the native edits[] format is present.
      // The underlying tool validates edits[] with clear messages ("Could not find...").
      const hasEditsArray =
        record != null &&
        Array.isArray((record as Record<string, unknown>).edits) &&
        ((record as Record<string, unknown>).edits as unknown[]).length > 0;
      if (!hasEditsArray) {
        assertRequiredParams(record, CLAUDE_PARAM_GROUPS.edit, tool.name);
      }
      return tool.execute(toolCallId, normalized ?? params, signal, onUpdate);
    },
  };
}
RAW_BUFFERClick to expand / collapse

Summary

The edit tool fails with Missing required parameters: oldText alias, newText alias when called with the native pi-coding-agent edits: [{oldText, newText}] array format. This is especially reproducible — and consistent — when oldText or newText contain unicode characters (em dashes U+2014, smart quotes, etc.), because those can also break JSON schema validation of the edits[] items, making edits come through as undefined.

Root Cause

createHostWorkspaceEditTool and createSandboxedEditTool in src/agents/pi-tools.read.ts wrap the underlying edit tool with:

wrapToolParamNormalization(tool, CLAUDE_PARAM_GROUPS.edit)

CLAUDE_PARAM_GROUPS.edit validates that flat top-level oldText/newText params are present. But Claude Sonnet 4.x sends the pi-coding-agent native format — edits: [{oldText, newText}] — not flat params. The validation fires unconditionally before the tool executes, producing a misleading error instead of the correct "Could not find the exact text in..." message from the underlying tool.

Additionally, the wrapper strips the base tool's prepareArguments hook, so the backward-compat flat→array conversion in edit.js::prepareEditArguments never runs.

Reproduction

Call the edit tool with:

{
  "path": "some/file.ts",
  "edits": [{"oldText": "text with \u2014 em dash", "newText": "replacement"}]
}

Result: Missing required parameters: oldText alias, newText alias
Expected: Successful edit (or Could not find the exact text in... if text not found)

Fix

Add wrapEditToolParamNormalization to src/agents/pi-tools.read.ts:

export function wrapEditToolParamNormalization(tool: AnyAgentTool): AnyAgentTool {
  const patched = patchToolSchemaForClaudeCompatibility(tool);
  return {
    ...patched,
    execute: async (toolCallId, params, signal, onUpdate) => {
      const normalized = normalizeToolParams(params);
      const record =
        normalized ??
        (params && typeof params === "object" ? (params as Record<string, unknown>) : undefined);
      // Skip flat-param validation when the native edits[] format is present.
      // The underlying tool validates edits[] with clear messages ("Could not find...").
      const hasEditsArray =
        record != null &&
        Array.isArray((record as Record<string, unknown>).edits) &&
        ((record as Record<string, unknown>).edits as unknown[]).length > 0;
      if (!hasEditsArray) {
        assertRequiredParams(record, CLAUDE_PARAM_GROUPS.edit, tool.name);
      }
      return tool.execute(toolCallId, normalized ?? params, signal, onUpdate);
    },
  };
}

Then use it in createSandboxedEditTool and createHostWorkspaceEditTool instead of wrapToolParamNormalization(_, CLAUDE_PARAM_GROUPS.edit).

Affected files

  • src/agents/pi-tools.read.ts

Notes

  • Backward compat preserved: flat {path, oldText, newText} format still works (falls through to the legacy validation path when edits[] is absent)
  • No changes needed to pi-tools.params.ts or the underlying @mariozechner/pi-coding-agent package
  • Verified fix works with Claude Sonnet 4.6 with em dashes (U+2014) and smart quotes in oldText
  • The CLAUDE_PARAM_GROUPS.edit validator was written for the legacy flat format; the pi-coding-agent has used edits[] natively for a while

extent analysis

TL;DR

The issue can be fixed by using the new wrapEditToolParamNormalization function in src/agents/pi-tools.read.ts to conditionally skip flat-param validation when the native edits[] format is present.

Guidance

  • Replace wrapToolParamNormalization(tool, CLAUDE_PARAM_GROUPS.edit) with wrapEditToolParamNormalization(tool) in createSandboxedEditTool and createHostWorkspaceEditTool to apply the fix.
  • Verify that the edits[] format is correctly handled by testing with a request containing Unicode characters, such as em dashes (U+2014) or smart quotes.
  • Ensure that the legacy flat format ({path, oldText, newText}) still works as expected, as the fix preserves backward compatibility.
  • No changes are needed to pi-tools.params.ts or the underlying @mariozechner/pi-coding-agent package.

Example

// In src/agents/pi-tools.read.ts
export const createSandboxedEditTool = (tool: AnyAgentTool) => {
  return wrapEditToolParamNormalization(tool); // Replace the old wrapper
};

Notes

  • The fix has been verified to work with Claude Sonnet 4.6 and should be compatible with other versions that use the edits[] format.
  • The CLAUDE_PARAM_GROUPS.edit validator was designed for the legacy flat format, and this fix adapts it to work with the native edits[] format used by the pi-coding-agent.

Recommendation

Apply the workaround by using the new wrapEditToolParamNormalization function, as it correctly handles both the edits[] and legacy flat formats, and has been verified to work with Unicode characters.

Vote matrix · Quick signals

Works
Did the solution work? Tap to confirm.
Easy Fix
Was it a quick fix?
Time Saver
Did it save you time?
Blocking
Was it severely blocking?
Common Issue
Are others likely hitting this too?
Flaky / Intermittent
Is it intermittent?
Verified / Reproducible
Can you reproduce it reliably?
Loading…

Still need to ship something?

×6

Another batch ranked right after the header list — different links, same matching logic.

Back to top recommendations

TRENDING

openclaw - 💡(How to fix) Fix fix(edit tool): edits[] format with unicode fails with misleading 'Missing required parameters' error [1 comments, 2 participants]