pre-commit Environment Issue in SourceTree
In the previous post, I mentioned pre-commit as a powerful tool to lint and format in a project.
It had worked seamlessly for me until I committed code using SourceTree. Just to clarify, I predominantly use git on terminal. I only use a GUI app such as SourceTree to view the diff, or to stage selective chunks in a file (which is a bit difficult to achieve when using terminal). Therefore, the issue went unnoticed during my usual workflow.
SourceTree and Ruby environment
The problem happened when pre-commit attempted to install Rubocop for Ruby linting, as highlighted in the error log from SourceTree.
The log revealed that it was using the System Ruby (version 2.6). This is unexpected as I manage Ruby versions with rbenv. Running which ruby
on terminal showed a different path: /Users/thuyen/.rbenv/shims/ruby
.
Investigating the PATH
My very first doubt was that the PATH variable might not be correctly configured. To verify, I modified the pre-commit hook script (located at .git/hooks/pre-commit
) to print the PATH value and the resolved paths of ruby
and python
for debugging purposes.
#!/usr/bin/env bash
# File generated by pre-commit: https://pre-commit.com
# ID: 138fd403232d2ddd5efb44317e38bf03
echo $PATH # <--- INSERTED
which ruby && which python # <--- INSERTED
...
ruby
and python
.Meanwhile, the PATH we see when running on terminal (ie. creating a shell session) is as follows:
/opt/homebrew/opt/[email protected]/libexec/bin:/opt/bin:/Users/thuyen/.rbenv/shims:/opt/homebrew/bin:/opt/homebrew/sbin:/usr/local/bin:/System/Cryptexes/App/usr/bin:/usr/bin:/bin:/usr/sbin:/sbin:/Library/Apple/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/local/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/bin:/var/run/com.apple.security.cryptexd/codex.system/bootstrap/usr/appleinternal/bin:/Users/thuyen/.rugby/clt
Comparing the two PATH values, we noticed that the order of /Users/thuyen/.rbenv/shims
and /usr/bin
was reversed in SourceTree, causing ruby
to resolve to /usr/bin/ruby
.
Resolving the issue
Examining the log, we found that /usr/bin/
follows /usr/local/bin
in the PATH. This gives us a hint to create a symlink in /usr/local/bin/ruby
that points to the rbenv managed version.
$ ln -s $(which ruby) /usr/local/bin/ruby
As Ruby related flow heavily relies on gem
and bundle
. It’s strongly recommended to do the same for these programs.
$ ln -s $(which gem) /usr/local/bin/gem
$ ln -s $(which bundle) /usr/local/bin/bundle
Now the hook works perfectly in SourceTree.
Discussion
Similar issues may arise with other scripting languages included in macOS, such as Python and Perl. In my case, pre-commit worked perfectly fine with Python, mainly because it executes the program python
which is not under /usr/bin
. However, you might see unexpected behavior when using python3
because it’s present in /usr/bin
, pointing to the System Python. To deal with this scenario, I’d also recommend creating a symlink /usr/local/bin/python3
to the version you’re using (managed by Homebrew, in my case).
One more thing I want to point out is how the PATH was handled in SourceTree. Typically, when starting a macOS app (but not from terminal), the app doesn’t inherit environment variables from a shell session. In other words, custom variables defined in ~/.zshrc
(if your default shell is zsh, for example) will not included upon app launch. The question is how come /opt/homebrew/opt/[email protected]/libexec/bin
appears in the PATH. SourceTree seems to modify the PATH environment variable. My hypothesis is that SourceTree initializes a shell session just for the sake of reading & updating PATH. You can test hypothesis by adding the following to ~/.zshrc
and restarting SourceTree.
echo $(date +%s) load $0 >> ~/Downloads/trace
The below entry in the trace should confirm that ~/.zshrc
was sourced during app launch.
1707125068 load /Users/thuyen/.zshrc