Repository navigation
Description
Description
When a file containing sensitive/filtered content is read and included in the LLM prompt, the provider API returns an HTTP 500 error (content moderation). opencode's retry
logic treats all 5xx errors as transient and retryable, and the retry policy has no maximum retry limit, causing opencode to hang indefinitely with exponential backoff.
Root Cause
- packages/opencode/src/session/retry.ts:57-63 — All 5xx are unconditionally retryable:
if (MessageV2.APIError.isInstance(error)) {
const status = error.data.statusCode
// 5xx errors are transient server failures and should always be retried
if (!error.data.isRetryable && !(status !== undefined && status >= 500)) return undefined
// ...
return error.data.message
}
-
packages/opencode/src/session/retry.ts:106-123 — policy() has no max attempts. When retryable() returns a truthy string, the schedule retries forever.
-
packages/opencode/src/session/retry.ts:47 — When response headers are present (typical for API 500 errors), delay grows exponentially with a cap at RETRY_MAX_DELAY
(~24.8 days):
attempt 1: 2s
attempt 2: 4s
attempt 3: 8s
attempt 4: 16s
attempt 5: 32s
attempt 6: 64s
...
attempt 15: ~33s
attempt 16: ~66s
...
attempt 20+: capped at ~24.8 days per attempt
Expected Behavior
- opencode should have a maximum retry limit (e.g., 3-5 attempts for 5xx errors)
- Certain 500 errors are not transient — content moderation/filtering 500s will never succeed on retry
- The retry policy should eventually give up and surface the error to the user
Suggested Fix
Option A — Add max retry count to the retry policy:
In packages/opencode/src/session/retry.ts, add a max attempt check in the policy() function:
const MAX_RETRY_ATTEMPTS = 5
export function policy(opts: { ... }) {
return Schedule.fromStepWithMetadata(
Effect.succeed((meta: Schedule.InputMetadata) => {
if (meta.attempt > MAX_RETRY_ATTEMPTS) return Cause.done(meta.attempt) // stop after N retries
const error = opts.parse(meta.input)
const message = retryable(error)
if (!message) return Cause.done(meta.attempt)
// ... existing retry delay logic
}),
)
}
Option B — Detect non-transient 500 patterns:
In the retryable() function, check the response body for content-moderation-related error messages and return undefined (not retryable) for those:
if (status === 500) {
const body = error.data.responseBody ?? ''
// Content moderation errors are permanent, not transient
if (/content.?filter|sensitive|moderation|content.?policy/i.test(body)) {
return undefined
}
}
Environment
- opencode version: 1.14.29
- Platform: Windows 11
Plugins
No response
OpenCode version
1.14.29
Steps to reproduce
- Open a project containing a file with content that triggers the LLM provider's content filter
- Ask opencode to read or process that file
- The LLM API returns HTTP 500 (content moderation/sensitive word filter)
- opencode enters an infinite retry loop
- The ACP prompt() call never resolves
- The host application (TUI, etc.) hangs indefinitely
Screenshot and/or share link
No response
Operating System
win 11
Terminal
windows Terminal
Activity
github-actions commentedon Apr 30, 2026
This issue doesn't fully meet our contributing guidelines.
What needs to be fixed:
Please edit this issue to address the above within 2 hours, or it will be automatically closed.
This issue might be a duplicate of existing issues. Please check:
If you believe this was flagged incorrectly, please let a maintainer know.
github-actions commentedon Apr 30, 2026
This issue has been automatically closed because it was not updated to meet our contributing guidelines within the 2-hour window.
Feel free to open a new issue that follows our issue templates.