fix(code): preserve inherited PATH so husky hooks work in Create PR#2051
fix(code): preserve inherited PATH so husky hooks work in Create PR#2051
Conversation
`fixPath` was replacing `process.env.PATH` with what `zsh -lc` returns. `-lc` only sources `.zprofile`, not `.zshrc`, so version-manager paths (nvm/mise/volta — where `pnpm`, `node`, `husky` typically live) added by `.zshrc` got wiped out. When `getCleanEnv` then snapshotted PATH for git child processes, the husky pre-commit hook ran without those tools and failed with "command not found", blocking the Create PR button even though the same hook ran fine from a terminal. Now merge the inherited PATH with the resolved shell PATH and fallbacks (deduped), instead of replacing. Terminal launches keep their rich PATH; Finder/Spotlight launches still get the shell PATH filled in. Generated-By: PostHog Code Task-Id: cf740c81-d28c-4ce9-9573-c016a5c0e773
Prompt To Fix All With AIFix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
apps/code/src/main/utils/fixPath.test.ts:154-192
**Prefer parameterised tests for repeated structure**
The two new tests ("preserves entries from the inherited PATH that the login shell lacks" and "preserves inherited PATH entries when reading from the cache") have nearly identical structure: set `process.env.PATH` to a rich terminal PATH, configure either the shell-resolution or cache mock, call `fixPath()`, and assert the same inherited entries survive. Per the team's convention, these should be collapsed into a single `it.each` that drives both code paths (live shell and cache hit) from one set of assertions, keeping the inherited-PATH check DRY.
Reviews (1): Last reviewed commit: "fix(code): preserve inherited PATH so hu..." | Re-trigger Greptile |
| it("preserves entries from the inherited PATH that the login shell lacks", async () => { | ||
| // Simulate launching from a terminal where .zshrc has added nvm/mise | ||
| // paths that the -lc resolution (only sources .zprofile) won't see. | ||
| process.env.PATH = | ||
| "/Users/me/.nvm/versions/node/v22.0.0/bin:/Users/me/.local/share/pnpm:/usr/bin:/bin"; | ||
| mockSpawnSync.mockReturnValue({ | ||
| status: 0, | ||
| stdout: shellOutput("/opt/homebrew/bin:/usr/bin:/bin"), | ||
| }); | ||
|
|
||
| const { fixPath } = await import("./fixPath"); | ||
| fixPath(); | ||
|
|
||
| const parts = process.env.PATH?.split(":") ?? []; | ||
| // Inherited entries (added by .zshrc, missing from .zprofile) survive. | ||
| expect(parts).toContain("/Users/me/.nvm/versions/node/v22.0.0/bin"); | ||
| expect(parts).toContain("/Users/me/.local/share/pnpm"); | ||
| // Shell-resolved entries are still merged in. | ||
| expect(parts).toContain("/opt/homebrew/bin"); | ||
| }); | ||
|
|
||
| it("preserves inherited PATH entries when reading from the cache", async () => { | ||
| process.env.PATH = "/Users/me/.nvm/versions/node/v22.0.0/bin:/usr/bin:/bin"; | ||
| mockExistsSync.mockReturnValue(true); | ||
| mockReadFileSync.mockReturnValue( | ||
| JSON.stringify({ | ||
| path: "/opt/homebrew/bin:/usr/bin:/bin", | ||
| timestamp: Date.now(), | ||
| }), | ||
| ); | ||
|
|
||
| const { fixPath } = await import("./fixPath"); | ||
| fixPath(); | ||
|
|
||
| const parts = process.env.PATH?.split(":") ?? []; | ||
| expect(parts).toContain("/Users/me/.nvm/versions/node/v22.0.0/bin"); | ||
| expect(parts).toContain("/opt/homebrew/bin"); | ||
| expect(mockSpawnSync).not.toHaveBeenCalled(); | ||
| }); |
There was a problem hiding this comment.
Prefer parameterised tests for repeated structure
The two new tests ("preserves entries from the inherited PATH that the login shell lacks" and "preserves inherited PATH entries when reading from the cache") have nearly identical structure: set process.env.PATH to a rich terminal PATH, configure either the shell-resolution or cache mock, call fixPath(), and assert the same inherited entries survive. Per the team's convention, these should be collapsed into a single it.each that drives both code paths (live shell and cache hit) from one set of assertions, keeping the inherited-PATH check DRY.
Context Used: Do not attempt to comment on incorrect alphabetica... (source)
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/code/src/main/utils/fixPath.test.ts
Line: 154-192
Comment:
**Prefer parameterised tests for repeated structure**
The two new tests ("preserves entries from the inherited PATH that the login shell lacks" and "preserves inherited PATH entries when reading from the cache") have nearly identical structure: set `process.env.PATH` to a rich terminal PATH, configure either the shell-resolution or cache mock, call `fixPath()`, and assert the same inherited entries survive. Per the team's convention, these should be collapsed into a single `it.each` that drives both code paths (live shell and cache hit) from one set of assertions, keeping the inherited-PATH check DRY.
**Context Used:** Do not attempt to comment on incorrect alphabetica... ([source](https://app.greptile.com/review/custom-context?memory=instruction-0))
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Summary
fixPathwas replacingprocess.env.PATHwith whatzsh -lcreturns.-lconly sources.zprofile, not.zshrc, so version-manager paths added by.zshrc(nvm/mise/volta — wherepnpm,node,huskytypically live) got wiped out. WhengetCleanEnvlater snapshotted PATH for git child processes, the husky hook ran without those tools and failed with "command not found".-ilc(full.zshrc) because of the zombie-process risk called out in #1399, so merging is the safer fix.Test plan
pnpm --filter code test src/main/utils/fixPath.test.ts— 8/8 passing (added 2 new tests for the inherited-PATH scenario, both with shell-resolved and cached paths)pnpm --filter code typecheck— cleanNote: existing users may need to wait up to 1h for the cached PATH to refresh, or delete `~/Library/Application Support/@posthog/posthog-code/shell-env-cache.json`. The cache only stores the shell PATH, which is now merged on top of the inherited PATH on every boot.
Generated-By: PostHog Code
Task-Id: cf740c81-d28c-4ce9-9573-c016a5c0e773