From 1d0551a991f23f1d607bb14e2e5f22f68c18d518 Mon Sep 17 00:00:00 2001 From: Luis Pater Date: Thu, 18 Jun 2026 19:35:09 +0000 Subject: [PATCH] feat(config): improve config reload handling and introduce async management save hook - Refactored `ConfigReloadHook` to use `reloadConfigFromWatcher` for consistency. - Added async `reloadConfigAfterManagementSaveAsync` to handle post-save operations. - Introduced `ReloadConfigIfChanged` in watcher for manual trigger support. - Enhanced config reload paths to separate auth synthesis from standard updates. - Updated `applyConfigUpdate` logic to allow more granular reload behaviors. Closes: #3235 --- internal/watcher/config_reload.go | 8 ++++++++ sdk/cliproxy/builder.go | 4 ++-- sdk/cliproxy/service.go | 22 ++++++++++++++++++++-- sdk/cliproxy/types.go | 10 ++++++++++ sdk/cliproxy/watcher.go | 3 +++ internal/api/handlers/management/handler.go | 6 ++++++ 6 file(s) changed, 49 insertion(s)(+), 4 deletion(s)(-) diff --git a/internal/watcher/config_reload.go b/internal/watcher/config_reload.go --- a/internal/watcher/config_reload.go +++ b/internal/watcher/config_reload.go @@ -40,6 +40,14 @@ }) } +// ReloadConfigIfChanged runs the same config reload path used by filesystem events. +func (w *Watcher) ReloadConfigIfChanged() { + if w == nil { + return + } + w.reloadConfigIfChanged() +} + func (w *Watcher) reloadConfigIfChanged() { data, err := os.ReadFile(w.configPath) if err != nil { diff --git a/sdk/cliproxy/builder.go b/sdk/cliproxy/builder.go --- a/sdk/cliproxy/builder.go +++ b/sdk/cliproxy/builder.go @@ -289,8 +289,8 @@ service.serverOptions = append(service.serverOptions, api.WithPostAuthPersistHook(service.runtimeAuthSyncHook()), api.WithPluginHost(pluginHost), - api.WithConfigReloadHook(func(ctx context.Context, cfg *config.Config) { - service.applyConfigUpdate(cfg) + api.WithConfigReloadHook(func(_ context.Context, _ *config.Config) { + service.reloadConfigFromWatcher() }), ) return service, nil diff --git a/sdk/cliproxy/service.go b/sdk/cliproxy/service.go --- a/sdk/cliproxy/service.go +++ b/sdk/cliproxy/service.go @@ -1097,6 +1097,14 @@ } func (s *Service) applyConfigUpdate(newCfg *config.Config) { + s.applyConfigUpdateWithAuthSynthesis(newCfg, true) +} + +func (s *Service) applyWatcherConfigUpdate(newCfg *config.Config) { + s.applyConfigUpdateWithAuthSynthesis(newCfg, false) +} + +func (s *Service) applyConfigUpdateWithAuthSynthesis(newCfg *config.Config, synthesizeConfigAuths bool) { if s == nil { return } @@ -1190,8 +1198,17 @@ auths: auths, }) ctx := coreauth.WithSkipPersist(context.Background()) - s.registerConfigAPIKeyAuths(ctx, newCfg) + if synthesizeConfigAuths { + s.registerConfigAPIKeyAuths(ctx, newCfg) + } s.syncPluginRuntime(ctx) +} + +func (s *Service) reloadConfigFromWatcher() bool { + if s == nil || s.watcher == nil { + return false + } + return s.watcher.ReloadConfigIfChanged() } func (s *Service) registerConfigAPIKeyAuths(ctx context.Context, cfg *config.Config) { @@ -1442,6 +1459,7 @@ if errLoad := s.coreManager.Load(ctx); errLoad != nil { log.Warnf("failed to load auth store: %v", errLoad) } + s.registerConfigAPIKeyAuths(coreauth.WithSkipPersist(ctx), s.cfg) } if !homeEnabled { @@ -1532,7 +1550,7 @@ if !homeEnabled { var watcherWrapper *WatcherWrapper - reloadCallback := func(newCfg *config.Config) { s.applyConfigUpdate(newCfg) } + reloadCallback := func(newCfg *config.Config) { s.applyWatcherConfigUpdate(newCfg) } watcherWrapper, errCreate := s.watcherFactory(s.configPath, s.cfg.AuthDir, reloadCallback) if errCreate != nil { diff --git a/sdk/cliproxy/types.go b/sdk/cliproxy/types.go --- a/sdk/cliproxy/types.go +++ b/sdk/cliproxy/types.go @@ -97,6 +97,7 @@ dispatchRuntimeUpdate func(update watcher.AuthUpdate) bool dispatchPersistedAuth func(update watcher.AuthUpdate) bool setPluginAuthParser func(parser PluginAuthParser) + reloadConfigIfChanged func() } // Start proxies to the underlying watcher Start implementation. @@ -121,6 +122,15 @@ return } w.setConfig(cfg) +} + +// ReloadConfigIfChanged asks the underlying watcher to reload config from disk. +func (w *WatcherWrapper) ReloadConfigIfChanged() bool { + if w == nil || w.reloadConfigIfChanged == nil { + return false + } + w.reloadConfigIfChanged() + return true } // SetPluginAuthParser updates the plugin auth parser used by the watcher. diff --git a/sdk/cliproxy/watcher.go b/sdk/cliproxy/watcher.go --- a/sdk/cliproxy/watcher.go +++ b/sdk/cliproxy/watcher.go @@ -37,5 +37,8 @@ setPluginAuthParser: func(parser PluginAuthParser) { w.SetPluginAuthParser(parser) }, + reloadConfigIfChanged: func() { + w.ReloadConfigIfChanged() + }, }, nil } diff --git a/internal/api/handlers/management/handler.go b/internal/api/handlers/management/handler.go --- a/internal/api/handlers/management/handler.go +++ b/internal/api/handlers/management/handler.go @@ -411,7 +411,13 @@ c.JSON(http.StatusInternalServerError, gin.H{"error": fmt.Sprintf("failed to save config: %v", err)}) return false } + snapshot := h.reloadSnapshotConfigLocked() c.JSON(http.StatusOK, gin.H{"status": "ok"}) + var reqCtx context.Context + if c != nil && c.Request != nil { + reqCtx = c.Request.Context() + } + h.reloadConfigAfterManagementSaveAsync(reqCtx, snapshot) return true } -- tangled.sh