Skip to content

fix(provider-endpoints): close #742 endpoint update regressions#746

Merged
ding113 merged 7 commits intodevfrom
fix/issue-742-provider-endpoint-update
Feb 10, 2026
Merged

fix(provider-endpoints): close #742 endpoint update regressions#746
ding113 merged 7 commits intodevfrom
fix/issue-742-provider-endpoint-update

Conversation

@ding113
Copy link
Owner

@ding113 ding113 commented Feb 9, 2026

Summary

Fixes deterministic endpoint edit outcomes, prevents unintended endpoint soft-deletion during provider sync, adds a partial unique index on provider_endpoints to exclude soft-deleted rows, and introduces a dry-run repair/diagnostic tool for affected data.

Problem

When editing a provider endpoint URL in the settings UI, three regressions were observed (reported in #742):

  1. Edits silently fail: The UI shows a success toast, but the URL change does not persist. The raw response reveals a unique constraint violation (duplicate key value) that was swallowed by a generic error handler.
  2. Sibling endpoints disappear: Editing one endpoint (or the parent provider) causes other unrelated endpoints under the same vendor to be soft-deleted by the syncProviderEndpointOnProviderEdit logic.
  3. Confusing "dual JSON" response: Users inspecting network responses saw two JSON-like structures (0:... and 1:...), which is actually the expected React Flight protocol but was undocumented.

Root cause: The unique index uniq_provider_endpoints_vendor_type_url included soft-deleted rows, so reviving or re-creating an endpoint for the same (vendor_id, provider_type, url) tuple would conflict with a zombie row. Additionally, the sync logic was too aggressive in soft-deleting sibling endpoints, and the UI error path could show both a success and error toast simultaneously.

Related Issues:

Solution

1. Partial unique index (migration 0064)

Replace the full unique index on (vendor_id, provider_type, url) with a partial unique index that only covers rows where deleted_at IS NULL. This allows soft-deleted rows to coexist without blocking active endpoint operations.

2. Deterministic endpoint edit with read-after-write verification

updateProviderEndpoint() now:

  • Builds an "expected fields" snapshot before the write
  • Performs a read-after-write consistency check to confirm the update took effect
  • On unique constraint violation, re-reads to check if the existing row already matches (idempotent), otherwise throws a typed PROVIDER_ENDPOINT_CONFLICT error

3. Conservative sync strategy

syncProviderEndpointOnProviderEdit() now preserves sibling endpoints when keepPreviousWhenReferenced is true. Two new code paths return kept-previous-and-created-next / kept-previous-and-revived-next / kept-previous-and-kept-next instead of soft-deleting the previous endpoint.

4. UI toast fix

Restructured the success/error branching in EditEndpointDialog and AddEndpointButton to use early-return on success, ensuring only one toast is ever displayed. Added void prefix on invalidateQueries promises to suppress unhandled promise warnings.

5. Conflict error surfacing

New isDirectEndpointEditConflictError() detector in the actions layer maps unique violation errors to a user-facing CONFLICT error code with a clear message.

6. Error rules sync fix

syncDefaultErrorRules() now correctly persists overrideResponse and overrideStatusCode fields that were previously dropped during upsert.

7. Repair & diagnostic tooling

backfillProviderEndpointsFromProviders() is enhanced with:

  • dry-run / apply modes
  • Risk classification: deterministic-safe-insert, historical-ambiguous-report-only, invalid-provider-row
  • Sample collection with configurable limits
  • Vendor scope filtering
  • Full diagnostic report structure

Changes

Core Changes

File Description
src/drizzle/schema.ts Add .where(deleted_at IS NULL) clause to unique index
drizzle/0064_stale_vertigo.sql Migration: drop and recreate partial unique index
src/repository/provider-endpoints.ts Deterministic edit with read-after-write, conflict detection, conservative sync, enhanced backfill with dry-run
src/actions/provider-endpoints.ts Conflict error detection and mapping to CONFLICT error code
src/repository/error-rules.ts Sync overrideResponse/overrideStatusCode in default error rules

UI Changes

File Description
src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx Fix toast double-fire with early-return pattern, void promise prefixes
src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx Wrap onSuccess callback in try-catch, void promise prefixes

Documentation

File Description
docs/troubleshooting/provider-endpoint-742.md Troubleshooting guide: Flight protocol explanation, verification steps, dry-run repair

Tests

File Description
tests/unit/repository/provider-endpoint-742-direct-edit.test.ts Unit tests for deterministic edit and conflict detection
tests/unit/actions/provider-endpoints.test.ts Unit tests for conflict error mapping in actions layer
tests/unit/settings/providers/provider-endpoints-table.test.tsx Unit tests for EditEndpointDialog toast behavior
tests/unit/repository/provider-endpoint-sync-helper.test.ts Updated sync helper tests for conservative keep logic
tests/unit/repository/provider-endpoints.test.ts Updated backfill tests
tests/integration/provider-endpoint-regression-742.test.ts Integration regression test for issue #742
tests/integration/provider-endpoint-index-and-repair.test.ts Integration test for partial index behavior and repair dry-run
tests/integration/provider-endpoint-sync-race.test.ts Minor updates to existing race condition test

Configuration

File Description
vitest.integration.config.ts Include new integration test files

Breaking Changes

The backfillProviderEndpointsFromProviders() function signature now uses overloads: calling without arguments returns the original summary shape, but calling with an options object returns the full report. Existing callers with no arguments are unaffected.

Database migration required: Migration 0064 drops and recreates the unique index as a partial index. This is a non-destructive DDL change but requires bun run db:migrate or AUTO_MIGRATE=true.

Testing

Automated Tests

  • Unit tests for deterministic endpoint edit (conflict, consistency check)
  • Unit tests for conflict error detection in actions layer
  • Unit tests for EditEndpointDialog toast behavior
  • Unit tests for conservative sync (keep previous when referenced)
  • Integration test: regression bug: 修改端点不生效以及导致其他端点消失 #742 scenarios
  • Integration test: partial index behavior and repair dry-run diagnostics
  • Integration test: sync race condition (updated)

Manual Testing

  1. Edit an endpoint URL to a value that already exists for the same vendor/type
    • Expected: Error toast with conflict message, not success
  2. Edit an endpoint URL to a new valid value
    • Expected: Success toast, value persists after refresh
  3. Edit a Provider's URL while sibling endpoints exist
    • Expected: Provider URL updates, sibling endpoints remain visible and active
  4. Run bunx vitest run tests/integration/provider-endpoint-index-and-repair.test.ts -t "dry-run" to verify repair diagnostics

Checklist

  • Code follows project conventions
  • Self-review completed
  • Tests pass locally
  • Migration generated via bun run db:generate
  • Documentation updated

Description enhanced by Claude AI

Greptile Overview

Greptile Summary

This PR fixes provider endpoint edit regressions by (1) changing the provider_endpoints uniqueness constraint to exclude soft-deleted rows, (2) making updateProviderEndpoint() deterministic via expected-field snapshots + read-after-write verification + typed conflict errors, and (3) making syncProviderEndpointOnProviderEdit() more conservative to avoid soft-deleting unrelated sibling endpoints during provider edits. It also updates UI flows to avoid showing both success and error toasts on endpoint add/edit, persists additional fields during default error-rule sync, and adds integration/unit coverage plus a dry-run/apply diagnostic backfill tool.

Key areas affected: database uniqueness semantics (drizzle/0065_*, src/drizzle/schema.ts), endpoint repository logic (src/repository/provider-endpoints.ts), server action error mapping (src/actions/provider-endpoints.ts), and settings UI cache invalidation/toast behavior (src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx).

Confidence Score: 2/5

  • Not safe to merge until a few correctness issues are addressed.
  • Core logic changes look directionally correct, but there are still concrete pre-merge issues: an integration test added in this PR is not included in the integration test config, some react-query invalidations can still produce unhandled promise rejections, and the new backfill overload’s runtime return-shape behavior may not match the documented contract for callers.
  • src/repository/provider-endpoints.ts, src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx, vitest.integration.config.ts

Important Files Changed

Filename Overview
drizzle/0065_stale_vertigo.sql Adds migration to drop and recreate uniq_provider_endpoints_vendor_type_url as a partial unique index excluding soft-deleted rows.
src/actions/provider-endpoints.ts Adds conflict error detection for endpoint edits and maps to CONFLICT errorCode; current detector remains over-broad per prior thread.
src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx Fixes toast branching via early returns and adds promise catch on some invalidateQueries; other invalidations still unhandled.
src/drizzle/schema.ts Changes provider_endpoints unique index to be partial (deleted_at IS NULL) to allow soft-deleted duplicates.
src/repository/error-rules.ts Fixes syncDefaultErrorRules upsert to persist overrideResponse and overrideStatusCode fields.
src/repository/provider-endpoints.ts Implements deterministic endpoint update with read-after-write checks, conservative provider-edit sync, and enhanced backfill with dry-run/report; overload return semantics may not match runtime.
tests/integration/provider-endpoint-index-and-repair.test.ts Adds integration tests for partial unique index behavior and dry-run/apply repair reporting; currently not included in integration config.
vitest.integration.config.ts Integration test include list updated but misses newly added provider-endpoint-index-and-repair test, so it won’t run.

Sequence Diagram

sequenceDiagram
  participant UI as Settings UI
  participant Action as Server Action
  participant Repo as Repository
  participant DB as Postgres

  UI->>Action: editProviderEndpoint({endpointId, fields...})
  Action->>Repo: updateProviderEndpoint(endpointId, payload)
  Repo->>DB: UPDATE provider_endpoints ... RETURNING
  alt Update applied and matches expected
    DB-->>Repo: updated row
    Repo-->>Action: ProviderEndpoint
    Action-->>UI: {ok:true, endpoint}
    UI->>UI: toast.success + close dialog
    UI->>UI: invalidateQueries(["provider-endpoints", ...])
  else Unique violation (23505)
    DB-->>Repo: error 23505
    Repo->>DB: SELECT endpoint by id (read-after-write)
    alt Row now matches expected (idempotent)
      DB-->>Repo: matching row
      Repo-->>Action: ProviderEndpoint
      Action-->>UI: {ok:true, endpoint}
    else Row does not match
      Repo-->>Action: throw {code: PROVIDER_ENDPOINT_CONFLICT}
      Action-->>UI: {ok:false, errorCode: CONFLICT}
      UI->>UI: toast.error(conflict)
    end
  else Write/read inconsistency
    Repo-->>Action: throw {code: PROVIDER_ENDPOINT_WRITE_READ_INCONSISTENCY}
    Action-->>UI: {ok:false, errorCode: UPDATE_FAILED}
  end

  Note over Repo,DB: Migration 0065 makes unique index partial where deleted_at IS NULL
Loading

…Rules

The update path for isDefault=true rules was missing overrideResponse and
overrideStatusCode fields, causing code-level changes to default error
override messages to never propagate to the database on restart.
- Fix UI consistency: ensure operations return single authoritative result (success/error)
- Fix sync logic: prevent aggressive soft-deletion of sibling endpoints
- Fix unique constraints: align constraints to ignore soft-deleted rows
- Add comprehensive regression tests for direct edit and sync scenarios
- Add data repair tool and documentation for troubleshooting
@coderabbitai
Copy link

coderabbitai bot commented Feb 9, 2026

📝 Walkthrough

Walkthrough

新增数据库迁移与部分索引更改;在仓库层引入冲突检测、写后读一致性与回填/修复流程;前端对端点增删改流程的异步与错误处理进行调整;添加大量单元与集成测试以及故障排查文档以覆盖 #742 场景。

Changes

Cohort / File(s) Summary
文档与迁移
docs/troubleshooting/provider-endpoint-742.md, drizzle/0065_stale_vertigo.sql, drizzle/meta/0065_snapshot.json, drizzle/meta/_journal.json
添加故障排查文档与数据库迁移及模式快照:将 provider_endpoints 的唯一索引改为仅对未删除行生效(deleted_at IS NULL)。
数据库架构定义
src/drizzle/schema.ts
将 provider_endpoints 表上的唯一索引 uniq_provider_endpoints_vendor_type_url 修改为带 .where(deletedAt IS NULL) 的部分索引。
仓库层(provider-endpoints)
src/repository/provider-endpoints.ts
新增冲突目标/where 常量、写后读一致性检查、computeVendorKey、可编辑字段校验、丰富的 backfill/repair 流与导出类型;引入冲突/一致性错误码与相关 helpers。
错误处理与动作
src/actions/provider-endpoints.ts, src/repository/error-rules.ts
新增 isDirectEndpointEditConflictError 冲突识别;editProviderEndpoint 在冲突时返回明确冲突错误码并本地化信息;syncDefaultErrorRules 在更新默认规则时同步 override 字段。
前端组件调整
src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx, src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx
使 query.invalidate 为非阻塞(void / .catch),将 onSuccess 包装 try/catch,分离成功/失败路径以避免重复或错时的 toast 通知。
测试 — 集成
tests/integration/provider-endpoint-index-and-repair.test.ts, tests/integration/provider-endpoint-regression-742.test.ts, tests/integration/provider-endpoint-sync-race.test.ts, vitest.integration.config.ts
新增/扩展集成测试覆盖唯一性约束、回填干跑/应用、端点更新持久性与兄弟端点行为,并将回归测试加入集成套件。
测试 — 单元
tests/unit/actions/provider-endpoints.test.ts, tests/unit/repository/provider-endpoint-742-direct-edit.test.ts, tests/unit/repository/provider-endpoint-sync-helper.test.ts, tests/unit/repository/provider-endpoints.test.ts, tests/unit/settings/providers/provider-endpoints-table.test.tsx
新增单元测试覆盖直接编辑冲突识别、读后读一致性模拟、onConflictDoNothing 用法断言及 UI 成功/失败 toast 隔离。
小修与依赖变更
src/app/[locale]/dashboard/logs/_hooks/use-lazy-filter-options.ts, src/lib/security/api-key-vacuum-filter.ts, src/lib/provider-endpoint-error-codes.ts
调整 effect 依赖以包含 fetcher、改动 import 顺序,新增两个导出错误码常量用于端点冲突与写读不一致情形。

预期代码审查工作量

🎯 4 (Complex) | ⏱️ ~60 分钟

可能相关的 PR

🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 18.18% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed 标题准确反映了 PR 的核心目标:修复 #742 中提报的端点更新回归问题,包括编辑失败、兄弟端点消失等。
Description check ✅ Passed 描述清晰地解释了问题、根本原因以及完整的解决方案,包含具体的代码变更和测试计划,与变更集高度相关。
Linked Issues check ✅ Passed PR 完整地解决了 #742 的三个回归问题:(1) 确定性端点编辑与读写验证防止更新失败,(2) 保守的同步策略防止兄弟端点消失,(3) 文档说明 React Flight 协议的双 JSON 响应。
Out of Scope Changes check ✅ Passed 所有变更都在 #742 范围内:数据库迁移、确定性编辑、UI Toast 修复、冲突检测。轻微的范围外变更包括 use-lazy-filter-options.ts 和 api-key-vacuum-filter.ts 的改动,但这些是必要的修复,已在描述中列出。

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/issue-742-provider-endpoint-update

No actionable comments were generated in the recent review. 🎉

🧹 Recent nitpick comments
src/repository/provider-endpoints.ts (5)

1187-1191: keepPreviousWhenReferenced 语义与实际行为存在偏差。

keepPreviousWhenReferencedtrue(默认值)时,即使 previous endpoint 被其他 provider 引用,也不会执行 soft-delete,而是保留。变量名暗示"仅在被引用时保留",但实际行为是"永不 soft-delete"。建议重命名为更贴切的名称(如 conservativeSyncneverSoftDeletePrevious),以减少后续维护者的困惑。

Also applies to: 1230-1234


1398-1411: options 参数时 mode 默认为 "apply" 可能导致误操作。

调用 backfillProviderEndpointsFromProviders({ vendorIds: [1] }) 时,未显式指定 mode 将直接执行写入。对于一个诊断/修复工具,默认为 "dry-run" 更安全,可以避免运维人员在不了解默认值的情况下意外修改数据。

建议将 options 重载的默认模式改为 dry-run
-  const mode = options?.mode ?? "apply";
+  const mode = options?.mode ?? (options === undefined ? "apply" : "dry-run");

这样无参调用保持向后兼容("apply"),而带 options 调用默认安全("dry-run")。


1739-1743: 空 payload 判断基于 payload 而非 expectedEditableFields,两者可能不一致。

当调用方传入 { url: undefined } 时,Object.keys(payload).length 为 1(不走短路),但 pickEditableFieldExpectations 生成的结果为空对象,导致仅更新 updatedAt 并执行一次无意义的写操作。建议统一判断基准:

建议基于 expectedEditableFields 判断是否为空
  const expectedEditableFields = pickEditableFieldExpectations(payload);

- if (Object.keys(payload).length === 0) {
+ if (Object.keys(expectedEditableFields).length === 0) {
    return findActiveProviderEndpointById(endpointId);
  }

209-226: providerEndpointSelectFields 仅在 updateProviderEndpoint 中使用,其余查询仍内联字段列表。

文件中多处查询(如 findProviderEndpointByIdfindProviderEndpointsByVendorAndType 等)仍重复列出相同字段。可以考虑统一使用此常量来减少重复,提升一致性。这不影响正确性,可后续统一处理。


1524-1570: 回填诊断工具在 active endpoint 扫描阶段可能产生较大 I/O 开销。

scopedVendorIds 为空(全量扫描)时,Phase 2(Lines 1527-1570)和 Phase 3(Lines 1579-1622)分别对 provider_endpoints 表做全表分页扫描。对于大规模数据集,建议在文档或日志中提示预估耗时,或考虑在 candidate 数量较少时改用 IN 查询直接按 key 匹配,避免全表扫描。


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 and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @ding113, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request addresses several regressions related to provider endpoint management, significantly enhancing the system's reliability and user experience. It ensures that endpoint updates are deterministic and consistent, provides clear feedback for conflicts, and prevents accidental data loss during provider synchronization. Additionally, a new diagnostic and repair tool has been added to help identify and resolve historical data inconsistencies.

Highlights

  • Improved Endpoint Edit Reliability: Implemented read-after-write consistency checks and specific error handling for direct endpoint edits, ensuring updates persist correctly and conflicts are clearly communicated to the user.
  • Conservative Provider Sync Logic: Revised the provider synchronization mechanism to prevent unintended soft-deletion of sibling endpoints when a provider's URL is updated, preserving existing configurations.
  • Enhanced Troubleshooting and Data Repair: Introduced a comprehensive troubleshooting guide and a robust backfill utility with dry-run diagnostics to identify and safely repair missing provider endpoints.
Changelog
  • docs/troubleshooting/provider-endpoint-742.md
    • Added a new troubleshooting guide for provider endpoint issues.
    • Includes explanations for "Dual JSON" responses (React Flight Protocol).
    • Details root causes and resolutions for "Endpoint Updates Not Persisting" and "Sibling Endpoints Disappearing".
    • Provides verification steps and data repair instructions.
  • drizzle/0064_stale_vertigo.sql
    • Modified the unique index uniq_provider_endpoints_vendor_type_url on provider_endpoints to apply only to active records (WHERE "provider_endpoints"."deleted_at" IS NULL).
  • drizzle/meta/0064_snapshot.json
    • Added a new Drizzle ORM snapshot reflecting the updated database schema.
  • drizzle/meta/_journal.json
    • Updated the Drizzle migration journal to include the new schema migration.
  • src/actions/provider-endpoints.ts
    • Added isDirectEndpointEditConflictError to identify endpoint conflict errors.
    • Updated editProviderEndpoint to return a specific CONFLICT error code and a localized message ("端点 URL 与同供应商类型下的其他端点冲突") on unique constraint violations.
  • src/app/[locale]/settings/providers/_components/forms/provider-form/index.tsx
    • Modified queryClient.invalidateQueries calls to void to prevent unhandled promise rejections.
    • Added try...catch for onSuccess callback to improve error robustness.
  • src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx
    • Refactored error handling and queryClient.invalidateQueries calls in AddEndpointButton and EditEndpointDialog to ensure unambiguous UI toast feedback.
  • src/drizzle/schema.ts
    • Updated the Drizzle schema definition for providerEndpoints to include the WHERE deletedAt IS NULL condition for the unique index.
  • src/repository/error-rules.ts
    • Added overrideResponse and overrideStatusCode fields when syncing default error rules.
  • src/repository/provider-endpoints.ts
    • Introduced constants and helper functions for managing unique index conflicts and read-after-write consistency.
    • Updated ensureProviderEndpointExistsForUrl and syncProviderEndpointOnProviderEdit to use the new unique index conditions, preventing conflicts with soft-deleted records.
    • Implemented a conservative sync strategy in syncProviderEndpointOnProviderEdit to avoid unintended soft-deletions of previous endpoints.
    • Significantly refactored backfillProviderEndpointsFromProviders to provide detailed dry-run diagnostics and a robust repair mechanism for missing endpoints.
    • Enhanced updateProviderEndpoint with read-after-write consistency checks and specific conflict error handling.
  • tests/integration/provider-endpoint-index-and-repair.test.ts
    • Added new integration tests verifying the active-only unique index behavior and the dry-run and apply modes of the backfill utility.
  • tests/integration/provider-endpoint-regression-742.test.ts
    • Added a new integration test to confirm that sibling endpoints are no longer unintentionally deleted during provider updates.
  • tests/integration/provider-endpoint-sync-race.test.ts
    • Updated existing integration tests to reflect the new conservative sync behavior, where previous endpoints are kept active.
  • tests/unit/actions/provider-endpoints.test.ts
    • Added unit tests for editProviderEndpoint to validate conflict error mapping and successful update scenarios.
  • tests/unit/repository/provider-endpoint-742-direct-edit.test.ts
    • Added a new unit test for updateProviderEndpoint to verify its conflict fallback and read-after-write consistency.
  • tests/unit/repository/provider-endpoint-sync-helper.test.ts
    • Updated unit tests for syncProviderEndpointOnProviderEdit to reflect the conservative sync strategy and handling of existing next endpoints.
  • tests/unit/repository/provider-endpoints.test.ts
    • Updated unit tests for ensureProviderEndpointExistsForUrl to include the WHERE clause in onConflictDoNothing.
  • tests/unit/settings/providers/provider-endpoints-table.test.tsx
    • Added unit tests for UI components to ensure correct and unambiguous toast notifications for success and failure scenarios.
  • vitest.integration.config.ts
    • Updated the Vitest integration test configuration to include the new regression test file.
Activity
  • Initial implementation of fixes for endpoint update regressions.
  • Addition of new database migrations and schema updates to support active-only unique constraints.
  • Extensive refactoring of repository logic for provider endpoint management, including new consistency checks and a conservative sync strategy.
  • Development of a new troubleshooting guide and a data repair utility with dry-run capabilities.
  • Comprehensive unit and integration tests were added or updated to cover the new logic and verify the fixes for reported regressions.
  • UI components were updated to provide clearer user feedback for endpoint operations.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

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

Code Review

This pull request successfully addresses the endpoint update regressions and sibling disappearance issues identified in #742. The implementation of a more conservative sync strategy, deterministic edit outcomes with read-after-write consistency checks, and a robust repair tool significantly improves the reliability of provider endpoint management. The troubleshooting guide and integration tests provide excellent support for maintaining these changes. I have identified a few minor areas for improvement regarding consistency in field usage and promise handling in the UI components.

Comment on lines 424 to 425
// Use void to catch promise errors silently to avoid double toast
void queryClient.invalidateQueries({ queryKey: ["provider-endpoints", vendorId] });
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

The comment states that void is used to 'catch promise errors silently', but void only informs the linter that the promise is intentionally not being awaited; it does not catch rejections. To truly avoid unhandled promise rejections and ensure errors are silenced as intended, you should append a .catch(() => {}) to the promise.

Suggested change
// Use void to catch promise errors silently to avoid double toast
void queryClient.invalidateQueries({ queryKey: ["provider-endpoints", vendorId] });
// Use void and .catch() to ignore promise outcomes and avoid double toast
void queryClient.invalidateQueries({ queryKey: ["provider-endpoints", vendorId] }).catch(() => {});

Comment on lines 1745 to 1748
if (expectedEditableFields.url !== undefined) updates.url = expectedEditableFields.url;
if (payload.label !== undefined) updates.label = payload.label;
if (payload.sortOrder !== undefined) updates.sortOrder = payload.sortOrder;
if (payload.isEnabled !== undefined) updates.isEnabled = payload.isEnabled;
Copy link
Contributor

Choose a reason for hiding this comment

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

medium

For better consistency and to ensure the updates object perfectly aligns with the expectedEditableFields used in the subsequent consistency check, it is recommended to use the expectedEditableFields object for all fields in the update payload.

Suggested change
if (expectedEditableFields.url !== undefined) updates.url = expectedEditableFields.url;
if (payload.label !== undefined) updates.label = payload.label;
if (payload.sortOrder !== undefined) updates.sortOrder = payload.sortOrder;
if (payload.isEnabled !== undefined) updates.isEnabled = payload.isEnabled;
if (expectedEditableFields.url !== undefined) updates.url = expectedEditableFields.url;
if (expectedEditableFields.label !== undefined) updates.label = expectedEditableFields.label;
if (expectedEditableFields.sortOrder !== undefined) updates.sortOrder = expectedEditableFields.sortOrder;
if (expectedEditableFields.isEnabled !== undefined) updates.isEnabled = expectedEditableFields.isEnabled;

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

7 files reviewed, 1 comment

Edit Code Review Agent Settings | Greptile

Comment on lines 1746 to 1748
if (payload.label !== undefined) updates.label = payload.label;
if (payload.sortOrder !== undefined) updates.sortOrder = payload.sortOrder;
if (payload.isEnabled !== undefined) updates.isEnabled = payload.isEnabled;
Copy link

Choose a reason for hiding this comment

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

Inconsistency: URL uses expectedEditableFields.url (trimmed) but label/sortOrder/isEnabled use raw payload values. For consistency, all fields should go through the expectations picking logic.

Suggested change
if (payload.label !== undefined) updates.label = payload.label;
if (payload.sortOrder !== undefined) updates.sortOrder = payload.sortOrder;
if (payload.isEnabled !== undefined) updates.isEnabled = payload.isEnabled;
if (expectedEditableFields.url !== undefined) updates.url = expectedEditableFields.url;
if (expectedEditableFields.label !== undefined) updates.label = expectedEditableFields.label;
if (expectedEditableFields.sortOrder !== undefined) updates.sortOrder = expectedEditableFields.sortOrder;
if (expectedEditableFields.isEnabled !== undefined) updates.isEnabled = expectedEditableFields.isEnabled;
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/repository/provider-endpoints.ts
Line: 1746:1748

Comment:
Inconsistency: URL uses `expectedEditableFields.url` (trimmed) but label/sortOrder/isEnabled use raw `payload` values. For consistency, all fields should go through the expectations picking logic.

```suggestion
  if (expectedEditableFields.url !== undefined) updates.url = expectedEditableFields.url;
  if (expectedEditableFields.label !== undefined) updates.label = expectedEditableFields.label;
  if (expectedEditableFields.sortOrder !== undefined) updates.sortOrder = expectedEditableFields.sortOrder;
  if (expectedEditableFields.isEnabled !== undefined) updates.isEnabled = expectedEditableFields.isEnabled;
```

How can I resolve this? If you propose a fix, please make it concise.

@github-actions github-actions bot added bug Something isn't working area:provider area:UI area:docs size/XL Extra Large PR (> 1000 lines) labels Feb 9, 2026
Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

19 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 144 to 165
if (candidate.code === "PROVIDER_ENDPOINT_CONFLICT" || candidate.code === "23505") {
return true;
}

if (typeof candidate.message === "string") {
if (candidate.message.includes("[ProviderEndpointEdit] endpoint conflict")) {
return true;
}

if (candidate.message.includes("duplicate key value")) {
return true;
}
}

if (candidate.cause?.code === "23505") {
return true;
}

return (
typeof candidate.cause?.message === "string" &&
candidate.cause.message.includes("duplicate key value")
);
Copy link

Choose a reason for hiding this comment

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

Over-broad conflict detection
isDirectEndpointEditConflictError() treats any Postgres unique violation (code === "23505") or message containing "duplicate key value" as an endpoint-URL edit conflict. That will misreport unrelated unique constraint failures inside editProviderEndpoint() (e.g., if another unique index is violated during the same action) as ERROR_CODES.CONFLICT with the wrong user message. This should be constrained to the specific PROVIDER_ENDPOINT_CONFLICT code you throw from the repository, or at least check the constraint name (uniq_provider_endpoints_vendor_type_url) before mapping to CONFLICT.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/actions/provider-endpoints.ts
Line: 144:165

Comment:
**Over-broad conflict detection**
`isDirectEndpointEditConflictError()` treats any Postgres unique violation (`code === "23505"`) or message containing `"duplicate key value"` as an endpoint-URL edit conflict. That will misreport *unrelated* unique constraint failures inside `editProviderEndpoint()` (e.g., if another unique index is violated during the same action) as `ERROR_CODES.CONFLICT` with the wrong user message. This should be constrained to the specific `PROVIDER_ENDPOINT_CONFLICT` code you throw from the repository, or at least check the constraint name (`uniq_provider_endpoints_vendor_type_url`) before mapping to CONFLICT.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +1 to +2
DROP INDEX IF EXISTS "uniq_provider_endpoints_vendor_type_url";--> statement-breakpoint
CREATE UNIQUE INDEX IF NOT EXISTS "uniq_provider_endpoints_vendor_type_url" ON "provider_endpoints" USING btree ("vendor_id","provider_type","url") WHERE "provider_endpoints"."deleted_at" IS NULL;
Copy link

Choose a reason for hiding this comment

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

Migration numbering mismatch
PR description and docs refer to migration 0064 for the partial unique index, but the actual migration added here is 0065_stale_vertigo.sql (and journal updated accordingly). If downstream environments expect 0064_* or if troubleshooting docs/scripts reference 0064, this will break operator instructions and could leave the old index in place. Update the PR description/docs (or rename/regenerate the migration) so the migration number/tag referenced externally matches what ships.

Prompt To Fix With AI
This is a comment left during a code review.
Path: drizzle/0065_stale_vertigo.sql
Line: 1:2

Comment:
**Migration numbering mismatch**
PR description and docs refer to migration `0064` for the partial unique index, but the actual migration added here is `0065_stale_vertigo.sql` (and journal updated accordingly). If downstream environments expect `0064_*` or if troubleshooting docs/scripts reference 0064, this will break operator instructions and could leave the old index in place. Update the PR description/docs (or rename/regenerate the migration) so the migration number/tag referenced externally matches what ships.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +5 to +52
vi.resetModules();

const duplicateKeyError = Object.assign(
new Error("duplicate key value violates unique constraint"),
{
code: "23505",
}
);

const endpointRow = {
id: 42,
vendorId: 7,
providerType: "claude",
url: "https://next.example.com/v1/messages",
label: null,
sortOrder: 0,
isEnabled: true,
lastProbedAt: null,
lastProbeOk: null,
lastProbeStatusCode: null,
lastProbeLatencyMs: null,
lastProbeErrorType: null,
lastProbeErrorMessage: null,
createdAt: new Date("2026-01-01T00:00:00.000Z"),
updatedAt: new Date("2026-01-01T00:00:00.000Z"),
deletedAt: null,
};

const updateReturningMock = vi.fn(async () => {
throw duplicateKeyError;
});
const updateWhereMock = vi.fn(() => ({ returning: updateReturningMock }));
const updateSetMock = vi.fn(() => ({ where: updateWhereMock }));
const updateMock = vi.fn(() => ({ set: updateSetMock }));

const selectLimitMock = vi.fn(async () => [endpointRow]);
const selectWhereMock = vi.fn(() => ({ limit: selectLimitMock }));
const selectFromMock = vi.fn(() => ({ where: selectWhereMock }));
const selectMock = vi.fn(() => ({ from: selectFromMock }));

vi.doMock("@/drizzle/db", () => ({
db: {
update: updateMock,
select: selectMock,
},
}));

const { updateProviderEndpoint } = await import("@/repository/provider-endpoints");
Copy link

Choose a reason for hiding this comment

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

Mock leakage across tests
This test uses vi.resetModules() + vi.doMock("@/drizzle/db", ...) but never vi.unmock()/vi.doUnmock() or vi.restoreAllMocks() afterward. In Vitest, that can leak the mocked module into later tests that import @/drizzle/db (especially if ordering changes), causing unrelated unit tests to run against the mock DB. Add cleanup (e.g. afterEach(() => { vi.restoreAllMocks(); vi.doUnmock("@/drizzle/db"); })) or scope the mock using vi.mock at file level with proper teardown.

Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/unit/repository/provider-endpoint-742-direct-edit.test.ts
Line: 5:52

Comment:
**Mock leakage across tests**
This test uses `vi.resetModules()` + `vi.doMock("@/drizzle/db", ...)` but never `vi.unmock()`/`vi.doUnmock()` or `vi.restoreAllMocks()` afterward. In Vitest, that can leak the mocked module into later tests that import `@/drizzle/db` (especially if ordering changes), causing unrelated unit tests to run against the mock DB. Add cleanup (e.g. `afterEach(() => { vi.restoreAllMocks(); vi.doUnmock("@/drizzle/db"); })`) or scope the mock using `vi.mock` at file level with proper teardown.

How can I resolve this? If you propose a fix, please make it concise.

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@src/repository/provider-endpoints.ts`:
- Around line 1752-1796: The write-after-read consistency branch in
updateProviderEndpoint throws a plain Error when
readConsistentProviderEndpointAfterWrite returns falsy, which lacks a structured
code like the unique-violation path; change that throw to produce a structured
error with a code (e.g. PROVIDER_ENDPOINT_WRITE_READ_INCONSISTENCY) so callers
can dispatch uniformly—create the error similarly to the conflict branch (e.g.
Object.assign(new Error("[ProviderEndpointEdit] write-read consistency check
failed"), { code: PROVIDER_ENDPOINT_WRITE_READ_INCONSISTENCY })) and reference
the same symbols: updateProviderEndpoint,
readConsistentProviderEndpointAfterWrite, and the existing
DIRECT_ENDPOINT_EDIT_CONFLICT_CODE pattern.
- Around line 63-64: The constant DIRECT_ENDPOINT_EDIT_CONFLICT_CODE is defined
but not exported, causing the actions layer to hardcode
"PROVIDER_ENDPOINT_CONFLICT"; export DIRECT_ENDPOINT_EDIT_CONFLICT_CODE from
provider-endpoints.ts and update the actions code that checks the literal
"PROVIDER_ENDPOINT_CONFLICT" to import and use
DIRECT_ENDPOINT_EDIT_CONFLICT_CODE instead so the error code is maintained in
one place.
🧹 Nitpick comments (5)
src/repository/provider-endpoints.ts (5)

1351-1356: clampSampleLimit 参数类型与实际使用不一致,且存在死代码

参数声明为 number | undefined(来自可选属性),但 !Number.isFinite(value)valueundefined 时已返回 true。第 1355 行的 value ?? 20 永远不会走到 ?? 20 分支,因为执行到此处时 value 一定是有限正数。

建议简化
 function clampSampleLimit(value: number | undefined): number {
-  if (!Number.isFinite(value) || (value ?? 0) <= 0) {
+  if (value === undefined || !Number.isFinite(value) || value <= 0) {
     return 20;
   }
-  return Math.min(Math.trunc(value ?? 20), 2000);
+  return Math.min(Math.trunc(value), 2000);
 }

615-642: 多处查询重复展开字段选择,可复用 providerEndpointSelectFields

findProviderEndpointById(615-642)、findProviderEndpointsByVendorAndType(751-784)、findProviderEndpointsByVendor(787-812)等函数仍然手动列举所有列,而第 207-224 行已定义了 providerEndpointSelectFieldsupdateProviderEndpoint.returning() 已使用它(第 1757 行),建议其他查询函数也统一使用以减少重复。

示例(以 findProviderEndpointById 为例)
 export async function findProviderEndpointById(
   endpointId: number
 ): Promise<ProviderEndpoint | null> {
   const rows = await db
-    .select({
-      id: providerEndpoints.id,
-      vendorId: providerEndpoints.vendorId,
-      providerType: providerEndpoints.providerType,
-      url: providerEndpoints.url,
-      label: providerEndpoints.label,
-      sortOrder: providerEndpoints.sortOrder,
-      isEnabled: providerEndpoints.isEnabled,
-      lastProbedAt: providerEndpoints.lastProbedAt,
-      lastProbeOk: providerEndpoints.lastProbeOk,
-      lastProbeStatusCode: providerEndpoints.lastProbeStatusCode,
-      lastProbeLatencyMs: providerEndpoints.lastProbeLatencyMs,
-      lastProbeErrorType: providerEndpoints.lastProbeErrorType,
-      lastProbeErrorMessage: providerEndpoints.lastProbeErrorMessage,
-      createdAt: providerEndpoints.createdAt,
-      updatedAt: providerEndpoints.updatedAt,
-      deletedAt: providerEndpoints.deletedAt,
-    })
+    .select(providerEndpointSelectFields)
     .from(providerEndpoints)

Also applies to: 751-784, 787-812


1662-1696: backfill apply 模式下批量插入无事务保护

flush() 中的 db.insert() 在事务外逐批执行。如果中途出错,已插入的批次无法回滚,导致部分修复状态。对于修复工具这可能是可接受的(幂等 onConflictDoNothing),但建议在日志或报告中明确记录实际完成的批次数,以便排查中断情况。


446-448: 注释使用了中文

根据编码规范,用户可见字符串需要 i18n。虽然这些是代码注释而非用户可见字符串,但在多语言团队中建议保持注释语言统一为英文,便于所有贡献者理解。

Also applies to: 472-474


157-170: computeVendorKeyderiveDisplayNameFromDomain 无需 async

两个函数都声明为 async 但不含任何异步操作,返回 Promise 包装不必要。这增加微任务开销,且会误导调用方认为存在 I/O 操作。

建议同时移除两个函数的 async

  • computeVendorKey: 改为 string | null
  • deriveDisplayNameFromDomain: 改为 string

更新所有调用点移除 await(src/repository/provider-endpoints.ts:400, 520; src/actions/providers.ts:3666; 测试文件)。

@github-actions
Copy link
Contributor

github-actions bot commented Feb 9, 2026

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

5 files reviewed, 3 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 1744 to +1750
const updates: Partial<typeof providerEndpoints.$inferInsert> = { updatedAt: now };
if (payload.url !== undefined) updates.url = payload.url;
if (payload.label !== undefined) updates.label = payload.label;
if (payload.sortOrder !== undefined) updates.sortOrder = payload.sortOrder;
if (payload.isEnabled !== undefined) updates.isEnabled = payload.isEnabled;
if (expectedEditableFields.url !== undefined) updates.url = expectedEditableFields.url;
if (expectedEditableFields.label !== undefined) updates.label = expectedEditableFields.label;
if (expectedEditableFields.sortOrder !== undefined)
updates.sortOrder = expectedEditableFields.sortOrder;
if (expectedEditableFields.isEnabled !== undefined)
updates.isEnabled = expectedEditableFields.isEnabled;
Copy link

Choose a reason for hiding this comment

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

Fields picked inconsistently: url uses expectedEditableFields.url (already trimmed), but label, sortOrder, and isEnabled use expectedEditableFields.* which go through the expectations picking logic. For true consistency, all fields should use the expected values or all should use raw payload.

Suggested change
const updates: Partial<typeof providerEndpoints.$inferInsert> = { updatedAt: now };
if (payload.url !== undefined) updates.url = payload.url;
if (payload.label !== undefined) updates.label = payload.label;
if (payload.sortOrder !== undefined) updates.sortOrder = payload.sortOrder;
if (payload.isEnabled !== undefined) updates.isEnabled = payload.isEnabled;
if (expectedEditableFields.url !== undefined) updates.url = expectedEditableFields.url;
if (expectedEditableFields.label !== undefined) updates.label = expectedEditableFields.label;
if (expectedEditableFields.sortOrder !== undefined)
updates.sortOrder = expectedEditableFields.sortOrder;
if (expectedEditableFields.isEnabled !== undefined)
updates.isEnabled = expectedEditableFields.isEnabled;
const updates: Partial<typeof providerEndpoints.$inferInsert> = { updatedAt: now };
if (expectedEditableFields.url !== undefined) updates.url = expectedEditableFields.url;
if (payload.label !== undefined) updates.label = payload.label;
if (payload.sortOrder !== undefined) updates.sortOrder = payload.sortOrder;
if (payload.isEnabled !== undefined) updates.isEnabled = payload.isEnabled;
Prompt To Fix With AI
This is a comment left during a code review.
Path: src/repository/provider-endpoints.ts
Line: 1744:1750

Comment:
Fields picked inconsistently: `url` uses `expectedEditableFields.url` (already trimmed), but `label`, `sortOrder`, and `isEnabled` use `expectedEditableFields.*` which go through the expectations picking logic. For true consistency, all fields should use the expected values or all should use raw payload.

```suggestion
  const updates: Partial<typeof providerEndpoints.$inferInsert> = { updatedAt: now };
  if (expectedEditableFields.url !== undefined) updates.url = expectedEditableFields.url;
  if (payload.label !== undefined) updates.label = payload.label;
  if (payload.sortOrder !== undefined) updates.sortOrder = payload.sortOrder;
  if (payload.isEnabled !== undefined) updates.isEnabled = payload.isEnabled;
```

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 144 to 165
if (candidate.code === "PROVIDER_ENDPOINT_CONFLICT" || candidate.code === "23505") {
return true;
}

if (typeof candidate.message === "string") {
if (candidate.message.includes("[ProviderEndpointEdit] endpoint conflict")) {
return true;
}

if (candidate.message.includes("duplicate key value")) {
return true;
}
}

if (candidate.cause?.code === "23505") {
return true;
}

return (
typeof candidate.cause?.message === "string" &&
candidate.cause.message.includes("duplicate key value")
);
Copy link

Choose a reason for hiding this comment

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

Over-broad conflict detection: any Postgres unique violation (code === "23505") or "duplicate key value" message gets mapped to endpoint conflict. This will misreport unrelated unique constraint failures (e.g., on different tables or indexes) as ERROR_CODES.CONFLICT with the wrong user message.

Check for the specific PROVIDER_ENDPOINT_CONFLICT code first, or verify the constraint name matches uniq_provider_endpoints_vendor_type_url before mapping to CONFLICT.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/actions/provider-endpoints.ts
Line: 144:165

Comment:
Over-broad conflict detection: any Postgres unique violation (`code === "23505"`) or `"duplicate key value"` message gets mapped to endpoint conflict. This will misreport unrelated unique constraint failures (e.g., on different tables or indexes) as `ERROR_CODES.CONFLICT` with the wrong user message.

Check for the specific `PROVIDER_ENDPOINT_CONFLICT` code first, or verify the constraint name matches `uniq_provider_endpoints_vendor_type_url` before mapping to CONFLICT.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +5 to +10
vi.resetModules();

const duplicateKeyError = Object.assign(
new Error("duplicate key value violates unique constraint"),
{
code: "23505",
Copy link

Choose a reason for hiding this comment

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

Mock cleanup missing: vi.doMock("@/drizzle/db", ...) without vi.doUnmock() or vi.restoreAllMocks() in afterEach can leak the mocked module to other tests that import @/drizzle/db, especially if test ordering changes.

Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/unit/repository/provider-endpoint-742-direct-edit.test.ts
Line: 5:10

Comment:
Mock cleanup missing: `vi.doMock("@/drizzle/db", ...)` without `vi.doUnmock()` or `vi.restoreAllMocks()` in `afterEach` can leak the mocked module to other tests that import `@/drizzle/db`, especially if test ordering changes.

How can I resolve this? If you propose a fix, please make it concise.

@github-actions
Copy link
Contributor

github-actions bot commented Feb 9, 2026

🧪 测试结果

测试类型 状态
代码质量
单元测试
集成测试
API 测试

总体结果: ✅ 所有测试通过

Copy link

@greptile-apps greptile-apps bot left a comment

Choose a reason for hiding this comment

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

8 files reviewed, 4 comments

Edit Code Review Agent Settings | Greptile

Comment on lines 597 to +600
if (res.ok) {
toast.success(t("endpointUpdateSuccess"));
setOpen(false);
queryClient.invalidateQueries({ queryKey: ["provider-endpoints"] });
} else {
toast.error(
res.errorCode
? getErrorMessage(tErrors, res.errorCode, res.errorParams)
: t("endpointUpdateFailed")
);
queryClient.invalidateQueries({ queryKey: ["provider-endpoints"] }).catch(() => undefined);
Copy link

Choose a reason for hiding this comment

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

Unhandled rejection from invalidation

In EditEndpointDialog, invalidateQueries(...).catch(...) is used, but other invalidateQueries calls in this same file (probe/delete/toggle success handlers) are still fire-and-forget without awaiting/voiding/catching. If invalidateQueries rejects (e.g., cancelled query function throwing), this can surface as an unhandled promise rejection at runtime.

Also appears at src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx:206,229-230,250.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx
Line: 597:600

Comment:
**Unhandled rejection from invalidation**

In `EditEndpointDialog`, `invalidateQueries(...).catch(...)` is used, but other `invalidateQueries` calls in this same file (probe/delete/toggle success handlers) are still fire-and-forget without awaiting/voiding/catching. If `invalidateQueries` rejects (e.g., cancelled query function throwing), this can surface as an unhandled promise rejection at runtime.

Also appears at `src/app/[locale]/settings/providers/_components/provider-endpoints-table.tsx:206,229-230,250`.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines +1724 to 1731
if (options === undefined) {
return {
inserted: report.inserted,
uniqueCandidates: report.uniqueCandidates,
skippedInvalid: report.skippedInvalid,
};
}

Copy link

Choose a reason for hiding this comment

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

Overload return shape bug

backfillProviderEndpointsFromProviders() returns the summary shape only when options === undefined, but the declared overload says “calling with an options object returns the full report.” Passing an empty object (e.g. {}) will currently return the full report, but passing { mode: undefined } / other fields explicitly undefined will also return the full report, which can break callers that treat “options present but effectively empty” as the legacy call. If you intend “no options or empty options” to keep the legacy shape, the runtime check needs to match that contract.

Location: src/repository/provider-endpoints.ts:1724-1732.

Prompt To Fix With AI
This is a comment left during a code review.
Path: src/repository/provider-endpoints.ts
Line: 1724:1731

Comment:
**Overload return shape bug**

`backfillProviderEndpointsFromProviders()` returns the *summary shape* only when `options === undefined`, but the declared overload says “calling with an options object returns the full report.” Passing an empty object (e.g. `{}`) will currently return the full report, but passing `{ mode: undefined }` / other fields explicitly undefined will also return the full report, which can break callers that treat “options present but effectively empty” as the legacy call. If you intend “no options or empty options” to keep the legacy shape, the runtime check needs to match that contract.

Location: `src/repository/provider-endpoints.ts:1724-1732`.

How can I resolve this? If you propose a fix, please make it concise.

Comment on lines 141 to +148
});

it("editProviderEndpoint: conflict maps to CONFLICT errorCode", async () => {
getSessionMock.mockResolvedValue({ user: { role: "admin" } });
updateProviderEndpointMock.mockRejectedValue(
Object.assign(new Error("[ProviderEndpointEdit] endpoint conflict"), {
code: "PROVIDER_ENDPOINT_CONFLICT",
})
Copy link

Choose a reason for hiding this comment

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

Mock state leaks between tests

New tests introduce updateProviderEndpointMock but don’t reset its call history/implementations between test cases. Since the module is mocked once at file scope, earlier .mockRejectedValue / .mockResolvedValue can affect later tests depending on ordering.

Add an afterEach(() => vi.clearAllMocks()) (or at least reset updateProviderEndpointMock) to keep tests isolated.

Prompt To Fix With AI
This is a comment left during a code review.
Path: tests/unit/actions/provider-endpoints.test.ts
Line: 141:148

Comment:
**Mock state leaks between tests**

New tests introduce `updateProviderEndpointMock` but don’t reset its call history/implementations between test cases. Since the module is mocked once at file scope, earlier `.mockRejectedValue` / `.mockResolvedValue` can affect later tests depending on ordering.

Add an `afterEach(() => vi.clearAllMocks())` (or at least reset `updateProviderEndpointMock`) to keep tests isolated.

How can I resolve this? If you propose a fix, please make it concise.

@greptile-apps
Copy link

greptile-apps bot commented Feb 9, 2026

Additional Comments (1)

vitest.integration.config.ts
New integration test not included

tests/integration/provider-endpoint-index-and-repair.test.ts was added in this PR, but it isn’t listed in vitest.integration.config.ts’s test.include. As a result, it won’t run in the integration suite even when DSN is set, which defeats the purpose of guarding the new partial-index/repair behavior.

Add it to the include list or broaden the include pattern to cover new integration tests.

Prompt To Fix With AI
This is a comment left during a code review.
Path: vitest.integration.config.ts
Line: 21:32

Comment:
**New integration test not included**

`tests/integration/provider-endpoint-index-and-repair.test.ts` was added in this PR, but it isn’t listed in `vitest.integration.config.ts`’s `test.include`. As a result, it won’t run in the integration suite even when `DSN` is set, which defeats the purpose of guarding the new partial-index/repair behavior.

Add it to the include list or broaden the include pattern to cover new integration tests.

How can I resolve this? If you propose a fix, please make it concise.

@ding113 ding113 merged commit b16a9dc into dev Feb 10, 2026
15 checks passed
@github-project-automation github-project-automation bot moved this from Backlog to Done in Claude Code Hub Roadmap Feb 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:docs area:provider area:UI bug Something isn't working size/XL Extra Large PR (> 1000 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant