Skip to content

feat: detect retryable errors through error.cause chain#3

Merged
ShutovKS merged 1 commit into
devfrom
feat/error-cause
Jul 13, 2026
Merged

feat: detect retryable errors through error.cause chain#3
ShutovKS merged 1 commit into
devfrom
feat/error-cause

Conversation

@ShutovKS

Copy link
Copy Markdown
Owner

Проблема

statusCode/errorText смотрели только на error, error.data, error.response. Обёрнутые fetch-ошибки (new TypeError("fetch failed", { cause: { statusCode: 429 } })) прятали реальный код/текст в error.cause, и fallback не срабатывал.

Решение

Обе функции теперь проходят по цепочке error.cause (ограничение глубины 5, безопасно к циклам). 429/503, лежащий на один-несколько уровней глубже, снова триггерит fallback. Заодно вынесен numericField/ownStatusCode — меньше дублирования.

Тесты

Добавлены кейсы: статус на cause, вложенная цепочка cause.cause, Error с текстовым cause, циклическая ссылка (терминируется). Юнит-тесты 40/40, typecheck чист.

🤖 Generated with Claude Code

Wrapped fetch errors (e.g. `new TypeError("fetch failed", { cause: {...} })`)
carry the real status code and message on `error.cause`. statusCode and
errorText now walk the cause chain (bounded to depth 5, safe against cycles),
so a 429/503 buried one or more levels deep still triggers fallback.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 13, 2026 23:34
@ShutovKS ShutovKS merged commit bad9c4b into dev Jul 13, 2026
2 checks passed
@ShutovKS ShutovKS deleted the feat/error-cause branch July 13, 2026 23:35

Copilot AI 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.

Pull request overview

This PR improves the model-fallback plugin’s retryability detection by following error.cause chains so wrapped fetch-style errors can still trigger fallback, and adds tests to cover the new behavior.

Changes:

  • Refactors status extraction into numericField/ownStatusCode and makes statusCode() recurse through error.cause with a max depth.
  • Extends errorText() to incorporate Error.cause text (depth-limited) when the input is an Error.
  • Adds helper tests for wrapped errors, nested cause chains, and circular cause chains.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
src/plugin.ts Adds depth-limited cause traversal for status-code detection and enhances error text aggregation for Error instances.
test/index.test.ts Adds unit tests covering retryability via error.cause, nested causes, and cycle termination.
Comments suppressed due to low confidence (1)

src/plugin.ts:231

  • errorText only walks the cause chain when the value is an Error. For plain objects it falls back to JSON.stringify(error), which drops text if cause is an Error (JSON.stringify(new Error("x")) -> "{}"). That can miss retryable text buried in error.cause for non-Error wrappers. Consider handling Record values by extracting name/message (when present) and recursively appending cause text with the same depth cap.
  try {
    return JSON.stringify(error)
  } catch {
    return String(error)
  }

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

ShutovKS added a commit that referenced this pull request Jul 14, 2026
PR #3 (suppressed comment): errorText only walked `cause` for Error
instances; plain-object wrappers fell through to JSON.stringify, which
serializes a nested Error to {} and drops retryable text. Walk `cause`
explicitly for records too, with the same depth cap.

PR #2 (inline comment): the prune test only proved an expired cooldown made
the model selectable again — pruning itself was unobservable. Expose active
cooldowns as `cooling` in model_fallback_control status and assert the
expired entries are gone.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
@ShutovKS ShutovKS mentioned this pull request Jul 14, 2026
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.

2 participants