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_textandnew_textas valid aliases, matching the existing pattern forold_string/new_string. - Actual: The tool rejects the parameters and returns an
INVALID_PARAMETERSerror.
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 parameternewText/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
CLAUDE_PARAM_GROUPS.editβ Thekeysarrays for the old/new text parameter groups do not include"old_text"or"new_text".normalizeToolParamsβ The parameter normalization function lacks mappings forold_text β oldTextandnew_text β newText. Existing mappings follow this pattern:// Existing mappings (line ~47) old_string: 'oldText', new_string: 'newText', // Missing: // old_text: 'oldText', // new_text: 'newText',patchToolSchemaForClaudeCompatibilityβ The schema alias generation does not includeold_textandnew_textin 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 aliasingThe old_text/new_text aliases simply follow this established pattern but were omitted from the original implementation.
Provider Compatibility Matrix
| Model/Provider | Parameter Convention | Requires Alias |
|---|---|---|
| Claude (Anthropic) | camelCase | oldText, newText |
| Gemini (Google) | snake_case | old_string, new_string |
| Qwen (Alibaba) | snake_case | old_text, new_text |
| Llama variants | Mixed | Often 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.
π Related Errors
INVALID_PARAMETERSβ General parameter validation failure. In this context, caused by unrecognizedold_text/new_textnames.MISSING_REQUIRED_PARAMETERβ Related error whenoldTextandnewTextare both absent after normalization fails.- Issue #793 β Original implementation of
old_string/new_stringaliases. The current issue is a follow-up to support additional snake_case variants. pathvsfile_pathβ Same alias pattern issue for file path parameters. Resolved by addingfile_pathas an accepted alias forpath.- 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.