Skip to content

fix(server): keep OpenCode timeouts from crashing the error formatter - #12469

Open
tachytelicdetonation wants to merge 4 commits into
pingdotgg:mainfrom
tachytelicdetonation:fix/opencode-runtime-error-detail
Open

tachytelicdetonation wants to merge 4 commits into
pingdotgg:mainfrom
tachytelicdetonation:fix/opencode-runtime-error-detail

Conversation

@tachytelicdetonation

@tachytelicdetonation tachytelicdetonation commented Sep 18, 2026 •

Copy link
Copy Markdown

What Changed

openCodeRuntimeErrorDetail in apps/server/src/provider/opencodeRuntime.ts assumed every Error has a string message and called .trim() on it. It now only trims message when it is a string and otherwise falls through to the existing object and String(cause) fallbacks.

Tests added:

  • opencodeRuntime.errorDetail.test.ts: the formatter returns a string for an Error with an undefined or non-string message, for new Cause.TimeoutError() built without a message, and keeps its existing outputs for runtime errors, trimmed messages, SDK response shapes, and primitives.
  • OpenCodeAdapter.test.ts: a prompt that times out whose cleanup abort then fails with a message-less error still emits the "cleanup abort did not complete" warning with a readable detail and still schedules prompt-admission recovery.
  • OpenCodeAdapter.test.ts: the issue's full sequence, a prompt timeout followed by a cleanup abort timeout, emits the same warning carrying the abort timeout and still schedules recovery. The vendored Effect gives that timeout a message, so this test passes before and after the formatter fix; it pins the recovery path.

Why

Fixes #12456.

When OpenCode is unresponsive, sendTurn times out session.promptAsync and then the cleanup session.abort. Formatting the abort cause ran before schedulePromptAdmissionRecovery. Effect's TimeoutError can be constructed without a message, so the formatter threw TypeError: Cannot read properties of undefined (reading 'trim'). That defect replaced the useful timeout warning, recovery never ran, and every later message on the thread failed the same way. This follows the suggested fix in the triage and mirrors the guard already used by legacySetupFailureDescription in apps/server/src/ws.ts.

Verification on this branch:

  • The four new tests fail on main with the TypeError from the issue and pass with the fix.
  • OpenCodeAdapter.test.ts and all opencodeRuntime*.test.ts files: 177 passed.
  • apps/server typecheck, lint, and vp fmt --check are clean.

Checklist

  • This PR is small and focused
  • I explained what changed and why
  • I included before/after screenshots for any UI changes (no UI changes)
  • I included a video for animation/interaction changes (not applicable)

Work done with Claude Fable 5.1 in Claude Code.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved error handling when runtime errors have missing, empty, or non-string messages.
    • Cleanup failures now produce clear warnings and continue recovery without causing additional failures.
    • Runtime failures now provide safer, more informative details across timeout and SDK response scenarios.
  • Tests

    • Added coverage for timeout errors, SDK response formats, fallback error details, cleanup abort failures, and recovery behavior.

`openCodeRuntimeErrorDetail` called `.trim()` on `cause.message` for every
`Error`, but Effect's `TimeoutError` can be constructed without a message.
When an unresponsive OpenCode timed out the prompt and its cleanup abort,
formatting the abort cause threw a TypeError, which replaced the useful
timeout warning with a defect and skipped prompt-admission recovery.

Only trim `message` when it is a string, and fall through to the existing
object and `String(cause)` fallbacks otherwise. Covered by unit tests for
the formatter and an adapter test for the prompt-timeout cleanup path.

Fixes pingdotgg#12456

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions github-actions Bot added vouch:unvouched PR author is not yet trusted in the VOUCHED list. size:XS 0-9 changed lines (additions + deletions). labels Sep 18, 2026
macroscopeapp[bot]
macroscopeapp Bot previously approved these changes Sep 18, 2026
@macroscopeapp

macroscopeapp Bot commented Sep 18, 2026 •

Copy link
Copy Markdown
Contributor

Approvability

Verdict: Would Approve

Macroscope's review found this PR approvable — This is a small, focused server bug fix with targeted coverage for message-less timeout errors and cleanup recovery, while normal formatting behavior remains unchanged. A separate unresolved High-severity concern remains about reading a potentially unstable message getter twice, which independently blocks approval until resolved.

Not approved because:

  • 1 blocking correctness issue found at or above your repo's Minimum Blocking Severity

Adjust the Minimum Blocking Severity for this repo — including turning it Off — in Settings. You can add or adjust custom eligibility rules. Learn more.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Sep 18, 2026 •

Copy link
Copy Markdown

Review Change StackReview Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Advanced

Run ID: 71fe4a91-d7d5-4d84-98cd-ac6a7dd4f007

📥 Commits

Reviewing files that changed from the base of the PR and between 3b7d255 and 7c67a7d.

📒 Files selected for processing (1)
  • apps/server/src/provider/Layers/OpenCodeAdapter.test.ts

Included review availability: Your plan provides up to 10 included reviews per hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

The formatter now handles missing or non-string error messages. Tests cover fallback formatting and prompt cleanup recovery when the prompt or cleanup abort times out.

Changes

OpenCode timeout recovery

Layer / File(s) Summary
Safe runtime error formatting
apps/server/src/provider/opencodeRuntime.ts, apps/server/src/provider/opencodeRuntime.errorDetail.test.ts
openCodeRuntimeErrorDetail checks that Error.message is a string before trimming it. Tests cover timeout errors, structured SDK responses, empty messages, and primitive causes.
Prompt cleanup recovery
apps/server/src/provider/Layers/OpenCodeAdapter.test.ts
Adapter tests cover cleanup aborts with undefined error messages and abort timeouts. They verify warning details, recovery polling, failed sendTurn completion, and cleanup release.

Priority: ➖ Normal

Estimated code review effort: 2 (Simple) | ~10 minutes

Change: Bug fix · Severity of issue fixed: Medium

Suggested reviewers: t3dotgg

Merge Risk: ⚪ Minimal · up to 7c67a

The timeout recovery change safely preserves diagnostic details and recovery scheduling for the covered error shapes. No actionable merge-blocking risk remains.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 50.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 2 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed The change satisfies the coding requirements in #12456. openCodeRuntimeErrorDetail trims Error.message only when it is a string and preserves structured-cause and String(cause) fallbacks. Format…
Out of Scope Changes check ✅ Passed The changes are limited to the OpenCode runtime formatter, formatter tests, and an OpenCode adapter regression test. Each change supports the timeout diagnostic and session recovery requirements in #1…
Title check ✅ Passed The title clearly identifies the server fix for OpenCode timeout error formatting. It is concise and matches the main change.
Description check ✅ Passed The description explains what changed, why it changed, test coverage, verification results, and checklist status. It is focused and sufficiently complete. The UI section is omitted because the pull re…
  • Fix all pre-merge checks with AI
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create a new PR

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

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@github-actions github-actions Bot added size:S 10-29 changed lines (additions + deletions). and removed size:XS 0-9 changed lines (additions + deletions). labels Sep 18, 2026
The issue's repro is a prompt that times out whose cleanup abort then
times out as well. The existing test reproduces the crashing error shape
directly; this one drives the full sequence through the adapter and
checks the warning carries the abort timeout and recovery still runs.
The vendored Effect supplies a timeout message, so this sequence passes
with or without the formatter fix; it pins the recovery path.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@juliusmarminge juliusmarminge added the macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews label Oct 1, 2026 — with ChatGPT Codex Connector
@macroscopeapp
macroscopeapp Bot dismissed their stale review October 1, 2026 04:05

Dismissing prior approval to re-evaluate 7c67a7d

Comment on lines +126 to +129
if (cause instanceof Error && typeof cause.message === "string") {
const message = cause.message.trim();
if (message.length > 0) return message;
}

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.

🟠 High provider/opencodeRuntime.ts:126

This formatter throws instead of returning a diagnostic when an Error subclass changes or throws on its second message getter access. The typeof check and subsequent .trim() read cause.message separately; read it once into a local before checking and trimming.

Suggested change
if (cause instanceof Error && typeof cause.message === "string") {
const message = cause.message.trim();
if (message.length > 0) return message;
}
if (cause instanceof Error) {
const message = cause.message;
if (typeof message === "string") {
const trimmedMessage = message.trim();
if (trimmedMessage.length > 0) return trimmedMessage;
}
}
🤖 Copy this AI Prompt to have your agent fix this:
In file @apps/server/src/provider/opencodeRuntime.ts around lines 126-129:

This formatter throws instead of returning a diagnostic when an `Error` subclass changes or throws on its second `message` getter access. The `typeof` check and subsequent `.trim()` read `cause.message` separately; read it once into a local before checking and trimming.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

macroscope-review Opt PRs made by unvouched contributors in for Macroscope review. Vouched contributors auto-reviews size:S 10-29 changed lines (additions + deletions). vouch:unvouched PR author is not yet trusted in the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: OpenCode timeout error formatting crashes and skips session recovery

2 participants