resolveFallbackRetryPrompt Discards Original User Prompt on Model Fallback Retry
When a model call fails and triggers fallback retry, the resolveFallbackRetryPrompt() function replaces the entire original user prompt with a generic message, causing agents to lose task context.
π Symptoms
Observable Behavior
When a model call fails or times out and OpenClaw triggers a fallback retry, the agent receives only a generic message instead of the original task instruction. This manifests in two distinct ways:
1. Session Log Evidence
The session log shows a user message with no sender metadata that replaces the original task:
// Session log excerpt (session ec19b29b)
Line 25: { customType: "model-snapshot", provider: "xiaomi", model: "mimo-v2-pro" }
Line 26: { role: "user", content: "Continue where you left off. The previous model attempt failed or timed out.", sender: null }
Line 27: { role: "assistant", model: "mimo-v2-pro" } // fallback retry response2. Context Loss in Subagent Scenarios
When a subagent is spawned with a specific task instruction (e.g., [Subagent Task]: RECORDζη³»εζ΄ε), the original task is completely discarded:
// Original prompt sent to subagent
"[Subagent Task]: RECORDζη³»εζ΄ε"
// Prompt received after fallback retry
"Continue where you left off. The previous model attempt failed or timed out."The agent must infer the task from session history alone, risking:
- Incorrect action selection based on incomplete context
- Failure to complete the intended task
- Potential generation of unintended outputs
3. Diagnostic Indicators
The issue can be identified by examining the resolveFallbackRetryPrompt() function behavior:
// Current implementation (dist/agent-command-*.js)
function resolveFallbackRetryPrompt(params) {
if (!params.isFallbackRetry) return params.body;
if (!params.sessionHasHistory) return params.body;
return "Continue where you left off. The previous model attempt failed or timed out.";
}π§ Root Cause
Technical Analysis
The root cause lies in the conditional logic of resolveFallbackRetryPrompt() which uses an early-return replacement strategy instead of a concatenation strategy.
Failure Sequence:
- The agent sends `params.body` containing the original user prompt to the model
- The model call fails or times out (e.g., xiaomi/mimo-v2-pro)
- OpenClaw sets `params.isFallbackRetry = true`
- OpenClaw checks `params.sessionHasHistory = true` (session contains prior messages)
- The function executes the replacement path:
return "Continue where you left off. The previous model attempt failed or timed out."; - The original `params.body` is discarded entirely
Architectural Inconsistency:
The function conflates two distinct requirements:
- Requirement A: Notify the model that a retry occurred
- Requirement B: Preserve the original task instruction
The current implementation satisfies Requirement A while completely violating Requirement B.
Code Path Analysis:
javascript // In runAgentAttempt(): const effectivePrompt = resolveFallbackRetryPrompt({ body: params.body, isFallbackRetry: params.isFallbackRetry, sessionHasHistory: params.sessionHasHistory });
When isFallbackRetry is true and sessionHasHistory is true, the function returns a fixed string instead of combining it with params.body.
Impact Chain:
Original Prompt (params.body)
β
Model Call Fails
β
isFallbackRetry = true
β
sessionHasHistory = true
β
resolveFallbackRetryPrompt() returns FIXED_STRING
β
effectivePrompt = FIXED_STRING β Original task LOST
β
Fallback model receives no task contextπ οΈ Step-by-Step Fix
Resolution Strategy
Modify resolveFallbackRetryPrompt() to prepend the retry notification to the original prompt rather than replacing it.
Code Change
File: dist/agent-command-*.js (varies by version)
Before:
function resolveFallbackRetryPrompt(params) {
if (!params.isFallbackRetry) return params.body;
if (!params.sessionHasHistory) return params.body;
return "Continue where you left off. The previous model attempt failed or timed out.";
}After:
function resolveFallbackRetryPrompt(params) {
if (!params.isFallbackRetry) return params.body;
if (!params.sessionHasHistory) return params.body;
return "[System: Previous model attempt failed or timed out. Continuing from where you left off.]\n\n" + params.body;
}Alternative Implementation (More Verbose)
For environments requiring clearer separation:
function resolveFallbackRetryPrompt(params) {
if (!params.isFallbackRetry) return params.body;
if (!params.sessionHasHistory) return params.body;
const retryNotice = "[System: Previous model attempt failed or timed out. Continuing from where you left off.]\n\n";
const originalPrompt = params.body;
return retryNotice + originalPrompt;
}Deployment Steps
- Locate the affected file:
find /path/to/openclaw -name "agent-command-*.js" -type f - Backup the original file:
cp /path/to/agent-command-*.js /path/to/agent-command-*.js.bak - Apply the fix using sed:
sed -i 's/return "Continue where you left off. The previous model attempt failed or timed out.";/return "[System: Previous model attempt failed or timed out. Continuing from where you left off.]\\n\\n" + params.body;/g' /path/to/agent-command-*.js - Verify the change:
grep -A3 "function resolveFallbackRetryPrompt" /path/to/agent-command-*.js - Restart the OpenClaw service:
sudo systemctl restart openclaw
π§ͺ Verification
Verification Methodology
To confirm the fix, simulate a model failure and verify the fallback retry prompt contains the original task.
Test Procedure
1. Enable Debug Logging:
export OPENCLAW_LOG_LEVEL=debug
export DEBUG=openclaw:agent:*2. Trigger a Fallback Scenario:
Create a test agent configuration with an intentionally failing model:
// test-fallback-prompt.json
{
"agent": {
"model": "intentionally-invalid-model-for-testing",
"fallbackModel": "gpt-4o-mini",
"prompt": "[Test Task]: Identify the color of the sky"
}
}3. Execute the Agent:
openclaw run --config test-fallback-prompt.json --session test-fallback-$(date +%s)4. Inspect Session Log:
openclaw session log --session-id <session-id> --format json | jq '.messages[] | select(.role == "user") | {content, sender, metadata}'5. Verify Expected Output:
Before fix, the output shows:
{
"content": "Continue where you left off. The previous model attempt failed or timed out.",
"sender": null,
"metadata": {}
}After fix, the output should show:
{
"content": "[System: Previous model attempt failed or timed out. Continuing from where you left off.]\n\n[Test Task]: Identify the color of the sky",
"sender": "system",
"metadata": {
"isFallbackRetry": true
}
}Automated Verification Script
#!/bin/bash
SESSION_ID=$(openclaw session list --limit 1 --format json | jq -r '.[0].id')
FALLBACK_USER_MSG=$(openclaw session log --session-id "$SESSION_ID" --format json | jq -r '.messages[] | select(.role == "user" and .sender == null) | .content')
if echo "$FALLBACK_USER_MSG" | grep -q "\[System: Previous model attempt"; then
if echo "$FALLBACK_USER_MSG" | grep -q "\[Test Task\]"; then
echo "β
VERIFIED: Original prompt preserved in fallback retry"
exit 0
else
echo "β FAILED: System message present but original prompt missing"
exit 1
fi
else
echo "β FAILED: Generic message still being used (fix not applied)"
exit 1
fiβ οΈ Common Pitfalls
Edge Cases and Environment-Specific Traps
1. Empty Prompt Body (params.body is empty string)
If params.body is an empty string, the fixed implementation will produce a prompt with only the system notice:
// Result when params.body = ""
"[System: Previous model attempt failed...]\n\n"
// β Empty original task (may be valid if session history is sufficient)Mitigation: Verify that params.body is non-empty before concatenation:
function resolveFallbackRetryPrompt(params) {
if (!params.isFallbackRetry) return params.body;
if (!params.sessionHasHistory) return params.body;
const retryNotice = "[System: Previous model attempt failed or timed out. Continuing from where you left off.]\n\n";
return params.body ? retryNotice + params.body : retryNotice.trim();
}2. Very Long Original Prompts
Long prompts concatenated with the retry notice may exceed model context limits.
- Monitor token usage when original prompts approach model limits
- Consider truncating `params.body` if it exceeds a threshold (e.g., 4000 tokens)
3. Non-ASCII Characters in Original Prompt
Japanese characters (as in the reported issue with RECORDζη³»εζ΄ε) must be properly preserved:
// Verify encoding is maintained
const testPrompt = "[Subagent Task]: RECORDζη³»εζ΄ε";
const fixed = "[System: ...]\n\n" + testPrompt;
console.log(fixed.includes("RECORDζη³»εζ΄ε")); // Must be true4. Docker Container Caching
If OpenClaw is running in Docker, cached JavaScript files may persist:
# Rebuild container to ensure new code is deployed
docker-compose down
docker-compose build --no-cache openclaw
docker-compose up -d5. Multiple Sequential Fallback Retries
If a model fails multiple times in succession, each retry may prepend additional system notices:
// After 3 retries, prompt becomes:
"[System: ...]\n\n[System: ...]\n\n[System: ...]\n\n[Test Task]: ..."
// β Duplicate notices accumulateMitigation: Check if original prompt already contains the retry notice before prepending:
function resolveFallbackRetryPrompt(params) {
if (!params.isFallbackRetry) return params.body;
if (!params.sessionHasHistory) return params.body;
if (params.body.includes("[System: Previous model attempt")) return params.body;
return "[System: Previous model attempt failed...]\n\n" + params.body;
}6. Version Compatibility
The function signature or call site may change between versions:
| Version | File Pattern | Status |
|---|---|---|
| 2026.4.9 | agent-command-8TL7BESJ.js | Affected |
| 2026.4.11 | agent-command-BUw17dbz.js | Affected |
Always verify the exact function location and parameter names in your deployed version.
π Related Errors
Contextually Connected Issues
The following errors and historical issues are related to the fallback retry prompt behavior:
1. Model Timeout Errors
- E_TIMEOUT: Model call exceeded time limit
- E_MODEL_UNAVAILABLE: Model endpoint unreachable
- E_RATE_LIMIT: API rate limit exceeded during fallback attempt
These errors trigger the isFallbackRetry flag, which activates the problematic code path.
2. Context Window Errors
- E_CONTEXT_LENGTH: Combined prompt + history exceeds model context limit
- May occur after fix if retry notice + original prompt + history exceeds limits
3. Historical Issues
| Issue ID | Description | Status |
|---|---|---|
| GH-XXXX | Initial report: Subagent loses task context on retry | Open |
| GH-YYYY | Session logs show user messages with null sender metadata | Related |
4. Related Configuration Parameters
// These parameters control the fallback behavior
interface FallbackConfig {
enabled: boolean; // Enable/disable fallback retry
maxRetries: number; // Maximum retry attempts
retryDelay: number; // Delay between retries (ms)
retryModels: string[]; // List of fallback models to try
preserveOriginalPrompt: boolean; // NEW: Flag to preserve original prompt
}5. Similar Patterns in Codebase
Other functions that may exhibit similar replacement-vs-concatenation issues:
- `resolveSystemPrompt()` - May overwrite system instructions
- `injectContextSummary()` - Could replace rather than append context
- `formatHistoryForModel()` - Might truncate history instead of summarizing
6. Monitoring Recommendations
Implement telemetry for fallback retry scenarios:
// Suggested metrics to track
metrics.increment('openclaw.fallback.retry.count');
metrics.gauge('openclaw.fallback.prompt.length', effectivePrompt.length);
metrics.histogram('openclaw.fallback.prompt.original_length', params.body.length);