Skip to content

fix(opencode): offer a fresh session when a provider rejects stored history - #1307

Open
pedramamini wants to merge 1 commit into
mainfrom
fix/307-opencode-invalid-argument-session-recovery
Open

fix(opencode): offer a fresh session when a provider rejects stored history#1307
pedramamini wants to merge 1 commit into
mainfrom
fix/307-opencode-invalid-argument-session-recovery

Conversation

@pedramamini

@pedramamini pedramamini commented Jul 25, 2026

Copy link
Copy Markdown
Collaborator

closes #307

Problem

With OpenCode + Gemini, a session can reach a state where every prompt fails with a bare 400:

{ "error": { "code": 400, "message": "Request contains an invalid argument.", "status": "INVALID_ARGUMENT" } }

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:

  • matchErrorPattern had no OpenCode pattern for this message, so it fell through to type: 'unknown' and the modal showed the raw provider string.
  • useAgentErrorRecovery has no session_not_found case at all, so it hit the default branch 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 the Request contains an invalid argument / INVALID_ARGUMENT family as session_not_found for OpenCode, with a message that names the cause ("the stored session history is unusable - start a new session to continue"). session_not_found is already in NON_RETRYABLE_TYPES, so automation won't burn quota re-poking a dead session either.
  • src/renderer/hooks/agent/useAgentErrorRecovery.tsx: add the missing session_not_found case, 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 the session not found / invalid session patterns that Claude Code and OpenCode already emit - those got the same futile retry button.

Tests

  • error-patterns.test.ts: new session_not_found block covering the bare Gemini message, the full response body with "status": "INVALID_ARGUMENT", the existing session not found path, and false-positive guards ("the function takes three arguments" etc. stay unmatched).
  • useAgentErrorRecovery.test.ts: asserts session_not_found yields exactly one primary new-session action and never calls onRetry.

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), plus npm 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

    • Added automatic detection for invalid or unavailable stored sessions, including Gemini-style errors.
    • Added a Start New Session recovery option when a stored session can’t be restored.
    • Recovery now prioritizes starting a new session instead of retrying an unusable session.
  • Tests

    • Added coverage for session error detection and recovery behavior, including protection against false positives.

…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.
Copilot AI review requested due to automatic review settings July 25, 2026 21:01
@coderabbitai

coderabbitai Bot commented Jul 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Changes

OpenCode session recovery

Layer / File(s) Summary
Classify unusable OpenCode sessions
src/main/parsers/error-patterns.ts, src/__tests__/main/parsers/error-patterns.test.ts
Invalid-argument responses are classified as recoverable session_not_found errors, with matching and false-positive cases tested.
Route session errors to new-session recovery
src/renderer/hooks/agent/useAgentErrorRecovery.tsx, src/__tests__/renderer/hooks/useAgentErrorRecovery.test.ts
session_not_found errors expose a primary “Start New Session” action without retry, and its callback behavior is tested.

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
Loading

Possibly related PRs

  • RunMaestro/Maestro#1043: Extends the same session_not_found classification and recovery flow for another provider pattern.
  • RunMaestro/Maestro#1190: Extends session_not_found classification through the same parser and recovery plumbing.

Suggested reviewers: copilot, reachrazamair

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly summarizes the main change: offering a new session when stored history is rejected.
Linked Issues check ✅ Passed The changes address issue #307 by classifying the INVALID_ARGUMENT error and routing users to Start New Session.
Out of Scope Changes check ✅ Passed The PR stays focused on the reported error handling and matching tests, with no obvious unrelated code changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/307-opencode-invalid-argument-session-recovery

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.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@greptile-apps

greptile-apps Bot commented Jul 25, 2026

Copy link
Copy Markdown

Greptile Summary

This PR classifies OpenCode Gemini INVALID_ARGUMENT responses as unusable sessions and adds a fresh-session recovery action.

  • Adds OpenCode error-pattern coverage and false-positive tests.
  • Adds a session_not_found branch to the renderer recovery-action hook.
  • Adds a hook test asserting that the branch invokes new-session rather than retry.

Confidence Score: 4/5

The 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

Filename Overview
src/main/parsers/error-patterns.ts Adds a broad INVALID_ARGUMENT mapping to session_not_found that is not restricted to errors caused by stored history.
src/renderer/hooks/agent/useAgentErrorRecovery.tsx Adds a new-session recovery branch that the normal session_not_found flow currently bypasses.
src/tests/main/parsers/error-patterns.test.ts Covers intended strings and basic argument-related false positives but not non-session provider INVALID_ARGUMENT responses.
src/tests/renderer/hooks/useAgentErrorRecovery.test.ts Verifies the hook branch in isolation but not its reachability through the production error flow.

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]
Loading

Reviews (1): Last reviewed commit: "fix(opencode): offer a fresh session whe..." | Re-trigger Greptile

Comment on lines +156 to +171
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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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

📥 Commits

Reviewing files that changed from the base of the PR and between d2085a0 and aaad055.

📒 Files selected for processing (4)
  • src/__tests__/main/parsers/error-patterns.test.ts
  • src/__tests__/renderer/hooks/useAgentErrorRecovery.test.ts
  • src/main/parsers/error-patterns.ts
  • src/renderer/hooks/agent/useAgentErrorRecovery.tsx

Comment on lines +387 to +390
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,

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 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.

Suggested change
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.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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_ARGUMENT failure, classifying it as session_not_found.
  • Extends the renderer recovery-action logic to handle session_not_found by leading with a Start New Session action.
  • Adds unit tests for the new pattern matching and for the session_not_found recovery-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.

Comment on lines +387 to +390
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,
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Error " Request contains an invalid argument"

2 participants