diff --git a/community/rfc/2025-01-10-service-webhook-auto-repair.md b/community/rfc/2025-01-10-service-webhook-auto-repair.md
new file mode 100644
index 0000000000..3830508e00
--- /dev/null
+++ b/community/rfc/2025-01-10-service-webhook-auto-repair.md
@@ -0,0 +1,428 @@
+# Service Webhook 自动修复机制
+
+- Author: 夏佳怡
+- Issue: 服务模板 webhook 被手动删除后无法自动恢复
+- Date: 2025-10-10
+- Reviewer:
+- Review status: pending
+
+## Objective
+
+当服务模板(Service Template)的 webhook 在代码仓库(GitLab/GitHub/Gerrit/Gitee)中被手动删除后,系统能够在下次更新服务时自动检测并重建 webhook,确保 webhook 功能的持续可用性。
+
+## Background
+
+### 当前问题
+
+Zadig 在添加服务时会在对应的代码仓库创建 webhook,用于监听代码变更并触发相应的工作流。但存在以下问题:
+
+1. **问题根源**: 当 webhook 在代码仓库中被手动删除后(如清理、误删、仓库迁移等场景),系统无法自动恢复
+2. **现有机制缺陷**: 更新服务时,系统只对比数据库中的 webhook 记录与期望的记录,而不检查代码仓库中 webhook 的实际状态
+3. **影响**: webhook 失效后,代码变更无法触发工作流,用户需要手动删除服务并重新创建才能恢复
+
+### 当前实现
+
+服务更新调用链:
+```
+PUT /api/aslan/service/loader/load/:codehostId
+ → SyncServiceTemplate (handler)
+ → LoadServiceFromCodeHost (service layer)
+ → loadService / loadGerritService / loadGiteeService
+ → CreateServiceTemplate
+ → ProcessServiceWebhook
+ → ProcessWebhook (只对比数据库记录,不验证仓库实际状态)
+```
+
+### 重要发现
+
+系统已经在 `webhook` MongoDB 集合中存储了完整的 webhook 信息:
+
+```go
+type WebHook struct {
+ ID primitive.ObjectID `bson:"_id,omitempty"`
+ Owner string `bson:"owner"`
+ Repo string `bson:"repo"`
+ Address string `bson:"address"`
+ HookID string `bson:"hook_id,omitempty"` // Webhook 在 Git 平台的实际 ID
+ References []string `bson:"references"` // 引用此 webhook 的服务列表
+}
+```
+
+这意味着无需修改 Service 模型,可以直接利用现有的 `HookID` 字段进行验证。
+
+## Design overview
+
+### 核心思路
+
+在更新服务时,增加 webhook 存在性验证环节:
+1. 从数据库查询 webhook 记录及其 HookID
+2. 调用 Git 平台 API 验证 webhook 是否真实存在
+3. 如果不存在,自动重建 webhook
+
+### 关键约束
+
+1. **非侵入性**: 验证失败不应影响服务更新主流程
+2. **执行时机**: 仅在**更新**服务时验证(不在创建时验证,因为创建时 webhook 是新建的)
+3. **异步执行**: 使用 goroutine 异步执行验证,不阻塞服务更新响应
+4. **多平台支持**: 支持 GitHub、GitLab、Gerrit、Gitee 公有云、Gitee 私有部署
+
+### 成功标准
+
+1. 用户更新服务时,如果 webhook 已被删除,系统能自动重建
+2. 重建过程对用户透明,无需手动干预
+3. 验证/重建失败不影响服务更新流程
+4. 所有代码托管平台(GitHub、GitLab、Gerrit、Gitee 公有云、Gitee 私有部署)均正常工作
+
+## Detailed design
+
+### 整体流程
+
+```
+更新服务操作
+ ↓
+ProcessServiceWebhook (现有逻辑)
+ ├─ ProcessWebhook (现有: 对比数据库记录,增删 webhook)
+ └─ [新增] 验证并修复 webhook
+ ├─ 查询数据库中的 webhook 记录(获取 HookID)
+ ├─ 调用 Git 平台 API 验证 webhook 是否存在
+ └─ 如果不存在: 删除旧记录 → 重新创建 webhook
+```
+
+### 实现步骤
+
+#### 1. 为各 Git 平台客户端添加验证接口
+
+**GitLab** (`pkg/microservice/aslan/core/common/service/gitlab/webhook.go`):
+```go
+func (c *Client) WebHookExists(owner, repo, hookID string) (bool, error) {
+ if hookID == "" {
+ return false, nil
+ }
+
+ hookIDInt, err := strconv.Atoi(hookID)
+ if err != nil {
+ return false, err
+ }
+
+ _, err = c.GetProjectHook(owner, repo, hookIDInt)
+ if err != nil {
+ // 404 错误说明 webhook 不存在
+ return false, nil
+ }
+ return true, nil
+}
+```
+
+#### 2. 修改 ProcessServiceWebhook
+
+**位置**: `pkg/microservice/aslan/core/common/service/service.go:752`
+
+```go
+func ProcessServiceWebhook(updated, current *commonmodels.Service, serviceName string, production bool, logger *zap.SugaredLogger) {
+ // ... 现有逻辑保持不变 ...
+
+ err := ProcessWebhook(updatedHooks, currentHooks, name, logger)
+ if err != nil {
+ logger.Errorf("Failed to process WebHook, error: %s", err)
+ }
+
+ // 新增: 仅在更新服务时验证并修复 webhook
+ // 异步执行,不阻塞主流程,失败也不影响服务更新
+ if updated != nil && current != nil {
+ go func(svc *commonmodels.Service, name string, prod bool, log *zap.SugaredLogger) {
+ if err := VerifyAndRepairWebhook(svc, name, prod, log); err != nil {
+ log.Warnf("Failed to verify and repair webhook for service %s, error: %s", name, err)
+ }
+ }(updated, serviceName, production, logger)
+ }
+}
+```
+
+**关键点**:
+- 条件 `updated != nil && current != nil` 确保只在更新服务时执行
+- **异步执行 goroutine**: 不阻塞服务更新主流程,提升响应速度
+- 验证失败只记录 Warning,不影响主流程
+- 在 ProcessWebhook 之后调用,确保主流程的增删操作已完成
+- 通过参数传递避免闭包捕获变量的并发问题
+
+#### 3. 实现验证修复函数
+
+**位置**: `pkg/microservice/aslan/core/common/service/service.go`
+
+```go
+func VerifyAndRepairWebhook(service *commonmodels.Service, serviceName string, production bool, logger *zap.SugaredLogger) error {
+ if !needProcessWebhook(service.Source) {
+ return nil
+ }
+
+ // 1. 根据 Service 查询 webhook 记录
+ namespace := service.GetRepoNamespace()
+ repoName := service.RepoName
+ address, err := GetGitlabAddress(service.SrcPath)
+ if err != nil || address == "" {
+ return fmt.Errorf("failed to get gitlab address: %w", err)
+ }
+
+ // 2. 查询数据库中的 webhook (包含 HookID)
+ webhookRecord, err := mongodb.NewWebHookColl().Find(namespace, repoName, address)
+ if err != nil {
+ logger.Warnf("Failed to find webhook record in database: %v", err)
+ return nil // 数据库没有记录,说明未创建过,无需修复
+ }
+
+ if webhookRecord.HookID == "" {
+ logger.Warnf("Webhook record exists but has no HookID, skipping verification")
+ return nil
+ }
+
+ // 3. 获取 codehost 配置
+ ch, err := systemconfig.New().GetCodeHost(service.CodehostID)
+ if err != nil {
+ return fmt.Errorf("failed to get codehost: %w", err)
+ }
+
+ // 4. 验证 webhook 是否存在
+ exists, err := checkWebhookExists(ch, service, webhookRecord.HookID, logger)
+ if err != nil {
+ logger.Warnf("Failed to check webhook existence: %v", err)
+ return err
+ }
+
+ // 5. 如果不存在,重新创建
+ if !exists {
+ logger.Infof("Webhook not found in repository (HookID: %s), recreating for service %s",
+ webhookRecord.HookID, serviceName)
+
+ name := webhook.ServicePrefix + serviceName
+ if production {
+ name = webhook.ServicePrefix + "production-" + serviceName
+ }
+
+ // 删除旧记录
+ err = mongodb.NewWebHookColl().Delete(namespace, repoName, address)
+ if err != nil {
+ logger.Warnf("Failed to delete old webhook record: %v", err)
+ }
+
+ // 重新创建
+ err = webhook.NewClient().AddWebHook(&webhook.TaskOption{
+ ID: ch.ID,
+ Owner: service.RepoOwner,
+ Namespace: namespace,
+ Repo: repoName,
+ Address: ch.Address,
+ Token: ch.AccessToken,
+ Ref: name,
+ AK: ch.AccessKey,
+ SK: ch.SecretKey,
+ Region: ch.Region,
+ From: ch.Type,
+ })
+ if err != nil {
+ return fmt.Errorf("failed to recreate webhook: %w", err)
+ }
+ logger.Infof("Successfully recreated webhook for service %s", serviceName)
+ }
+
+ return nil
+}
+
+func checkWebhookExists(ch *systemconfig.CodeHost, service *commonmodels.Service, hookID string, logger *zap.SugaredLogger) (bool, error) {
+ namespace := service.GetRepoNamespace()
+ repo := service.RepoName
+
+ switch ch.Type {
+ case setting.SourceFromGitlab:
+ client, err := gitlabservice.NewClient(ch.ID, ch.Address, ch.AccessToken,
+ config.ProxyHTTPSAddr(), ch.EnableProxy, ch.DisableSSL)
+ if err != nil {
+ return false, fmt.Errorf("failed to create gitlab client: %w", err)
+ }
+ return client.WebHookExists(namespace, repo, hookID)
+
+ case setting.SourceFromGithub:
+ client := githubservice.NewClient(ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy)
+ return client.WebHookExists(namespace, repo, hookID)
+
+ case setting.SourceFromGerrit:
+ client := gerrit.NewClient(ch.Address, ch.AccessToken, config.ProxyHTTPSAddr(), ch.EnableProxy)
+ remoteName := service.GerritRemoteName
+ if remoteName == "" {
+ remoteName = "origin"
+ }
+ return client.WebHookExists(repo, remoteName, hookID)
+
+ case setting.SourceFromGitee, setting.SourceFromGiteeEE:
+ client := giteeservice.NewClient(ch.ID, ch.Address, ch.AccessToken,
+ config.ProxyHTTPSAddr(), ch.EnableProxy)
+ return client.WebHookExists(namespace, repo, hookID)
+
+ default:
+ logger.Warnf("Unsupported code source type: %s", ch.Type)
+ return true, nil // 不支持的类型默认认为存在
+ }
+}
+```
+
+### API changes
+
+无需修改 API。此功能在服务更新的现有流程中自动执行。
+
+### Database changes
+
+无需修改数据库。利用现有的 `webhook` 集合和 `HookID` 字段。
+
+### Backward compatibility consideration
+
+#### 完全向后兼容
+
+1. **不影响现有流程**:
+ - 验证逻辑作为可选增强,失败不影响服务更新
+ - 所有现有功能保持不变
+
+2. **数据兼容性**:
+ - 使用现有的 webhook 表结构,无需迁移
+ - 如果 HookID 为空(老数据),自动跳过验证
+
+3. **行为兼容性**:
+ - 只在更新服务时执行验证(不在创建时)
+ - 不改变任何现有的 webhook 创建/删除逻辑
+
+4. **回滚方案**:
+ - 如果出现问题,可以通过代码开关禁用验证逻辑
+ - 原有的 ProcessWebhook 逻辑完全保留
+
+### Performance consideration
+
+1. **API 调用开销**:
+ - 每次更新服务增加 1 次 Git 平台 API 调用(验证 webhook)
+ - **异步执行**: 不阻塞服务更新响应,用户无感知
+ - API 验证通常响应时间 < 100ms
+
+2. **失败场景**:
+ - 需要重建时增加 2 次 API 调用(删除 + 创建)
+ - 这是低频操作,只在 webhook 被删除时发生
+ - 异步执行不影响用户体验
+
+3. **并发考虑**:
+ - 使用 goroutine 异步执行,避免阻塞主线程
+ - 通过参数传递避免闭包变量竞争
+ - 每个服务的验证操作是独立的,互不干扰
+
+### Security and privacy consideration
+
+1. **权限控制**:
+ - 使用系统已配置的 codehost token
+ - 不引入新的权限要求
+
+2. **错误处理**:
+ - API 调用失败不暴露敏感信息
+ - 只记录必要的日志信息
+
+3. **数据安全**:
+ - 不修改任何敏感数据
+ - webhook token 的处理与现有逻辑一致
+
+## Implementation plan
+
+### Phase 1: 核心功能实现(预计 2-3 天)
+
+1. 为 GitLab/GitHub/Gerrit/Gitee 客户端添加 `WebHookExists` 方法
+2. 实现 `VerifyAndRepairWebhook` 和 `checkWebhookExists` 函数
+3. 修改 `ProcessServiceWebhook` 调用验证逻辑
+
+### Phase 2: 测试验证(预计 2 天)
+
+1. **单元测试**:
+ - 测试各平台的 WebHookExists 方法
+ - 测试验证修复逻辑的各种场景
+
+2. **集成测试**:
+ - 在测试环境创建服务
+ - 手动删除 webhook
+ - 更新服务验证自动重建
+
+3. **多平台测试**:
+ - GitHub: GitHub.com
+ - GitLab: 自托管 + GitLab.com
+ - Gerrit: 自托管 Gerrit 服务器
+ - Gitee 公有云: Gitee.com
+ - Gitee 私有部署: 企业版 Gitee
+
+### Phase 3: 文档和发布(预计 1 天)
+
+1. 更新用户文档,说明自动修复功能
+2. 准备 changelog
+3. 发布 PR
+
+## Testing plan
+
+### 单元测试
+
+```go
+func TestWebHookExists(t *testing.T) {
+ // 测试 webhook 存在的情况
+ // 测试 webhook 不存在的情况
+ // 测试 hookID 为空的情况
+ // 测试 API 调用失败的情况
+}
+
+func TestVerifyAndRepairWebhook(t *testing.T) {
+ // 测试数据库无记录的情况
+ // 测试 HookID 为空的情况
+ // 测试 webhook 存在,无需修复
+ // 测试 webhook 不存在,需要重建
+}
+```
+
+### 集成测试场景
+
+| 场景 | 操作步骤 | 预期结果 |
+|------|---------|---------|
+| 创建新服务 | 创建服务 | webhook 正常创建,不触发验证 |
+| 更新服务(webhook 正常) | 更新服务 | 验证通过,无额外操作 |
+| 更新服务(webhook 被删) | 1. 创建服务
2. 手动删除仓库中的 webhook
3. 更新服务 | 自动检测并重建 webhook |
+| Webhook 记录异常 | HookID 为空或记录不存在 | 跳过验证,不影响更新 |
+
+### 性能测试
+
+- 测试 100 个服务并发更新的响应时间
+- 确保 API 调用不会成为性能瓶颈
+
+## Open questions
+
+1. **API 限流**: 各 Git 平台都有 API rate limit,大规模使用时是否需要考虑?
+ - **答**: 当前方案只在更新服务时验证,属于低频操作,暂不需要额外限流措施
+
+2. **webhook 共享**: 一个 webhook 可能被多个服务引用,重建时如何处理?
+ - **答**: 系统已通过 References 数组管理共享 webhook,重建时会正确处理引用关系
+
+3. **Gerrit webhook 机制**: Gerrit 的 webhook 使用 remoteName 标识,与其他平台有差异,如何统一处理?
+ - **答**: Gerrit 已纳入支持范围,通过 Service.GerritRemoteName 字段获取 remoteName(默认"origin"),在 checkWebhookExists 中针对 Gerrit 做特殊处理
+
+## Alternatives considered
+
+### 方案 A: 定时任务巡检
+
+**优点**: 集中处理,可以发现所有失效的 webhook
+**缺点**:
+- 有时间延迟,可能几小时后才能修复
+- 增加系统后台负担
+- 批量 API 调用可能触发限流
+
+**结论**: 不采用。实时修复更符合用户期望。
+
+### 方案 B: 强制刷新模式
+
+**优点**: 实现简单,用户可控
+**缺点**:
+- 需要用户主动操作,体验不佳
+- 需要修改 UI 和 API
+
+**结论**: 可作为补充方案,但不作为主要方案。
+
+## References
+
+- Webhook 表定义: `pkg/microservice/aslan/core/common/repository/models/webhook.go`
+- 当前 webhook 处理: `pkg/microservice/aslan/core/common/service/service.go:752`
+- 服务更新流程: `pkg/microservice/aslan/core/service/service/loader.go:76`