Skip to content

Commit 673578d

Browse files
aooohanclaude
andcommitted
fix: preserve user-added PATH entries by detecting PATH changes in cache
Fixes #606 When users manually export PATH (e.g., `export PATH="xxx/bin:$PATH"`), the new paths were lost after vfox activation due to caching mechanism only checking config file changes. Root Cause: - `vfox env` caches output based on config file modification times - Cache didn't account for user-made changes to PATH environment variable - Hook would return stale cached output without user's manual PATH exports Solution: - Add `CachedPath` field to ConfigState to track PATH value used for cache - Update `HasChanged()` to detect when current PATH differs from cached PATH - Update `Update()` to store current PATH when updating cache - Use cross-platform `PathVarName` constant (Unix: "PATH", Windows: "Path") Changes: - internal/env/state.go: Add PATH tracking to detect user-made changes - internal/env/state_test.go: Add TestConfigState_HasChanged_PathChanged Testing: - Added comprehensive test for PATH change detection - All existing tests pass - Cross-platform compatible (Unix/Linux/macOS/Windows) Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
1 parent 89cefd6 commit 673578d

File tree

2 files changed

+75
-2
lines changed

2 files changed

+75
-2
lines changed

internal/env/state.go

Lines changed: 14 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,9 @@ type ConfigState struct {
3939
SessionMtime int64 `json:"session_mtime,omitempty"`
4040
ProjectMtime int64 `json:"project_mtime,omitempty"`
4141

42+
// Cached PATH value (to detect user-made PATH changes)
43+
CachedPath string `json:"cached_path,omitempty"`
44+
4245
// Cached env output (shell script)
4346
CachedOutput string `json:"cached_output,omitempty"`
4447

@@ -95,7 +98,7 @@ func (s *ConfigState) saveLocked() error {
9598
}
9699

97100
// HasChanged checks if any config file has changed based on modification time
98-
// Returns true if any config file has been modified since last check, or if project directory changed
101+
// Returns true if any config file has been modified since last check, or if project directory changed, or if PATH changed
99102
func (s *ConfigState) HasChanged(configPaths map[UseScope]string) (bool, error) {
100103
s.mu.RLock()
101104
defer s.mu.RUnlock()
@@ -107,6 +110,13 @@ func (s *ConfigState) HasChanged(configPaths map[UseScope]string) (bool, error)
107110
return true, nil
108111
}
109112

113+
// Check if PATH has changed since last cache
114+
currentPath := os.Getenv(PathVarName)
115+
if currentPath != s.CachedPath {
116+
// User has modified PATH since last cache
117+
return true, nil
118+
}
119+
110120
// Check each config file
111121
for scope, path := range configPaths {
112122
if path == "" {
@@ -143,7 +153,7 @@ func (s *ConfigState) HasChanged(configPaths map[UseScope]string) (bool, error)
143153
return false, nil
144154
}
145155

146-
// Update updates the state with new config mtimes and cached output
156+
// Update updates the state with new config mtimes, current PATH, and cached output
147157
func (s *ConfigState) Update(configPaths map[UseScope]string, output string) error {
148158
s.mu.Lock()
149159
defer s.mu.Unlock()
@@ -175,6 +185,8 @@ func (s *ConfigState) Update(configPaths map[UseScope]string, output string) err
175185
}
176186
}
177187

188+
// Store current PATH to detect user-made changes later
189+
s.CachedPath = os.Getenv(PathVarName)
178190
s.CachedOutput = output
179191

180192
return s.saveLocked()

internal/env/state_test.go

Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,3 +272,64 @@ func TestConfigState_GetCachedOutput(t *testing.T) {
272272
t.Errorf("GetCachedOutput() = %q, want %q", output, "export TEST=value")
273273
}
274274
}
275+
276+
func TestConfigState_HasChanged_PathChanged(t *testing.T) {
277+
// Save original PATH
278+
origPath := os.Getenv("PATH")
279+
defer func() {
280+
os.Setenv("PATH", origPath)
281+
}()
282+
283+
tmpDir := t.TempDir()
284+
stateFile := filepath.Join(tmpDir, "state.json")
285+
configFile := filepath.Join(tmpDir, ".vfox.toml")
286+
287+
// Create config file
288+
err := os.WriteFile(configFile, []byte("test"), 0644)
289+
if err != nil {
290+
t.Fatalf("Failed to create config file: %v", err)
291+
}
292+
293+
// Set initial PATH
294+
initialPath := "/usr/bin:/bin"
295+
os.Setenv("PATH", initialPath)
296+
297+
// Create state and update with current mtime and PATH
298+
state := NewConfigState(stateFile)
299+
configPaths := map[UseScope]string{
300+
Global: configFile,
301+
}
302+
303+
err = state.Update(configPaths, "export PATH=/test")
304+
if err != nil {
305+
t.Fatalf("Update() failed: %v", err)
306+
}
307+
308+
// Verify cached PATH
309+
if state.CachedPath != initialPath {
310+
t.Errorf("CachedPath = %q, want %q", state.CachedPath, initialPath)
311+
}
312+
313+
// Check if changed (should be false, PATH and configs are the same)
314+
changed, err := state.HasChanged(configPaths)
315+
if err != nil {
316+
t.Fatalf("HasChanged() failed: %v", err)
317+
}
318+
if changed {
319+
t.Error("HasChanged() should return false when nothing has changed")
320+
}
321+
322+
// User manually adds a path
323+
modifiedPath := "/home/user/bin:/usr/bin:/bin"
324+
os.Setenv("PATH", modifiedPath)
325+
326+
// Check if changed (should be true, PATH changed)
327+
changed, err = state.HasChanged(configPaths)
328+
if err != nil {
329+
t.Fatalf("HasChanged() failed: %v", err)
330+
}
331+
if !changed {
332+
t.Error("HasChanged() should return true when PATH has changed")
333+
}
334+
}
335+

0 commit comments

Comments
 (0)