@@ -1,7 +1,6 @@
1package config
1package config
6 "encoding/json"
5 "encoding/json"
@@ -127,9 +126,18 @@ func (s *ConfigStore) HasConfigField(scope Scope, key string) bool {
127// After a successful write, it automatically reloads config to keep in-memory
126// After a successful write, it automatically reloads config to keep in-memory
128// state fresh.
127// state fresh.
129func (s *ConfigStore) SetConfigField(scope Scope, key string, value any) error {
128func (s *ConfigStore) SetConfigField(scope Scope, key string, value any) error {
129+ return s.SetConfigFields(scope, map[string]any{key: value})
132+// SetConfigFields sets multiple key/value pairs in the config file for the given
133+// scope in a single write. After a successful write, it automatically reloads
134+// config to keep in-memory state fresh. This is preferred over multiple
135+// SetConfigField calls when writing several fields atomically to avoid
136+// intermediate reloads with partial state.
137+func (s *ConfigStore) SetConfigFields(scope Scope, kv map[string]any) error {
130 path, err := s.configPath(scope)
138 path, err := s.configPath(scope)
131 if err != nil {
139 if err != nil {
132− return fmt.Errorf("%s: %w", key, err)
140+ return fmt.Errorf("%v: %w", kv, err)
134 data, err := os.ReadFile(path)
142 data, err := os.ReadFile(path)
135 if err != nil {
143 if err != nil {
@@ -140,9 +148,12 @@ func (s *ConfigStore) SetConfigField(scope Scope, key string, value any) error {
143− newValue, err := sjson.Set(string(data), key, value)
151+ newValue := string(data)
152+ for key, value := range kv {
153+ newValue, err = sjson.Set(newValue, key, value)
144− if err != nil {
154+ if err != nil {
145− return fmt.Errorf("failed to set config field %s: %w", key, err)
155+ return fmt.Errorf("failed to set config field %s: %w", key, err)
147 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
158 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
148 return fmt.Errorf("failed to create config directory %q: %w", path, err)
159 return fmt.Errorf("failed to create config directory %q: %w", path, err)
@@ -238,10 +249,10 @@ func (s *ConfigStore) SetProviderAPIKey(scope Scope, providerID string, apiKey a
239 setKeyOrToken = func() { providerConfig.APIKey = v }
250 setKeyOrToken = func() { providerConfig.APIKey = v }
240 case *oauth.Token:
251 case *oauth.Token:
241− if err := cmp.Or(
252+ if err := s.SetConfigFields(scope, map[string]any{
242− s.SetConfigField(scope, fmt.Sprintf("providers.%s.api_key", providerID), v.AccessToken),
253+ fmt.Sprintf("providers.%s.api_key", providerID): v.AccessToken,
243− s.SetConfigField(scope, fmt.Sprintf("providers.%s.oauth", providerID), v),
254+ fmt.Sprintf("providers.%s.oauth", providerID): v,
244− ); err != nil {
255+ }); err != nil {
245 return err
256 return err
247 setKeyOrToken = func() {
258 setKeyOrToken = func() {
@@ -343,10 +354,10 @@ func (s *ConfigStore) RefreshOAuthToken(ctx context.Context, scope Scope, provid
344 s.config.Providers.Set(providerID, providerConfig)
355 s.config.Providers.Set(providerID, providerConfig)
346− if err := cmp.Or(
357+ if err := s.SetConfigFields(scope, map[string]any{
347− s.SetConfigField(scope, fmt.Sprintf("providers.%s.api_key", providerID), refreshedToken.AccessToken),
358+ fmt.Sprintf("providers.%s.api_key", providerID): refreshedToken.AccessToken,
348− s.SetConfigField(scope, fmt.Sprintf("providers.%s.oauth", providerID), refreshedToken),
359+ fmt.Sprintf("providers.%s.oauth", providerID): refreshedToken,
349− ); err != nil {
360+ }); err != nil {
350 return fmt.Errorf("failed to persist refreshed token: %w", err)
361 return fmt.Errorf("failed to persist refreshed token: %w", err)
@@ -460,10 +471,10 @@ func (s *ConfigStore) ImportCopilot() (*oauth.Token, bool) {
460 return token, false
471 return token, false
463− if err := cmp.Or(
474+ if err := s.SetConfigFields(ScopeGlobal, map[string]any{
464− s.SetConfigField(ScopeGlobal, "providers.copilot.api_key", token.AccessToken),
475+ "providers.copilot.api_key": token.AccessToken,
465− s.SetConfigField(ScopeGlobal, "providers.copilot.oauth", token),
476+ "providers.copilot.oauth": token,
466− ); err != nil {
477+ }); err != nil {
467 slog.Error("Unable to save GitHub Copilot token to disk", "error", err)
478 slog.Error("Unable to save GitHub Copilot token to disk", "error", err)