fix(agents): preserve prompt and images on failure-mode-aware fallback retry#43331
fix(agents): preserve prompt and images on failure-mode-aware fallback retry#43331mitchmcalister wants to merge 4 commits intoopenclaw:mainfrom
Conversation
Greptile SummaryThis PR fixes two bugs in the model fallback retry path: (1) the original user prompt is now forwarded verbatim to the fallback model instead of being replaced with a hardcoded continuation string, and (2) images are only stripped when the previous failure reason is Key changes:
Notable observations:
Confidence Score: 4/5
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2935806eaf
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Delete no-op resolveFallbackRetryPrompt (inline body directly), simplify resolveRetryImages to positional args with a single guard, and replace the effectiveOptions IIFE with a conditional spread one-liner. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
5e4181e to
5e98007
Compare
🔒 Aisle Security AnalysisWe found 2 potential security issue(s) in this PR:
1. 🟠 User-supplied images are forwarded on fallback retries (including cross-provider), enabling unintended third-party disclosure
DescriptionThe agent fallback retry logic no longer strips user-supplied images except for
This is a privacy/data-exfiltration regression vs. the prior behavior (where images were stripped on any fallback retry) and may violate user expectations/consent boundaries when fallbacks cross providers. Vulnerable code: function resolveRetryImages(images, isFallbackRetry, previousFailureReason?) {
if (isFallbackRetry && previousFailureReason === "format") {
return undefined;
}
return images;
}RecommendationTreat image forwarding on fallback as a privacy-sensitive operation. Mitigations (prefer safest default):
Example safer implementation (strip on cross-provider retries): function resolveRetryImages(
images: AgentCommandOpts["images"],
isFallbackRetry: boolean,
previousFailureReason: FailoverReason | undefined,
prevProvider: string | undefined,
nextProvider: string | undefined,
): AgentCommandOpts["images"] {
if (!isFallbackRetry) return images;
const isCrossProvider = Boolean(prevProvider && nextProvider && prevProvider !== nextProvider);
// Always strip on cross-provider retry unless explicit opt-in.
if (isCrossProvider) return undefined;
// Optionally still strip on format.
if (previousFailureReason === "format") return undefined;
return images;
}Additionally, document this behavior prominently so users understand that enabling cross-provider fallbacks can cause attachments to be shared with other providers. 2. 🟡 Model fallback retries resend original prompt, risking duplicate non-idempotent side effects
Description
This is risky because fallback retries are triggered by thrown errors (via
Security/impact scenarios:
Vulnerable code: // Fallback retries only fire on thrown errors (not partial streaming results),
// so the original prompt is always safe to re-send. The orphaned-user-message
// repair in attempt.ts handles any residual duplicate user turns.
const effectivePrompt = params.body;RecommendationTreat fallback retries as potentially non-idempotent. Recommended mitigations (choose one or combine):
const effectivePrompt = params.isFallbackRetry
? "Continue where you left off. Do NOT repeat any actions already completed; inspect the transcript/tool results first."
: params.body;
These changes reduce the chance that fallback attempts will duplicate external side effects when recovering from timeouts or mid-turn failures. Analyzed PR: #43331 at commit Last updated on: 2026-03-11T19:18:03Z |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 5e98007ca4
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
Re: Aisle Security AnalysisBoth flagged issues are pre-existing architectural gaps in the fallback logic, not regressions introduced by this PR. That said, the security review surfaced real improvement opportunities worth tracking: 1. Cross-provider image forwarding (High) Pre-existing: the old behavior stripped images as a compatibility hack, not a privacy boundary. Users who configure cross-provider fallbacks have already opted into sending their prompts (text included) to multiple providers. This PR doesn't change the trust model. However, there's a real gap: users may not expect image data to follow text to a different provider. Filed #43481 to add provider-level trust boundaries for images/attachments on cross-provider fallback. 2. Non-idempotent prompt replay (Medium) Pre-existing: the old "Continue where you left off" prompt also triggered a new model turn that could repeat side effects, just with less context about what was originally requested. This PR doesn't make it worse. However, there's a real gap: fallback retries can fire after partial tool execution, and neither the old nor new behavior accounts for that. Filed #43482 to track detecting partial tool execution before deciding replay strategy. |
…pt replay When a model fallback retry fires after partial execution (tool calls completed before timeout/rate-limit), warn the fallback model about already-executed actions to prevent replaying non-idempotent operations like sending messages or running shell commands. - Add partialExecution field to FailoverError with sanitized tool names - Thread partial execution context through model-fallback → agent.ts - Prepend [System: ...] context block to retry prompt listing completed tools and warning against re-sending messages - Log partial execution metadata in fallback decision observations Closes openclaw#43482 Related: openclaw#43331 (security review that flagged this gap) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
|
Converting to draft — rethinking the approach based on reviewer feedback across this PR and its follow-ups (#43485, #43507). The current approach works around the orphaned user message repair in Planning a unified PR that coordinates fallback retries with the session manager's existing retry/branching mechanisms instead of working around them. Issues #43481 and #43492 remain valid and will be addressed in the new approach. |
|
Superseded by #44188 (unified fallback retry safety PR). |
Summary
When a model fallback retry fires (rate limit, timeout, auth failure, format error), the retry callback previously:
This PR fixes both behaviors and plumbs the failure reason so the retry callback can make informed decisions.
Behavioral changes
1. Preserve original prompt on fallback retry
Before:
resolveFallbackRetryPromptreplaced the user's message body with"Continue where you left off. The previous model attempt failed or timed out."on every fallback retry.After: The original
bodyis forwarded verbatim. Fallback retries only fire on thrown errors (not partial streaming results), so the original prompt is always safe to re-send. The orphaned-user-message repair inattempt.tshandles any residual duplicate user turns in session history.Why: The continuation prompt was actively harmful — subagents lost their task text, and main agents got a content-free prompt that degraded response quality on the fallback model.
2. Failure-mode-aware image handling
Before: Images were stripped on every fallback retry (
images: params.isFallbackRetry ? undefined : params.opts.images).After: Images are only stripped when
previousFailureReason === "format"— indicating the model can't handle the image modality. Rate limit, timeout, and auth failures preserve images for the fallback model.Why: Unconditional stripping threw away valid image context on transient failures. Format-only stripping is the correct policy: if a model can't handle images, it should signal that through a format error, not a rate limit.
3.
previousFailureReasonplumbingModelFallbackRunOptionsnow includes an optionalpreviousFailureReason: FailoverReasonfield, populated from the error classification of the previous attempt. This lets theruncallback make failure-mode-aware decisions (currently used for image handling, available for future use).4. Helper cleanup
resolveFallbackRetryPrompt(was a no-op wrapper after the behavioral fix)resolveRetryImagesto a 2-line functionSupersedes
Related open PRs (same fallback area)
Issues addressed
Directly fixes:
Partially addresses:
Related (not fixed here):
previousFailureReasonis a building block)CLI session concern (from #43303 review)
Codex review flagged that forwarding
params.bodyon CLI retries could append a duplicate user turn to a resumed CLI session. This is pre-existing — the old continuation prompt also appended a user turn, just a misleading one. The orphaned-user-message repair inattempt.tshandles this for the embedded runner flow. Full CLI session dedup is tracked in the related issues above.Test plan
resolveRetryImagestests cover all failure-reason branches (format strips, others preserve)previousFailureReasonforwarding tested inmodel-fallback.tsunit tests (undefined on first attempt, set on retry)pnpm testpassespnpm checkpassespnpm buildpasses🤖 Generated with Claude Code