Skip to content

feat: channel retryable statu codes, close #1803#1807

Merged
looplj merged 1 commit into
unstablefrom
dev-tmp
Jun 10, 2026
Merged

feat: channel retryable statu codes, close #1803#1807
looplj merged 1 commit into
unstablefrom
dev-tmp

Conversation

@looplj

@looplj looplj commented Jun 10, 2026

Copy link
Copy Markdown
Owner

@devin-ai-integration devin-ai-integration Bot 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.

Devin Review found 1 potential issue.

View 3 additional findings in Devin Review.

Open in Devin Review

Comment on lines 610 to +614
return true
}

// otherwise check if the error is retryable.
return isRetryableError(err)
// otherwise check if the error is retryable for the current channel.
return isRetryableErrorForChannel(err, p.state.CurrentCandidate.Channel)

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.

🚩 CanRetry now checks channel-specific codes only for the last-model fallback path

In outbound.go:608-614, the channel-specific retryable status codes are only consulted in the final fallback of CanRetry — after checking for circuit breaker, queue errors, empty responses, 429 with/without Retry-After, and remaining models. This means a channel-configured code like 400 will trigger same-channel retry only when there are no more models to try in the candidate. If the candidate has multiple models, ANY error (including non-retryable ones) at line 609 will advance to the next model regardless. This is the pre-existing design — the PR correctly extends only the final fallback path — but reviewers should confirm this is the intended semantics.

(Refers to lines 608-614)

Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.

@greptile-apps

greptile-apps Bot commented Jun 10, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds per-channel configurable retryable HTTP status codes, allowing users to specify additional 4xx/5xx codes beyond the default 429 and 5xx retry behavior. The feature spans the full stack — GraphQL schema, Go backend (channel settings object, validation/normalization, retry logic), and a React frontend form field with real-time input parsing.

  • Backend: NormalizeRetryableStatusCodes validates, deduplicates, and sorts the provided codes (restricted to 400–599) and is wired into both createChannel and UpdateChannel. isRetryableErrorForChannel in retry.go checks the channel's custom codes after the default retryable check.
  • Frontend: A new text input in the channel dialog parses comma/space-separated integers with client-side validation mirroring the backend constraint, and the state is correctly reset on dialog close.
  • Incidental cleanup: The audio transcription/translation loop in audio_outbound.go is replaced with maps.Copy, and go.sum is updated to newer patch versions of several golang.org/x dependencies.

Confidence Score: 5/5

Safe to merge — all code paths that accept or use channel settings correctly validate and apply the new field.

The feature is well-scoped: validation runs in both create and update paths, the retry logic only adds new retryable codes on top of the existing defaults without removing any, and the frontend input parsing mirrors the backend constraints. Tests cover the key edge cases (nil channel, nil settings, default fallthrough, configured match, and non-match). No existing behavior is changed for channels that don't configure the new field.

No files require special attention.

Important Files Changed

Filename Overview
internal/server/orchestrator/retry.go New isRetryableErrorForChannel correctly chains default retryability check with channel-specific codes; nil guards are ordered correctly for safe embedded pointer access.
internal/server/biz/channel.go NormalizeRetryableStatusCodes validates, sorts, and deduplicates codes; called in both createChannel and UpdateChannel paths; bulk operations route through createChannel so they are also covered.
frontend/src/features/channels/components/channels-action-dialog.tsx Input state managed outside react-hook-form (matching the pattern used for passThroughUserAgent/passThroughBody); parse-then-validate pattern on submit correctly gates the mutation.
internal/server/orchestrator/outbound.go CanRetry now delegates to isRetryableErrorForChannel, passing the current channel; the change is minimal and correct.
internal/objects/channel.go Added RetryableStatusCodes []int with omitempty so empty/nil lists are stored identically and round-trip correctly.
llm/transformer/openai/audio_outbound.go Replaced manual map copy loop with maps.Copy; semantics are identical including nil-src safety.
internal/server/orchestrator/retry_test.go Good coverage of the new function: nil channel, nil settings, default retryable passthrough, configured-code match, and non-configured-code non-match.
frontend/src/features/channels/utils/merge.ts Added retryableStatusCodes to the merge output with fallback [], consistent with how other array fields are treated.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A[Outbound request fails] --> B{CanRetry?}
    B --> C[isRetryableErrorForChannel]
    C --> D{err == nil?}
    D -- yes --> E[return false]
    D -- no --> F[ExtractStatusCodeFromError]
    F --> G{IsHTTPStatusCodeRetryable?\n429 or 5xx}
    G -- yes --> H[return true]
    G -- no --> I{ch == nil OR ch.Channel == nil OR ch.Settings == nil?}
    I -- yes --> J[return false]
    I -- no --> K{slices.Contains channel RetryableStatusCodes?}
    K -- yes --> L[return true]
    K -- no --> M[return false]

    N[Channel settings saved] --> O[NormalizeRetryableStatusCodes]
    O --> P{codes empty?}
    P -- yes --> Q[no-op, return nil]
    P -- no --> R{any code outside 400-599?}
    R -- yes --> S[return error]
    R -- no --> T[sort + compact dedup]
    T --> U[assign back to settings]
Loading

Reviews (2): Last reviewed commit: "feat: channel retryable statu codes, clo..." | Re-trigger Greptile

Comment on lines +108 to +111
const tokens = value
.split(/[,\s]+/)
.map((token) => token.trim())
.filter(Boolean);

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.

P2 The .map((token) => token.trim()) call is redundant here: the split regex /[,\s]+/ already consumes all surrounding whitespace, so tokens can never have leading/trailing spaces after the split. The .filter(Boolean) already handles any empty strings at the boundaries. Removing the .trim() keeps the code consistent with the actual contract of the regex.

Suggested change
const tokens = value
.split(/[,\s]+/)
.map((token) => token.trim())
.filter(Boolean);
const tokens = value
.split(/[,\s]+/)
.filter(Boolean);

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Comment thread frontend/src/locales/en/channels.json Outdated
"channels.dialogs.bodyPassThrough.warning": "When enabled, both the request and response will be passed through only when the client request format matches the channel format. Prompt injection and prompt protection will not take effect.",
"channels.dialogs.retryableStatusCodes.label": "Retry Status Codes",
"channels.dialogs.retryableStatusCodes.placeholder": "400, 401, 403",
"channels.dialogs.retryableStatusCodes.tooltip": "Additional HTTP error status codes to retry for this channel. Empty keeps the default 429 and 5xx behavior.",

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.

P2 The tooltip says "Empty keeps the default 429 and 5xx behavior", which could imply that a non-empty list replaces the defaults. In reality, the custom codes are always additive — 429 and 5xx remain retryable regardless of what is configured here. Clarifying this prevents users from thinking they can disable 5xx retries by filling the field.

Suggested change
"channels.dialogs.retryableStatusCodes.tooltip": "Additional HTTP error status codes to retry for this channel. Empty keeps the default 429 and 5xx behavior.",
"channels.dialogs.retryableStatusCodes.tooltip": "Additional HTTP error status codes to retry for this channel. These are added on top of the default 429 and 5xx behavior, which always applies.",

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@looplj looplj merged commit 21284f2 into unstable Jun 10, 2026
4 checks passed
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.

1 participant