Skip to content

fix(code): preserve inherited PATH so husky hooks work in Create PR#2051

Open
k11kirky wants to merge 1 commit intomainfrom
posthog-code/fix-path-merge-husky
Open

fix(code): preserve inherited PATH so husky hooks work in Create PR#2051
k11kirky wants to merge 1 commit intomainfrom
posthog-code/fix-path-merge-husky

Conversation

@k11kirky
Copy link
Copy Markdown
Contributor

@k11kirky k11kirky commented May 6, 2026

Summary

  • The Create PR button failed when the underlying repo had husky pre-commit hooks, even though running the same hook from a terminal worked fine.
  • Root cause: fixPath was replacing process.env.PATH with what zsh -lc returns. -lc only sources .zprofile, not .zshrc, so version-manager paths added by .zshrc (nvm/mise/volta — where pnpm, node, husky typically live) got wiped out. When getCleanEnv later snapshotted PATH for git child processes, the husky hook ran without those tools and failed with "command not found".
  • Fix: 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. We can't safely switch to -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 — clean
  • Manual: launch the app from a terminal in a repo with husky hooks and click Create PR — pre-commit hook should now run successfully
  • Manual: launch the app from Finder/Spotlight and confirm the resolved shell PATH is still applied (regression check)

Note: 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

`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
@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 6, 2026

Prompt To Fix All With AI
Fix 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

Comment on lines +154 to +192
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();
});
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants