@@ -375,3 +375,138 @@ func TestReloadFromDisk_UsesNewConfigValues(t *testing.T) {
375375 require.Equal(t, "anthropic", store.config.Models[SelectedModelTypeLarge].Provider)
376376 require.Equal(t, "claude-3", store.config.Models[SelectedModelTypeLarge].Model)
377377}
378+
379+// TestSetConfigField_AutoReloads verifies that SetConfigField automatically
380+// reloads config into memory after writing, so subsequent reads see the new value.
381+func TestSetConfigField_AutoReloads(t *testing.T) {
382+ t.Parallel()
383+
384+ dir := t.TempDir()
385+ configPath := filepath.Join(dir, "crush.json")
386+
387+ // Create initial config file with debug = false
388+ initialConfig := `{"options": {"debug": false}}`
389+ require.NoError(t, os.WriteFile(configPath, []byte(initialConfig), 0o600))
390+
391+ // Load initial config
392+ store, err := Load(dir, dir, false)
393+ require.NoError(t, err)
394+
395+ // Verify initial state
396+ require.False(t, store.config.Options.Debug)
397+
398+ // Set globalDataPath and capture snapshot for staleness tracking
399+ store.globalDataPath = configPath
400+ store.CaptureStalenessSnapshot([]string{configPath})
401+
402+ // Use SetConfigField to change debug to true
403+ err = store.SetConfigField(ScopeGlobal, "options.debug", true)
404+ require.NoError(t, err)
405+
406+ // Verify in-memory state was automatically reloaded and reflects the change
407+ require.True(t, store.config.Options.Debug, "Expected config to auto-reload and show debug = true")
408+
409+ // Verify staleness is clean after the reload
410+ staleness := store.ConfigStaleness()
411+ require.False(t, staleness.Dirty, "Expected staleness to be clean after auto-reload")
412+}
413+
414+// TestRemoveConfigField_AutoReloads verifies that RemoveConfigField automatically
415+// reloads config into memory after writing.
416+func TestRemoveConfigField_AutoReloads(t *testing.T) {
417+ t.Parallel()
418+
419+ dir := t.TempDir()
420+ configPath := filepath.Join(dir, "crush.json")
421+
422+ // Create initial config file with a custom option
423+ initialConfig := `{"options": {"debug": true, "custom_field": "value"}}`
424+ require.NoError(t, os.WriteFile(configPath, []byte(initialConfig), 0o600))
425+
426+ // Load initial config
427+ store, err := Load(dir, dir, false)
428+ require.NoError(t, err)
429+
430+ // Set globalDataPath and capture snapshot
431+ store.globalDataPath = configPath
432+ store.CaptureStalenessSnapshot([]string{configPath})
433+
434+ // Verify the field exists initially (indirectly - store loaded successfully)
435+ require.True(t, store.config.Options.Debug)
436+
437+ // Remove the debug field
438+ err = store.RemoveConfigField(ScopeGlobal, "options.debug")
439+ require.NoError(t, err)
440+
441+ // Verify auto-reload occurred and stale state is clean
442+ staleness := store.ConfigStaleness()
443+ require.False(t, staleness.Dirty, "Expected staleness to be clean after auto-reload from RemoveConfigField")
444+}
445+
446+// TestSetConfigField_AutoReloadSkipsWhenNoWorkingDir verifies that auto-reload
447+// gracefully skips when working directory is not set (e.g., during testing).
448+func TestSetConfigField_AutoReloadSkipsWhenNoWorkingDir(t *testing.T) {
449+ t.Parallel()
450+
451+ dir := t.TempDir()
452+ configPath := filepath.Join(dir, "crush.json")
453+
454+ // Create a store without working directory (like some test setups)
455+ store := &ConfigStore{
456+ config: &Config{},
457+ globalDataPath: configPath,
458+ // workingDir is empty
459+ }
460+
461+ // SetConfigField should succeed even without workingDir (auto-reload skips)
462+ err := store.SetConfigField(ScopeGlobal, "foo", "bar")
463+ require.NoError(t, err)
464+
465+ // Verify file was still written
466+ data, err := os.ReadFile(configPath)
467+ require.NoError(t, err)
468+ require.Contains(t, string(data), "foo")
469+}
470+
471+// TestAutoReloadDisabledDuringReload verifies that auto-reload is suppressed
472+// during ReloadFromDisk to prevent re-entrant/nested reload calls.
473+func TestAutoReloadDisabledDuringReload(t *testing.T) {
474+ t.Parallel()
475+
476+ dir := t.TempDir()
477+ configPath := filepath.Join(dir, "crush.json")
478+
479+ // Create initial config with a provider that will trigger config modification during reload
480+ // (simulating the anthropic OAuth token removal case)
481+ initialConfig := `{
482+ "providers": {
483+ "anthropic": {
484+ "api_key": "test-key",
485+ "oauth": {"access_token": "token", "refresh_token": "refresh"}
486+ }
487+ }
488+ }`
489+ require.NoError(t, os.WriteFile(configPath, []byte(initialConfig), 0o600))
490+
491+ // Load will trigger configureProviders which removes anthropic OAuth config
492+ // This should NOT cause infinite recursion thanks to autoReloadDisabled guard
493+ store, err := Load(dir, dir, false)
494+ require.NoError(t, err)
495+
496+ // Verify the store loaded successfully and autoReloadDisabled was unset
497+ require.False(t, store.autoReloadDisabled)
498+
499+ // Capture snapshot and verify reload also works without recursion
500+ store.globalDataPath = configPath
501+ store.CaptureStalenessSnapshot([]string{configPath})
502+
503+ // Modify file and reload - this should work without re-entrancy issues
504+ time.Sleep(10 * time.Millisecond)
505+ require.NoError(t, os.WriteFile(configPath, []byte(`{"options": {"debug": true}}`), 0o600))
506+
507+ err = store.ReloadFromDisk(context.Background())
508+ require.NoError(t, err)
509+
510+ // Verify reload completed successfully
511+ require.False(t, store.autoReloadDisabled, "autoReloadDisabled should be false after ReloadFromDisk")
512+}