@@ -1,7 +1,6 @@
11package config
22
33import (
4− "cmp"
54 "context"
65 "encoding/json"
76 "fmt"
@@ -127,9 +126,18 @@ func (s *ConfigStore) HasConfigField(scope Scope, key string) bool {
127126// After a successful write, it automatically reloads config to keep in-memory
128127// state fresh.
129128func (s *ConfigStore) SetConfigField(scope Scope, key string, value any) error {
129+ return s.SetConfigFields(scope, map[string]any{key: value})
130+}
131+
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 {
130138 path, err := s.configPath(scope)
131139 if err != nil {
132− return fmt.Errorf("%s: %w", key, err)
140+ return fmt.Errorf("%v: %w", kv, err)
133141 }
134142 data, err := os.ReadFile(path)
135143 if err != nil {
@@ -140,9 +148,12 @@ func (s *ConfigStore) SetConfigField(scope Scope, key string, value any) error {
140148 }
141149 }
142150
143− newValue, err := sjson.Set(string(data), key, value)
144− if err != nil {
145− return fmt.Errorf("failed to set config field %s: %w", key, err)
151+ newValue := string(data)
152+ for key, value := range kv {
153+ newValue, err = sjson.Set(newValue, key, value)
154+ if err != nil {
155+ return fmt.Errorf("failed to set config field %s: %w", key, err)
156+ }
146157 }
147158 if err := os.MkdirAll(filepath.Dir(path), 0o755); err != nil {
148159 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
238249 }
239250 setKeyOrToken = func() { providerConfig.APIKey = v }
240251 case *oauth.Token:
241− if err := cmp.Or(
242− s.SetConfigField(scope, fmt.Sprintf("providers.%s.api_key", providerID), v.AccessToken),
243− s.SetConfigField(scope, fmt.Sprintf("providers.%s.oauth", providerID), v),
244− ); err != nil {
252+ if err := s.SetConfigFields(scope, map[string]any{
253+ fmt.Sprintf("providers.%s.api_key", providerID): v.AccessToken,
254+ fmt.Sprintf("providers.%s.oauth", providerID): v,
255+ }); err != nil {
245256 return err
246257 }
247258 setKeyOrToken = func() {
@@ -343,10 +354,10 @@ func (s *ConfigStore) RefreshOAuthToken(ctx context.Context, scope Scope, provid
343354
344355 s.config.Providers.Set(providerID, providerConfig)
345356
346− if err := cmp.Or(
347− s.SetConfigField(scope, fmt.Sprintf("providers.%s.api_key", providerID), refreshedToken.AccessToken),
348− s.SetConfigField(scope, fmt.Sprintf("providers.%s.oauth", providerID), refreshedToken),
349− ); err != nil {
357+ if err := s.SetConfigFields(scope, map[string]any{
358+ fmt.Sprintf("providers.%s.api_key", providerID): refreshedToken.AccessToken,
359+ fmt.Sprintf("providers.%s.oauth", providerID): refreshedToken,
360+ }); err != nil {
350361 return fmt.Errorf("failed to persist refreshed token: %w", err)
351362 }
352363
@@ -460,10 +471,10 @@ func (s *ConfigStore) ImportCopilot() (*oauth.Token, bool) {
460471 return token, false
461472 }
462473
463− if err := cmp.Or(
464− s.SetConfigField(ScopeGlobal, "providers.copilot.api_key", token.AccessToken),
465− s.SetConfigField(ScopeGlobal, "providers.copilot.oauth", token),
466− ); err != nil {
474+ if err := s.SetConfigFields(ScopeGlobal, map[string]any{
475+ "providers.copilot.api_key": token.AccessToken,
476+ "providers.copilot.oauth": token,
477+ }); err != nil {
467478 slog.Error("Unable to save GitHub Copilot token to disk", "error", err)
468479 }
469480