fix(opencode): offer a fresh session when a provider rejects stored history - #1307
fix(opencode): offer a fresh session when a provider rejects stored history#1307pedramamini wants to merge 1 commit into
Conversation
…istory When Gemini (via OpenCode) rejects a session's replayed transcript with a bare 400 "Request contains an invalid argument" / INVALID_ARGUMENT, that provider session is dead: OpenCode replays the whole stored conversation on every turn, so every later prompt fails identically. Maestro classified the message as `unknown` and the recovery modal offered only "Try Again", which replays the same broken history and fails again - the user's only way out was to notice this and start a new tab by hand. - Classify the 400 INVALID_ARGUMENT family as `session_not_found` for OpenCode, with a message that says the stored history is unusable. - Add the missing `session_not_found` case to useAgentErrorRecovery, which previously fell through to the generic retry action. It now leads with "Start New Session" (a fresh AI tab), which is the only action that can succeed - this also covers the existing "session not found" / "invalid session" patterns for every agent. This does not repair a transcript that OpenCode already wrote to disk (the corruption reproduces in the OpenCode CLI with Maestro out of the loop), it makes Maestro recognize the dead-end and hand the user the one recovery that works.
📝 WalkthroughWalkthroughChangesOpenCode session recovery
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant Provider
participant ErrorParser
participant RecoveryHook
participant SessionUI
Provider->>ErrorParser: Return 400 INVALID_ARGUMENT
ErrorParser->>RecoveryHook: Classify as recoverable session_not_found
RecoveryHook->>SessionUI: Provide Start New Session action
SessionUI->>RecoveryHook: Click action
RecoveryHook->>SessionUI: Invoke onNewSession
Possibly related PRs
Suggested reviewers: 🚥 Pre-merge checks | ✅ 5✅ Passed checks (5 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Greptile SummaryThis PR classifies OpenCode Gemini INVALID_ARGUMENT responses as unusable sessions and adds a fresh-session recovery action.
Confidence Score: 4/5The PR appears safe to merge, but the unreachable recovery branch and overly broad provider-error classification should be addressed as non-blocking correctness and maintainability concerns. The intended malformed-history response reaches existing fresh-session recovery, but the newly added modal action is bypassed and the generic INVALID_ARGUMENT matcher can apply session recovery to unrelated invalid requests. Files Needing Attention: src/main/parsers/error-patterns.ts, src/renderer/hooks/agent/useAgentErrorRecovery.tsx Important Files Changed
Flowchart%%{init: {'theme': 'neutral'}}%%
flowchart LR
A[OpenCode provider error] --> B[Error pattern matcher]
B --> C[session_not_found]
C --> D[Error listener]
D --> E[Clear provider session ID]
D --> F[Inline session recovery]
D -. modal suppressed .-> G[Agent error modal]
G -. unreachable .-> H[Start New Session action]
Reviews (1): Last reviewed commit: "fix(opencode): offer a fresh session whe..." | Re-trigger Greptile |
| case 'session_not_found': | ||
| // The provider session is gone or its stored history is unusable (e.g. | ||
| // a Gemini 400 INVALID_ARGUMENT on a session whose transcript was left | ||
| // malformed by an aborted turn). Retrying replays the same broken | ||
| // history, so a fresh session is the only way forward - lead with it. | ||
| if (options.onNewSession) { | ||
| actions.push({ | ||
| id: 'new-session', | ||
| label: 'Start New Session', | ||
| description: 'Begin a fresh conversation in a new tab', | ||
| primary: true, | ||
| icon: <MessageSquarePlus className="w-4 h-4" />, | ||
| onClick: options.onNewSession, | ||
| }); | ||
| } | ||
| break; |
There was a problem hiding this comment.
Recovery branch is unreachable
The normal session_not_found flow clears the tab error and explicitly skips opening the agent-error modal, so this modal recovery branch never provides the advertised Start New Session action. The isolated hook test therefore does not cover the user-facing recovery path, leaving this dead branch vulnerable to unnoticed drift.
Knowledge Base Used: Agent Run: Driving, Parsing, and Tracking AI Agent CLIs
| // prompt identically and the session is unusable (issue #307). | ||
| // Classify as session_not_found: the stored provider session is dead, | ||
| // and retrying it can never succeed - only a fresh session can. | ||
| pattern: /request contains an invalid argument|\bINVALID_ARGUMENT\b/i, |
There was a problem hiding this comment.
Generic errors discard sessions
This regex treats every OpenCode INVALID_ARGUMENT response as an unusable stored session, including request-level failures caused by an invalid model option, tool payload, prompt field, or configuration. That classification clears the provider session and starts fresh-session recovery even though recreating the conversation does not correct the invalid request.
Knowledge Base Used: Agent Run: Driving, Parsing, and Tracking AI Agent CLIs
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@src/main/parsers/error-patterns.ts`:
- Around line 387-390: Update the error pattern near the INVALID_ARGUMENT entry
so only messages containing session-history-specific context are classified as
an unusable session; remove the standalone \bINVALID_ARGUMENT\b alternative from
the pattern while preserving the existing request-specific match. Add a negative
test covering an unrelated standalone INVALID_ARGUMENT to verify it is not
mapped to session_not_found or treated as non-recoverable.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Path: .coderabbit.yaml
Review profile: CHILL
Plan: Pro Plus
Run ID: b26e952f-0274-427a-bb6f-ec4ac8de03cc
📒 Files selected for processing (4)
src/__tests__/main/parsers/error-patterns.test.tssrc/__tests__/renderer/hooks/useAgentErrorRecovery.test.tssrc/main/parsers/error-patterns.tssrc/renderer/hooks/agent/useAgentErrorRecovery.tsx
| pattern: /request contains an invalid argument|\bINVALID_ARGUMENT\b/i, | ||
| message: | ||
| 'The provider rejected this conversation (400 INVALID_ARGUMENT). The stored session history is unusable - start a new session to continue.', | ||
| recoverable: true, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
Do not classify every INVALID_ARGUMENT as an unusable session.
A standalone INVALID_ARGUMENT can describe a malformed current request or configuration, not corrupted stored history. This maps those failures to session_not_found, suppresses retry, and incorrectly tells users to start over. The JSON test also contains the message text, so it does not validate the standalone status branch. Remove the standalone status alternative, or require additional session-history-specific context, and add a negative test for an unrelated INVALID_ARGUMENT.
Proposed narrow match
- pattern: /request contains an invalid argument|\bINVALID_ARGUMENT\b/i,
+ pattern: /\brequest contains an invalid argument\b/i,📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| pattern: /request contains an invalid argument|\bINVALID_ARGUMENT\b/i, | |
| message: | |
| 'The provider rejected this conversation (400 INVALID_ARGUMENT). The stored session history is unusable - start a new session to continue.', | |
| recoverable: true, | |
| pattern: /\brequest contains an invalid argument\b/i, | |
| message: | |
| 'The provider rejected this conversation (400 INVALID_ARGUMENT). The stored session history is unusable - start a new session to continue.', | |
| recoverable: true, |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@src/main/parsers/error-patterns.ts` around lines 387 - 390, Update the error
pattern near the INVALID_ARGUMENT entry so only messages containing
session-history-specific context are classified as an unusable session; remove
the standalone \bINVALID_ARGUMENT\b alternative from the pattern while
preserving the existing request-specific match. Add a negative test covering an
unrelated standalone INVALID_ARGUMENT to verify it is not mapped to
session_not_found or treated as non-recoverable.
There was a problem hiding this comment.
Pull request overview
This PR improves OpenCode error recovery when a provider rejects the stored conversation history (notably Gemini returning INVALID_ARGUMENT), so users are offered a fresh session instead of being stuck in a retry loop.
Changes:
- Adds an OpenCode error-pattern mapping for the Gemini
Request contains an invalid argument/INVALID_ARGUMENTfailure, classifying it assession_not_found. - Extends the renderer recovery-action logic to handle
session_not_foundby leading with a Start New Session action. - Adds unit tests for the new pattern matching and for the
session_not_foundrecovery-action behavior.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| src/main/parsers/error-patterns.ts | Adds a new OpenCode session_not_found pattern for Gemini INVALID_ARGUMENT failures and a user-facing guidance message. |
| src/renderer/hooks/agent/useAgentErrorRecovery.tsx | Adds explicit recovery actions for session_not_found, prioritizing starting a new session. |
| src/tests/main/parsers/error-patterns.test.ts | Adds coverage for the new OpenCode session_not_found pattern and false-positive guards. |
| src/tests/renderer/hooks/useAgentErrorRecovery.test.ts | Verifies session_not_found yields a single primary new-session action and does not call onRetry. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| pattern: /request contains an invalid argument|\bINVALID_ARGUMENT\b/i, | ||
| message: | ||
| 'The provider rejected this conversation (400 INVALID_ARGUMENT). The stored session history is unusable - start a new session to continue.', | ||
| recoverable: true, |
closes #307
Problem
With OpenCode + Gemini, a session can reach a state where every prompt fails with a bare 400:
Once it happens the session never recovers. That part is not ours to fix: the reporter confirmed the same session fails identically in the OpenCode CLI with Maestro out of the loop, so the malformed transcript is already in OpenCode's session storage. OpenCode replays the whole stored conversation on every turn, so one bad entry (typically a tool call left without its matching result when a turn is aborted mid-stream) poisons every subsequent request.
What was ours to fix is the dead end Maestro presented:
matchErrorPatternhad no OpenCode pattern for this message, so it fell through totype: 'unknown'and the modal showed the raw provider string.useAgentErrorRecoveryhas nosession_not_foundcase at all, so it hit thedefaultbranch and offered a single Try Again action. Retrying replays the same broken history and fails the same way. The reporter's actual workaround was to notice this and hand-start a new session.Change
src/main/parsers/error-patterns.ts: classify theRequest contains an invalid argument/INVALID_ARGUMENTfamily assession_not_foundfor OpenCode, with a message that names the cause ("the stored session history is unusable - start a new session to continue").session_not_foundis already inNON_RETRYABLE_TYPES, so automation won't burn quota re-poking a dead session either.src/renderer/hooks/agent/useAgentErrorRecovery.tsx: add the missingsession_not_foundcase, leading with Start New Session (a fresh AI tab in the same agent, workspace and settings intact). This also fixes the pre-existing gap for thesession not found/invalid sessionpatterns that Claude Code and OpenCode already emit - those got the same futile retry button.Tests
error-patterns.test.ts: newsession_not_foundblock covering the bare Gemini message, the full response body with"status": "INVALID_ARGUMENT", the existingsession not foundpath, and false-positive guards ("the function takes three arguments" etc. stay unmatched).useAgentErrorRecovery.test.ts: assertssession_not_foundyields exactly one primarynew-sessionaction and never callsonRetry.Verified locally:
vitest run src/__tests__/main/parsers/ src/__tests__/renderer/hooks/useAgentErrorRecovery.test.ts src/__tests__/renderer/hooks/useModalHandlers.test.ts src/__tests__/shared/retryClassification.test.ts(537 passed), plusnpm run lint, eslint and prettier clean on the changed files.Not addressed here
Prevention. The most likely trigger is the interrupt path: on the CLI transport the stop button sends SIGINT and escalates to a kill 2s later, which can cut OpenCode off before it finalizes the aborted turn. The Encore-gated OpenCode serve transport (#1171) already aborts through
session.abort()instead, which should be gentler - but confirming that as the cause needs a repro rather than a guess, so it stays out of this PR.Summary by CodeRabbit
New Features
Tests