May 02, 2026

Edit Tool Rejects snake_case Parameters old_text/new_text

The edit tool rejects snake_case parameter names (old_text/new_text) emitted by certain LLMs, causing edit failures and forcing fallback to full file rewrite via write tool.

πŸ” Symptoms

Error Manifestation

When an LLM (such as qwen3.5:35b or other models preferring snake_case conventions) invokes the edit tool with old_text and new_text parameters, the tool call fails with the following error:

{
  "error": {
    "code": "INVALID_PARAMETERS",
    "message": "Missing required parameters: oldText (oldText or old_string), newText (newText or new_string). Supply correct parameters before retrying."
  }
}

CLI Reproduction Steps

bash

Simulate a model calling edit with snake_case parameters

claude –dangerously-skip-permissions edit
–path “src/utils/config.ts”
–old_text “const API_URL = ‘http://localhost’”
–new_text “const API_URL = ‘https://api.example.com’”

Expected vs Actual Behavior

  • Expected: The tool accepts old_text and new_text as valid aliases, matching the existing pattern for old_string/new_string.
  • Actual: The tool rejects the parameters and returns an INVALID_PARAMETERS error.

Downstream Effect

When the edit tool fails, the agent typically falls back to the write tool, which performs a full file rewrite rather than a targeted edit:

Tool call: write({ path: "src/utils/config.ts", content: "..." })
Result: Success (full file rewrite)

This behavior is less efficient, consumes more tokens, and increases the risk of unintended changes.

🧠 Root Cause

Parameter Validation Gap

The edit tool’s parameter validation layer in src/agents/pi-tools.params.ts only recognizes a subset of parameter names. The current accepted aliases are:

  • oldText / old_string β€” recognized old text parameter
  • newText / new_string β€” recognized new text parameter

The snake_case variants old_text and new_text are not included in the validation schema.

Affected Code Locations

  1. CLAUDE_PARAM_GROUPS.edit β€” The keys arrays for the old/new text parameter groups do not include "old_text" or "new_text".
  2. normalizeToolParams β€” The parameter normalization function lacks mappings for old_text β†’ oldText and new_text β†’ newText. Existing mappings follow this pattern:
    // Existing mappings (line ~47)
      old_string: 'oldText',
      new_string: 'newText',
      // Missing:
      // old_text: 'oldText',
      // new_text: 'newText',
  3. patchToolSchemaForClaudeCompatibility β€” The schema alias generation does not include old_text and new_text in the alias definitions sent to LLM providers.

Architectural Pattern

The codebase already follows a consistent pattern for parameter aliasing (established in PR #793 for old_string/new_string):

file_path β†’ path        // File path aliasing
old_string β†’ oldText     // Old text aliasing  
new_string β†’ newText     // New text aliasing

The old_text/new_text aliases simply follow this established pattern but were omitted from the original implementation.

Provider Compatibility Matrix

Model/ProviderParameter ConventionRequires Alias
Claude (Anthropic)camelCaseoldText, newText
Gemini (Google)snake_caseold_string, new_string
Qwen (Alibaba)snake_caseold_text, new_text
Llama variantsMixedOften old_text

πŸ› οΈ Step-by-Step Fix

Prerequisites

  • OpenClaw repository cloned locally
  • Node.js >= 18.x installed
  • Write access to branch for patch submission

Step 1: Locate the Parameter Configuration File

bash cd /path/to/openclaw find . -name “pi-tools.params.ts” -type f

Output: ./src/agents/pi-tools.params.ts

Step 2: Modify CLAUDE_PARAM_GROUPS.edit

Open src/agents/pi-tools.params.ts and locate the CLAUDE_PARAM_GROUPS.edit definition:

// BEFORE
export const CLAUDE_PARAM_GROUPS: Record<string, ParamGroup> = {
  edit: {
    oldText: {
      keys: ['oldText', 'old_string'],
      required: true,
    },
    newText: {
      keys: ['newText', 'new_string'],
      required: true,
    },
    path: {
      keys: ['path'],
      required: true,
    },
  },
  // ... other tools
};

// AFTER
export const CLAUDE_PARAM_GROUPS: Record<string, ParamGroup> = {
  edit: {
    oldText: {
      keys: ['oldText', 'old_string', 'old_text'],
      required: true,
    },
    newText: {
      keys: ['newText', 'new_string', 'new_text'],
      required: true,
    },
    path: {
      keys: ['path'],
      required: true,
    },
  },
  // ... other tools
};

Step 3: Update normalizeToolParams Function

Locate the normalizeToolParams function and add the missing mappings:

// BEFORE (around line 45-55)
export function normalizeToolParams(params: Record<string, unknown>): Record<string, unknown> {
  const normalized: Record<string, unknown> = {};
  
  for (const [key, value] of Object.entries(params)) {
    // Handle snake_case parameter names
    switch (key) {
      case 'file_path':
        normalized.path = value;
        break;
      case 'old_string':
        normalized.oldText = value;
        break;
      case 'new_string':
        normalized.newText = value;
        break;
      default:
        normalized[key] = value;
    }
  }
  
  return normalized;
}

// AFTER
export function normalizeToolParams(params: Record<string, unknown>): Record<string, unknown> {
  const normalized: Record<string, unknown> = {};
  
  for (const [key, value] of Object.entries(params)) {
    // Handle snake_case parameter names
    switch (key) {
      case 'file_path':
        normalized.path = value;
        break;
      case 'old_string':
      case 'old_text':      // <-- ADD THIS CASE
        normalized.oldText = value;
        break;
      case 'new_string':
      case 'new_text':      // <-- ADD THIS CASE
        normalized.newText = value;
        break;
      default:
        normalized[key] = value;
    }
  }
  
  return normalized;
}

Step 4: Update patchToolSchemaForClaudeCompatibility

Locate the function that generates schema aliases and add the new snake_case variants:

// BEFORE
function patchToolSchemaForClaudeCompatibility(schema: ToolParameters): ToolParameters {
  // ... existing logic
  
  // Add aliases for compatibility
  const aliases: Record<string, string[]> = {
    path: ['file_path'],
    oldText: ['old_string'],
    newText: ['new_string'],
  };
  
  // ... rest of function
}

// AFTER
function patchToolSchemaForClaudeCompatibility(schema: ToolParameters): ToolParameters {
  // ... existing logic
  
  // Add aliases for compatibility
  const aliases: Record<string, string[]> = {
    path: ['file_path'],
    oldText: ['old_string', 'old_text'],
    newText: ['new_string', 'new_text'],
  };
  
  // ... rest of function
}

Step 5: Run Type Checking

bash npm run type-check

Expected: No errors

Step 6: Run Relevant Tests

bash npm test – –grep “normalizeToolParams|edit.*param|param.*alias”

Expected: All related tests pass

πŸ§ͺ Verification

Unit Test for normalizeToolParams

Create or update the test file src/agents/pi-tools.params.test.ts:

describe('normalizeToolParams', () => {
  it('should normalize old_text and new_text to oldText and newText', () => {
    const input = {
      path: 'src/config.ts',
      old_text: 'const API = "v1"',
      new_text: 'const API = "v2"',
    };
    
    const result = normalizeToolParams(input);
    
    expect(result).toEqual({
      path: 'src/config.ts',
      oldText: 'const API = "v1"',
      newText: 'const API = "v2"',
    });
  });
  
  it('should normalize file_path to path', () => {
    const input = {
      file_path: 'src/config.ts',
      old_text: 'old',
      new_text: 'new',
    };
    
    const result = normalizeToolParams(input);
    
    expect(result).toEqual({
      path: 'src/config.ts',
      oldText: 'old',
      newText: 'new',
    });
  });
});

CLI Verification

After applying the fix, verify the edit tool accepts snake_case parameters:

bash

Create a test file

echo “const API = ‘v1’;” > /tmp/test-config.ts

Test edit with old_text/new_text (should succeed)

claude –dangerously-skip-permissions edit
–path “/tmp/test-config.ts”
–old_text “const API = ‘v1’;”
–new_text “const API = ‘v2’;”

Verify the change

cat /tmp/test-config.ts

Expected output: const API = ‘v2’;

Integration Test

bash

Run the full test suite for parameter handling

npm test – –grep “pi-tools”

Expected output:

βœ“ should normalize old_text and new_text to oldText and newText
βœ“ should handle mixed parameter conventions
βœ“ edit tool should accept snake_case parameters
βœ“ schema aliases should include old_text and new_text
βœ“ all 47 tests passed

⚠️ Common Pitfalls

1. Incomplete Alias Coverage

Pitfall: Adding aliases to only one of the three locations (CLAUDE_PARAM_GROUPS, normalizeToolParams, or patchToolSchemaForClaudeCompatibility) results in partial functionality.

Prevention: Always update all three locations in a single commit to maintain consistency.

2. Case Sensitivity Issues

Pitfall: Parameter names are case-sensitive. Old_Text or OLD_TEXT will not be recognized.

Prevention: Ensure exact snake_case: old_text, new_text.

typescript // WRONG - will not work keys: [‘oldText’, ‘old_string’, ‘Old_Text’]

// CORRECT keys: [‘oldText’, ‘old_string’, ‘old_text’]

3. Schema Validation Timing

Pitfall: The patchToolSchemaForClaudeCompatibility function modifies schemas sent to providers. If not updated, models may not see the new aliases in their tool definitions.

Impact: Models will receive tool schemas without old_text/new_text as valid options.

4. Docker Environment

Pitfall: When running OpenClaw inside Docker, changes to src/agents/pi-tools.params.ts require recompilation.

bash

Build the Docker image to include changes

docker build -t openclaw:dev . docker run -it openclaw:dev edit –path file.txt –old_text “foo” –new_text “bar”

5. Cached Parameter Schemas

Pitfall: Some environments cache tool schemas. After applying the fix, schema cache may need invalidation.

bash

Clear any cached schemas

rm -rf ~/.claude/cache/schemas/

Or set environment variable

export CLAUDE_SCHEMA_CACHE_TTL=0

6. Version Compatibility

Pitfall: If deploying to multiple OpenClaw versions, servers must be running the patched version for parameter normalization to work.

Prevention: Ensure all instances in a multi-server deployment are updated together.

  • INVALID_PARAMETERS β€” General parameter validation failure. In this context, caused by unrecognized old_text/new_text names.
  • MISSING_REQUIRED_PARAMETER β€” Related error when oldText and newText are both absent after normalization fails.
  • Issue #793 β€” Original implementation of old_string/new_string aliases. The current issue is a follow-up to support additional snake_case variants.
  • path vs file_path β€” Same alias pattern issue for file path parameters. Resolved by adding file_path as an accepted alias for path.
  • Tool schema mismatch errors β€” When provider tool definitions differ from internal validation schemas, causing validation failures on valid parameter sets.
  • Edit β†’ Write fallback cascade β€” Downstream effect where failed edit calls trigger the agent to use write, causing full file rewrites instead of targeted edits.

Evidence & Sources

This troubleshooting guide was automatically synthesized by the FixClaw Intelligence Pipeline from community discussions.