Skip to content

fix(probe): remove success log filtering to enable latency curve rendering#636

Merged
ding113 merged 2 commits into
devfrom
fix/endpoint-probe-log-filtering
Jan 21, 2026
Merged

fix(probe): remove success log filtering to enable latency curve rendering#636
ding113 merged 2 commits into
devfrom
fix/endpoint-probe-log-filtering

Conversation

@ding113

@ding113 ding113 commented Jan 21, 2026

Copy link
Copy Markdown
Owner

Summary

  • Remove shouldLogScheduledSuccess() filtering function that was causing ~5/6 of successful probe results to be discarded
  • Frontend latency curve can now render properly with complete probe history data
  • Change default log retention from 7 days to 1 day (storage: ~40MB/day with auto-cleanup)
  • Auto-backfill provider_vendor_id for legacy providers without vendor association

Related PRs

Problem Analysis

Endpoint probe scheduler was running correctly every 10s, but successful probes were filtered by a 60s sampling threshold (SUCCESS_LOG_MIN_INTERVAL_MS). This caused:

  • Only ~1/6 of successful probes recorded to provider_endpoint_probe_logs table
  • Frontend latency curve component could not render meaningful data
  • Users reported probe "only runs once at startup" (actually running, just not logging)

Additionally, legacy providers created before PR #608 had NULL or 0 values for provider_vendor_id, causing null pointer issues in vendor-type circuit breaker logic.

Changes

Core Changes

File Change
src/lib/provider-endpoints/probe.ts Remove filtering logic, always record all probes
src/lib/provider-endpoints/probe-log-cleanup.ts Change default retention from 7 to 1 day
tests/unit/lib/provider-endpoints/probe.test.ts Update test expectations
.env.example Remove obsolete env var, update documentation

Supporting Changes

File Change
src/repository/provider-endpoints.ts Add backfillProviderVendorsFromProviders() to auto-create vendors for legacy providers
src/instrumentation.ts Run vendor backfill on startup (before endpoint backfill)
src/drizzle/schema.ts Make providerVendorId nullable to support legacy data
src/types/provider.ts Update type definitions to reflect nullable vendor ID
src/repository/provider.ts Add null checks before vendor operations
src/app/v1/_lib/proxy/*.ts Add null checks for providerVendorId in circuit breaker logic
src/app/[locale]/settings/providers/_components/*.tsx Handle orphaned providers (no vendor) in UI
messages/*/settings/providers/strings.json Add i18n string for "Unknown Vendor"

Storage Impact

  • Before: ~24 records/min (filtered)
  • After: ~144 records/min (all probes)
  • With 1-day retention: ~207K records (~40MB) at steady state
  • Auto-cleanup runs every 24h to maintain bounded storage

Test Plan

  • Unit tests pass (9/9 probe tests, 1022/1022 total)
  • TypeScript typecheck pass
  • Biome lint pass
  • Production build pass
  • Verify latency curve renders in dashboard after deployment
  • Verify legacy providers auto-backfill vendor associations

Checklist

  • Code follows project conventions
  • Self-review completed
  • Tests pass locally
  • Documentation updated (if needed)

🤖 Generated with Claude Code

ding113 and others added 2 commits January 22, 2026 00:31
When upgrading from older versions, existing providers were incorrectly
grouped under "Unknown Vendor #0" instead of being auto-aggregated by
their website domain.

Changes:
- Add backfillProviderVendorsFromProviders() to auto-create vendors from
  provider URLs during startup
- Add deriveDisplayNameFromDomain() to generate display names from domains
- Integrate vendor backfill into instrumentation.ts startup flow
- Fix providerVendorId type to allow null (Schema, Type, Transformer)
- Add vendorId=-1 protection for orphaned providers in frontend
- Add i18n keys for orphanedProviders in 5 languages

The backfill runs idempotently on startup, processing providers with
null or 0 vendorId, extracting domains from websiteUrl (preferred) or
url, and creating/associating vendors accordingly.

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
…ering

Problem:
- Endpoint probe scheduler runs correctly every 10s
- But successful probes were filtered by 60s sampling threshold
- ~5/6 success probes not recorded to history table
- Frontend latency curve could not be rendered

Solution:
- Remove shouldLogScheduledSuccess() filtering function
- Remove SUCCESS_LOG_MIN_INTERVAL_MS constant
- Always call recordProviderEndpointProbeResult() for all probes
- Change default retention from 7 days to 1 day (storage: ~40MB/day)
- Update .env.example to reflect new behavior

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jan 21, 2026

Copy link
Copy Markdown
📝 Walkthrough

总体概述

该 PR 引入对孤立提供商(缺少有效供应商 ID 的提供商)的支持。将 providerVendorId 在数据库架构和类型定义中改为可空,添加孤立提供商的本地化字符串,实现启动时的供应商回填机制,更新 UI 组件处理孤立提供商分组,并移除基于快照的探测日志记录改为始终记录结果。探测日志保留期从 7 天减少到 1 天。

变更清单

类别 文件 变更摘要
配置与本地化 .env.example 移除 ENDPOINT_PROBE_SUCCESS_LOG_MIN_INTERVAL_MS 常量,将 ENDPOINT_PROBE_LOG_RETENTION_DAYS 从 7 改为 1,更新相关注释说明日志行为
messages/en/settings/providers/strings.json 新增 orphanedProviders 本地化键值 "Unknown Vendor"
messages/ja/settings/providers/strings.json 新增 orphanedProviders,更新 vendorTypeCircuitUpdated 文本
messages/ru/settings/providers/strings.json 新增 orphanedProviders,更新 vendorFallbackNamevendorTypeCircuitUpdated 文本
messages/zh-CN/settings/providers/strings.json 新增 orphanedProviders,更新 vendorTypeCircuitUpdated 文本
messages/zh-TW/settings/providers/strings.json 新增 orphanedProviders,更新 vendorFallbackNamevendorTypeCircuitUpdated 文本
数据库与类型定义 src/drizzle/schema.ts 移除 providers 表中 providerVendorId 列的 notNull() 约束,使其成为可空列
src/types/provider.ts ProviderProviderDisplay 中的 providerVendorId 类型从 number 改为 number | null
仓储层与业务逻辑 src/repository/_shared/transformers.ts toProvider() 函数中 providerVendorId 未定义时改为默认值 null 而非 0
src/repository/provider-endpoints.ts 新增 backfillProviderVendorsFromProviders() 导出函数及相关辅助函数,实现根据现有提供商 URL 回填供应商的逻辑
src/repository/provider.ts createProviderupdateProvider 中的端点初始化改为条件性执行(需要有效的 providerVendorId
src/actions/providers.ts removeProvider 中的自动清理条件从检查提供商对象存在改为检查 provider.providerVendorId 为真值
UI 组件 src/app/[locale]/settings/providers/_components/provider-vendor-view.tsx 实现孤立供应商分组(vendorId -1),更新供应商卡片显示名称逻辑,调整对话框和端点区域的渲染条件
src/app/[locale]/settings/providers/_components/vendor-keys-compact-list.tsx urlResolver 回调中添加警卫,当 vendorId <= 0 时提前返回 null
代理与会话处理 src/app/v1/_lib/proxy/forwarder.ts 将供应商类型断路器和端点超时检查用 providerVendorId 存在性条件保护
src/app/v1/_lib/proxy/provider-selector.ts 在三个位置添加 providerVendorId > 0 警卫,确保只在有效供应商 ID 时检查断路器
src/app/v1/_lib/proxy/session.ts ProxySession.addProviderToChainvendorId 现在使用 provider.providerVendorId ?? undefined
探测与日志 src/lib/provider-endpoints/probe-log-cleanup.ts RETENTION_DAYS 的默认值从 7 改为 1
src/lib/provider-endpoints/probe.ts 移除 SUCCESS_LOG_MIN_INTERVAL_MS 常量及 shouldLogScheduledSuccess 函数,简化探测逻辑始终记录结果
src/instrumentation.ts 在启动时多个位置添加 backfillProviderVendorsFromProviders 调用,包括生产和开发路径
测试 tests/unit/lib/provider-endpoints/probe.test.ts 更新快照相关测试用例,移除 updateProviderEndpointProbeSnapshot 模拟和断言,改为验证 recordProviderEndpointProbeResult

代码审查工作量估计

🎯 3 (中等) | ⏱️ ~30 分钟

可能相关的 PR

  • PR #635:包含重叠的供应商回填、可空 providerVendorId、孤立提供商 UI 字符串和分组、仪器/回填集成等相关代码级变更
  • PR #535:两个 PR 都修改提供商数据形状(架构和 toProvider 转换器),涉及相同的数据库映射代码路径
  • PR #608:主 PR 对 PR #608 引入的供应商/端点/探测功能进行目标调整(架构、探测日志/清理、回填、providerVendorId 处理、孤立供应商 UI 和警卫)
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 28.57% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed Pull request title accurately describes the main change: removing success log filtering to enable latency curve rendering, which aligns with the primary objective stated in the PR description.
Description check ✅ Passed 拉取请求描述与代码变更相关,详细说明了问题分析、核心变更、存储影响和测试计划。

✏️ 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/endpoint-probe-log-filtering

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
Copy Markdown
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 significantly enhances the reliability and completeness of endpoint probe data by removing previous filtering mechanisms that led to incomplete logging of successful probes. This change directly improves the accuracy of frontend latency visualizations and ensures a more robust historical record. Additionally, it introduces better handling for providers not associated with a vendor, including a backfill mechanism and UI adjustments, while also optimizing log storage by reducing the default retention period.

Highlights

  • Probe Log Filtering Removed: The filtering logic that previously discarded approximately 5/6 of successful endpoint probe results has been eliminated. All successful probe outcomes will now be recorded, providing a complete dataset for analysis.
  • Frontend Latency Curve Improvement: With the removal of probe log filtering, the frontend latency curve can now render accurately and meaningfully, utilizing the full history of probe data.
  • Probe Log Retention Adjusted: The default retention period for endpoint probe logs has been reduced from 7 days to 1 day. This change optimizes storage, with an estimated daily storage footprint of ~40MB.
  • Handling of Orphaned Providers: Providers without an assigned providerVendorId are now explicitly managed. They are grouped under an 'Unknown Vendor' category in the user interface, and the providerVendorId field in the database schema has been updated to be nullable.
  • Automatic Vendor Backfill: New instrumentation logic has been introduced to automatically backfill provider_vendors for existing providers that lack a vendor ID, aggregating them based on their domain names.
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.

@github-actions github-actions Bot added bug Something isn't working area:provider area:i18n area:UI size/L Large PR (< 1000 lines) labels Jan 21, 2026
@github-actions

Copy link
Copy Markdown
Contributor

🧪 测试结果

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

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

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Fix all issues with AI agents
In `@src/drizzle/schema.ts`:
- Around line 154-156: The schema change to providerVendorId (the
integer('provider_vendor_id') column referencing providerVendors.id) removed the
notNull() constraint but you did not generate the migration; run the migration
generator (bun run db:generate) to produce the corresponding migration that
drops the NOT NULL constraint for providerVendorId, verify the generated
migration reflects the change (alter column to nullable) and add/commit that
migration alongside the schema change before merging.
🧹 Nitpick comments (3)
src/app/[locale]/settings/providers/_components/provider-vendor-view.tsx (1)

133-137: 排序行为可能需要调整。

当前排序 (a, b) => a - b 会将孤立供应商组(vendorId = -1)置于列表最前。从用户体验角度,孤立供应商通常应显示在最后,便于用户优先关注正常关联的供应商。

可选:将孤立供应商排序至末尾
   const allVendorIds = useMemo(() => {
     const ids = new Set<number>(vendors.map((v) => v.id));
     Object.keys(providersByVendor).forEach((id) => ids.add(Number(id)));
-    return Array.from(ids).sort((a, b) => a - b);
+    return Array.from(ids).sort((a, b) => {
+      // 将 -1(孤立供应商)排到最后
+      if (a === -1) return 1;
+      if (b === -1) return -1;
+      return a - b;
+    });
   }, [vendors, providersByVendor]);
src/instrumentation.ts (1)

209-225: 建议提取重复的 backfill 逻辑到辅助函数

生产环境 (209-225) 和开发环境 (309-325) 的 provider vendors backfill 代码块完全相同。考虑提取到一个类似于 syncErrorRulesAndInitializeDetector() 的独立辅助函数,减少重复并便于维护。

♻️ 建议的重构方案
+/**
+ * 回填 provider_vendors(按域名自动聚合旧 providers)
+ * 非关键功能,失败不阻塞启动
+ */
+async function backfillProviderVendors(): Promise<void> {
+  try {
+    const { backfillProviderVendorsFromProviders } = await import(
+      "@/repository/provider-endpoints"
+    );
+    const vendorResult = await backfillProviderVendorsFromProviders();
+    logger.info("[Instrumentation] Provider vendors backfill completed", {
+      processed: vendorResult.processed,
+      providersUpdated: vendorResult.providersUpdated,
+      vendorsCreatedCount: vendorResult.vendorsCreated.size,
+      skippedInvalidUrl: vendorResult.skippedInvalidUrl,
+    });
+  } catch (error) {
+    logger.warn("[Instrumentation] Failed to backfill provider vendors", {
+      error: error instanceof Error ? error.message : String(error),
+    });
+  }
+}

然后在生产和开发环境中调用 await backfillProviderVendors();

Also applies to: 309-325

src/repository/provider-endpoints.ts (1)

310-310: vendorsCreated 命名可能产生误导

vendorsCreated Set 实际上收集的是所有被关联的 vendor ID(包括已存在的和新创建的),但命名暗示这些都是新创建的。这可能导致日志中 vendorsCreatedCount 的含义不够准确。

考虑重命名为 vendorsReferenceduniqueVendorIds,或者在 getOrCreateProviderVendorIdFromUrls 返回时区分是否为新创建。

Comment thread src/drizzle/schema.ts
Comment on lines +154 to +156
providerVendorId: integer('provider_vendor_id').references(() => providerVendors.id, {
onDelete: 'restrict',
}),

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# Check the schema.ts file around lines 154-156
head -160 src/drizzle/schema.ts | tail -20

Repository: ding113/claude-code-hub

Length of output: 771


🏁 Script executed:

# List migration files in the drizzle directory
ls -la src/drizzle/ 2>/dev/null || echo "drizzle directory not found"

Repository: ding113/claude-code-hub

Length of output: 360


🏁 Script executed:

# Check if migrations directory exists and list recent migrations
fd -e sql . src/drizzle/migrations 2>/dev/null | sort -r | head -10

Repository: ding113/claude-code-hub

Length of output: 49


🏁 Script executed:

# Search for provider_vendor_id in migration files to find the relevant migration
rg "provider_vendor_id" src/drizzle/migrations/ 2>/dev/null | head -20

Repository: ding113/claude-code-hub

Length of output: 49


必须执行 bun run db:generate 生成迁移文件。

providerVendorId 字段的 schema 变更已确认(移除 notNull() 约束),但未发现对应的迁移文件。根据编码规范,修改 src/drizzle/schema.ts 后必须运行 bun run db:generate 来生成迁移文件。请确保在合并前完成此步骤。

🤖 Prompt for AI Agents
In `@src/drizzle/schema.ts` around lines 154 - 156, The schema change to
providerVendorId (the integer('provider_vendor_id') column referencing
providerVendors.id) removed the notNull() constraint but you did not generate
the migration; run the migration generator (bun run db:generate) to produce the
corresponding migration that drops the NOT NULL constraint for providerVendorId,
verify the generated migration reflects the change (alter column to nullable)
and add/commit that migration alongside the schema change before merging.

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request effectively addresses the issue of incomplete probe history data by removing the success log filtering mechanism and adjusting the default log retention. The changes to allow providerVendorId to be nullable are well-integrated across the database schema, API actions, and UI components, including a crucial backfill script for existing data. The updated tests accurately reflect the new probe logging behavior. Overall, the changes significantly improve the accuracy of probe data and enable proper rendering of the latency curve.

I am having trouble creating individual review comments. Click here to see my feedback.

src/drizzle/schema.ts (154-156)

critical

Removing the .notNull() constraint for providerVendorId is a critical schema change that enables providers to exist without an explicit vendor association. This is fundamental to supporting 'orphaned' providers and the new backfill logic.

src/types/provider.ts (45)

critical

Changing providerVendorId: number; to providerVendorId: number | null; is a fundamental type definition update that reflects the database schema change. This is crucial for type safety and correctly representing the new nullable state of vendor associations.

src/repository/provider-endpoints.ts (230-337)

critical

The deriveDisplayNameFromDomain and backfillProviderVendorsFromProviders functions are critical for data migration. backfillProviderVendorsFromProviders ensures that existing providers are correctly associated with vendors based on their URLs, or new vendors are created. This is a robust solution for handling the new nullable providerVendorId field.

src/repository/provider.ts (565-585)

high

This conditional logic for ensureProviderEndpointExistsForUrl in updateProvider is well-designed. It ensures that endpoints are correctly managed when a provider's URL or vendor association changes, or when a provider is newly assigned to a vendor. The check for transformed.providerVendorId !== previousVendorId || previousVendorId === null is particularly important for handling initial vendor assignments.

src/lib/provider-endpoints/probe.ts (142-152)

high

The removal of the shouldLogScheduledSuccess function is a key change. This function was responsible for sampling successful probe logs, and its removal ensures that all successful probe results are now recorded, enabling more accurate latency curve rendering.

src/instrumentation.ts (209-225)

high

The addition of backfillProviderVendorsFromProviders() in the instrumentation is a high-priority change. This ensures that existing providers are correctly associated with vendors (or new ones created) after the providerVendorId column becomes nullable, maintaining data integrity and enabling the new UI features.

src/app/v1/_lib/proxy/forwarder.ts (269)

medium

This additional check for currentProvider.providerVendorId being truthy is important. It ensures that isVendorTypeCircuitOpen is only called when a valid providerVendorId exists, preventing potential issues with null values.

src/lib/provider-endpoints/probe.ts (5)

medium

Removing updateProviderEndpointProbeSnapshot from the imports is correct, as this function is no longer used after the refactoring of probe result logging.

src/app/v1/_lib/proxy/forwarder.ts (916-917)

medium

Adding currentProvider.providerVendorId to this condition is a necessary safety check. recordVendorTypeAllEndpointsTimeout should only be invoked if a valid providerVendorId is present, preventing errors when dealing with providers that might not have a vendor association.

src/app/v1/_lib/proxy/provider-selector.ts (534)

medium

Adding provider.providerVendorId && here is a crucial defensive check. It ensures that isVendorTypeCircuitOpen is only called when providerVendorId is not null, preventing potential runtime errors and improving the robustness of the circuit breaker logic.

src/app/v1/_lib/proxy/provider-selector.ts (877)

medium

This check is correctly added to ensure isVendorTypeCircuitOpen is only invoked when p.providerVendorId is a valid, non-null value, aligning with the updated schema.

src/app/v1/_lib/proxy/provider-selector.ts (971)

medium

Adding p.providerVendorId && here is consistent with other changes, ensuring that isVendorTypeCircuitOpen is only called with a valid providerVendorId.

src/app/v1/_lib/proxy/session.ts (484)

medium

Changing vendorId: provider.providerVendorId, to vendorId: provider.providerVendorId ?? undefined, correctly maps a null providerVendorId to undefined in the ProviderChainItem. This is a more idiomatic way to represent an optional field that is not present, aligning with the updated type definition.

src/app/v1/_lib/proxy/forwarder.ts (231)

medium

Adding currentProvider.providerVendorId && is a good defensive check. Since providerVendorId can now be null, this prevents potential TypeError if currentProvider.providerVendorId is null before accessing its value.

src/app/[locale]/settings/providers/_components/vendor-keys-compact-list.tsx (120)

medium

Adding this check ensures that getProviderEndpoints is not called for the virtual orphaned vendor group (where vendorId <= 0), preventing unnecessary API calls and potential errors.

src/instrumentation.ts (309-325)

medium

Including the backfillProviderVendorsFromProviders() call in the development environment is also important. This ensures that developers working with local databases will have their data correctly migrated and aligned with the new schema, preventing inconsistencies during development.

src/lib/provider-endpoints/probe-log-cleanup.ts (18)

medium

Changing the default ENDPOINT_PROBE_LOG_RETENTION_DAYS from 7 to 1 directly implements the specified change in the PR description. This reduces storage requirements for probe logs.

src/actions/providers.ts (759)

medium

The change from if (provider) to if (provider?.providerVendorId) is a good defensive measure. With providerVendorId now being nullable, explicitly checking for its existence before attempting to delete the vendor prevents potential runtime errors if providerVendorId is null or 0.

src/lib/provider-endpoints/probe.ts (32-35)

medium

The removal of SUCCESS_LOG_MIN_INTERVAL_MS is a direct consequence of eliminating the filtering logic for successful probe logs, which is a core change in this PR.

src/app/[locale]/settings/providers/_components/provider-vendor-view.tsx (248)

medium

Similarly, preventing the VendorEndpointsSection from rendering for the virtual orphaned group (vendorId > 0) is correct, as orphaned providers do not have associated endpoints in the same way real vendors do.

src/repository/_shared/transformers.ts (69)

medium

Changing the default value for providerVendorId from 0 to null aligns the transformation logic with the updated database schema, correctly representing providers without an assigned vendor.

src/repository/provider-endpoints.ts (3)

medium

Adding or to the drizzle-orm imports is necessary for the new backfillProviderVendorsFromProviders function, which uses or(isNull(providers.providerVendorId), eq(providers.providerVendorId, 0)) to identify providers needing a vendor association.

src/app/[locale]/settings/providers/_components/provider-vendor-view.tsx (230)

medium

Restricting the DeleteVendorDialog to vendorId > 0 is appropriate. The virtual 'orphaned' vendor group should not be deletable, as it's a logical grouping rather than a physical entity.

src/repository/provider.ts (136-150)

medium

Adding the if (created.providerVendorId) check before calling ensureProviderEndpointExistsForUrl is a good correctness improvement. It prevents attempts to create endpoints for providers that do not have a valid vendor ID, which is now possible with the nullable providerVendorId.

src/app/[locale]/settings/providers/_components/provider-vendor-view.tsx (195-197)

medium

This conditional rendering for displayName correctly displays 'Unknown Vendor' for the virtual orphaned group (vendorId === -1), improving the user experience and clarity for providers without an assigned vendor.

src/app/[locale]/settings/providers/_components/provider-vendor-view.tsx (115-128)

medium

The updated logic for grouping providers correctly handles cases where providerVendorId is null or 0 by assigning them to an 'orphaned' group with vendorId: -1. This ensures that all providers are displayed, even those not yet associated with a vendor, and aligns with the database schema change.

src/types/provider.ts (161)

medium

Updating providerVendorId to number | null in the ProviderDisplay interface is consistent with the schema and Provider interface changes, ensuring type consistency across the application.

tests/unit/lib/provider-endpoints/probe.test.ts (332)

medium

The test case name has been updated to reflect the new behavior where successful scheduled probes are always logged, which is consistent with the removal of the sampling logic.

tests/unit/lib/provider-endpoints/probe.test.ts (339)

medium

Removing snapshotMock is correct as the updateProviderEndpointProbeSnapshot function is no longer used in the probe logic.

tests/unit/lib/provider-endpoints/probe.test.ts (375)

medium

This assertion correctly verifies that recordMock (for recordProviderEndpointProbeResult) is now called for successful scheduled probes, confirming the removal of the filtering logic.

@ding113
ding113 merged commit 72c6341 into dev Jan 21, 2026
18 checks passed
@github-project-automation github-project-automation Bot moved this from Backlog to Done in Claude Code Hub Roadmap Jan 21, 2026

@github-actions github-actions 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.

Code Review Summary

No significant issues identified in this PR. The changes properly address the probe logging issue while maintaining code quality standards.

PR Size: L

  • Lines changed: 344 (237 additions, 107 deletions)
  • Files changed: 21

Split Suggestion: This PR could be split into two logical commits:

  1. Core probe logging fix (probe.ts, probe-log-cleanup.ts, tests, .env.example)
  2. Vendor backfill support for legacy data (schema.ts, repository files, UI components, i18n)

Review Coverage

  • Logic and correctness - Clean
  • Security (OWASP Top 10) - Clean
  • Error handling - Clean (all catch blocks properly log errors)
  • Type safety - Clean (nullable providerVendorId properly handled)
  • Documentation accuracy - Clean (comments match code, i18n properly implemented)
  • Test coverage - Adequate (probe tests updated to reflect new behavior)
  • Code clarity - Good

Key Changes Validated

  1. Probe logging: Removed filtering logic to record all probe results - correctly addresses the issue
  2. Error handling: All catch blocks use logger.warn/error appropriately
  3. Null safety: providerVendorId null checks added consistently across proxy logic
  4. Backfill logic: Vendor auto-creation from legacy providers is properly paginated and error-handled
  5. UI handling: Orphaned providers (vendorId = -1) are properly grouped and displayed
  6. i18n compliance: All user-facing strings use i18n (5 languages)

Automated review by Claude AI

stats.processed++;

const domainSource = row.websiteUrl?.trim() || row.url;
const domain = normalizeWebsiteDomainFromUrl(domainSource);

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] [LOGIC-BUG] Backfill treats invalid websiteUrl as fatal (even when url is usable)

src/repository/provider-endpoints.ts:288

Why this is a problem: The current code prefers websiteUrl unconditionally:
const domainSource = row.websiteUrl?.trim() || row.url;
If websiteUrl is non-empty but malformed, normalizeWebsiteDomainFromUrl(domainSource) returns null and the provider is counted as skippedInvalidUrl, leaving provider_vendor_id unset even though row.url may be a valid fallback. Additionally, getOrCreateProviderVendorIdFromUrls({ websiteUrl: row.websiteUrl }) will still throw for an invalid websiteUrl, preventing backfill.

Suggested fix:

const websiteDomain = row.websiteUrl ? normalizeWebsiteDomainFromUrl(row.websiteUrl) : null;
const providerDomain = normalizeWebsiteDomainFromUrl(row.url);
const domain = websiteDomain ?? providerDomain;

if (!domain) {
  // log + skippedInvalidUrl++
  ...
  continue;
}

const vendorId = await getOrCreateProviderVendorIdFromUrls({
  providerUrl: row.url,
  websiteUrl: websiteDomain ? row.websiteUrl : null,
  faviconUrl: row.faviconUrl ?? null,
  displayName: deriveDisplayNameFromDomain(domain),
});

* 按照 website_url(优先)或 url 的域名进行自动聚合
*/
export async function backfillProviderVendorsFromProviders(): Promise<{
processed: number;

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] [TEST-MISSING-CRITICAL] No unit coverage for backfillProviderVendorsFromProviders()

src/repository/provider-endpoints.ts:244

Why this is a problem: This PR adds a new startup backfill that reads/writes DB state (backfillProviderVendorsFromProviders() is invoked from src/instrumentation.ts). CLAUDE.md:13 requires: All new features must have unit test coverage of at least 80%. There are existing repository tests in tests/unit/repository/provider-endpoints.test.ts, but none cover this new backfill path (success update, invalid URL skip, error handling).

Suggested fix:

// tests/unit/repository/provider-endpoints.test.ts

test("backfillProviderVendorsFromProviders: updates providers with NULL vendor id", async () => {
  vi.resetModules();

  const providerRow = {
    id: 1,
    name: "OpenAI",
    url: "https://api.openai.com",
    websiteUrl: null,
    faviconUrl: null,
    providerVendorId: null,
  };

  // providers page -> vendor lookup (empty) -> providers end
  const selectPages = [[providerRow], [], []];
  const selectMock = vi.fn(() => createThenableQuery(selectPages.shift() ?? []));

  const returning = vi.fn(async () => [{ id: 99 }]);
  const onConflictDoNothing = vi.fn(() => ({ returning }));
  const values = vi.fn(() => ({ onConflictDoNothing }));
  const insertMock = vi.fn(() => ({ values }));

  const where = vi.fn(async () => []);
  const set = vi.fn(() => ({ where }));
  const updateMock = vi.fn(() => ({ set }));

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

  const { backfillProviderVendorsFromProviders } = await import("@/repository/provider-endpoints");
  const result = await backfillProviderVendorsFromProviders();

  expect(result.providersUpdated).toBe(1);
  expect(set).toHaveBeenCalledWith(expect.objectContaining({ providerVendorId: 99 }));
});

@github-actions github-actions 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.

Code Review Summary

This PR changes endpoint probe logging behavior and also introduces provider-vendor backfill + nullable providerVendorId handling across the UI/proxy layers. The probe change is straightforward, but the new vendor backfill path has a correctness gap and lacks unit coverage.

PR Size: L

  • Lines changed: 344
  • Files changed: 21
  • Split suggestion: Split into (1) probe logging + retention changes, (2) provider vendor nullability/backfill + UI/i18n updates.

Issues Found

Category Critical High Medium Low
Logic/Bugs 0 1 0 0
Security 0 0 0 0
Error Handling 0 0 0 0
Types 0 0 0 0
Comments/Docs 0 0 0 0
Tests 0 1 0 0
Simplification 0 0 0 0

Critical Issues (Must Fix)

None.

High Priority Issues (Should Fix)

  1. src/repository/provider-endpoints.ts: backfill skips valid providers when websiteUrl is malformed (Confidence: 90)
  2. src/repository/provider-endpoints.ts: missing unit coverage for new vendor backfill invoked on startup (Confidence: 85)

Review Coverage

  • Logic and correctness
  • Security (OWASP Top 10)
  • Error handling
  • Type safety
  • Documentation accuracy
  • Test coverage
  • Code clarity

Automated review by Codex AI

@github-actions github-actions 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.

  • Reviewed PR #636 and applied label size/L (344 lines changed across 21 files).
  • Posted 2 inline review comments on src/repository/provider-endpoints.ts:
    • Backfill logic bug: malformed websiteUrl can block backfill even when url is usable.
    • Missing unit coverage for the new backfillProviderVendorsFromProviders() startup behavior (per CLAUDE.md:13).
  • Submitted the required PR review summary via gh pr review --comment.

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

Labels

area:i18n area:provider area:UI bug Something isn't working size/L Large PR (< 1000 lines)

Projects

Status: Done

Development

Successfully merging this pull request may close these issues.

1 participant