on main help sign in
fix(config): individual errors on json parse
commit61a9fcedcbaea1bceb66a2ad70cfeb66d60c8579 authorKieran Klukas <kieran@dunkirk.sh> dateMon 11 May 2026, 15:45 (5 months ago) parent3b0d5de
3 files changed+41
internal/config/load.go+6 internal/config/load_test.go+32 internal/config/store.go+3
internal/config/load.go+6
@@ -55,6 +55,9 @@ func Load(workingDir, dataDir string, debug bool) (*ConfigStore, error) {
5555
5656 // Load workspace config last so it has highest priority.
5757 if wsData, err := os.ReadFile(store.workspacePath); err == nil && len(wsData) > 0 {
58+ if !json.Valid(wsData) {
59+ return nil, fmt.Errorf("invalid JSON in config file %s", store.workspacePath)
60+ }
5861 merged, mergeErr := loadFromBytes(append([][]byte{mustMarshalConfig(cfg)}, wsData))
5962 if mergeErr == nil {
6063 // Preserve defaults that setDefaults already applied.
@@ -734,6 +737,9 @@ func loadFromConfigPaths(configPaths []string) (*Config, []string, error) {
734737 if len(data) == 0 {
735738 continue
736739 }
740+ if !json.Valid(data) {
741+ return nil, nil, fmt.Errorf("invalid JSON in config file %s", path)
742+ }
737743 configs = append(configs, data)
738744 loaded = append(loaded, path)
739745 }
internal/config/load_test.go+32
@@ -36,6 +36,38 @@ func TestConfig_LoadFromBytes(t *testing.T) {
3636 require.Equal(t, "https://api.openai.com/v2", pc.BaseURL)
3737}
3838
39+func TestLoadFromConfigPaths_InvalidJSON(t *testing.T) {
40+ t.Parallel()
41+
42+ t.Run("identifies the offending file", func(t *testing.T) {
43+ t.Parallel()
44+ tmpDir := t.TempDir()
45+ good := filepath.Join(tmpDir, "good.json")
46+ bad := filepath.Join(tmpDir, "bad.json")
47+ require.NoError(t, os.WriteFile(good, []byte(`{"providers":{}}`), 0o644))
48+ require.NoError(t, os.WriteFile(bad, []byte(`{not valid json}`), 0o644))
49+
50+ _, _, err := loadFromConfigPaths([]string{good, bad})
51+ require.Error(t, err)
52+ require.Contains(t, err.Error(), "invalid JSON in config file")
53+ require.Contains(t, err.Error(), "bad.json")
54+ })
55+
56+ t.Run("skips missing and empty files", func(t *testing.T) {
57+ t.Parallel()
58+ tmpDir := t.TempDir()
59+ empty := filepath.Join(tmpDir, "empty.json")
60+ require.NoError(t, os.WriteFile(empty, []byte(""), 0o644))
61+
62+ cfg, _, err := loadFromConfigPaths([]string{
63+ filepath.Join(tmpDir, "nonexistent.json"),
64+ empty,
65+ })
66+ require.NoError(t, err)
67+ require.NotNil(t, cfg)
68+ })
69+}
70+
3971// testStore wraps a Config in a minimal ConfigStore for testing.
4072func testStore(cfg *Config) *ConfigStore {
4173 return &ConfigStore{config: cfg}
internal/config/store.go+3
@@ -647,6 +647,9 @@ func (s *ConfigStore) ReloadFromDisk(ctx context.Context) error {
647647 // Merge workspace config if present
648648 workspacePath := filepath.Join(cfg.Options.DataDirectory, fmt.Sprintf("%s.json", appName))
649649 if wsData, err := os.ReadFile(workspacePath); err == nil && len(wsData) > 0 {
650+ if !json.Valid(wsData) {
651+ return fmt.Errorf("invalid JSON in config file %s", workspacePath)
652+ }
650653 merged, mergeErr := loadFromBytes(append([][]byte{mustMarshalConfig(cfg)}, wsData))
651654 if mergeErr == nil {
652655 dataDir := cfg.Options.DataDirectory