On Windows SHELL is never set, so detectShellProfile() always falls back to ~/.bashrc. Git Bash — the shell Windows users actually run teamai's block under — starts as a login shell, and a login shell reads ~/.bash_profile, ~/.bash_login or ~/.profile, never ~/.bashrc. The block gets written, the file looks correct, and the variables are never loaded.
Filed separately at @jeff-r2026's request in #661.
Where
src/resources/env.ts:
private detectShellProfile(): string {
const home = getUserHome();
const shell = process.env.SHELL ?? '';
if (shell.includes('zsh')) {
return path.join(home, '.zshrc');
}
return path.join(home, '.bashrc');
}
SHELL is a POSIX convention. Windows defines it neither at the user nor the machine level, so the zsh branch is unreachable there and .bashrc is the only possible outcome.
Reproduction on Windows 11
$ node -e "console.log(JSON.stringify(process.env.SHELL))"
undefined
$ reg query HKCU\Environment | findstr /i shell
(no SHELL value)
After teamai pull with an env/env.yaml that has at least one variable, ~/.bashrc receives:
# [teamai:env:start]
# DO NOT EDIT: This section is auto-managed by teamai
[ -f "/c/Users/<user>/.teamai/env.sh" ] && source "/c/Users/<user>/.teamai/env.sh"
# [teamai:env:end]
while ~/.profile gets no block at all. The two shell types then diverge:
$ bash -lc 'echo ${MY_VAR:-<unset>}'
<unset> # login shell — reads .bash_profile / .bash_login / .profile only
$ bash -c '. ~/.bashrc; echo ${MY_VAR:-<unset>}'
set # the block itself is fine; it is simply never read
git-bash.exe, and Git Bash launched from Windows Terminal, pass --login, so the first case is the one users get.
Git for Windows tries to paper over this in /etc/profile.d/bash_profile.sh, but only when none of the three files exist:
if [ -e ~/.bashrc -a ! -e ~/.bash_profile -a ! -e ~/.bash_login -a ! -e ~/.profile ]; then
# ...generates a ~/.bash_profile that sources ~/.profile and ~/.bashrc
fi
A ~/.profile is common — many installers create one, and ~/.local/bin/env adds a line to it. The moment it exists, Git for Windows does not create the .bash_profile that would have sourced .bashrc.
Why nothing catches it
doctor checks that the block is present in the profile file, and it is. Whether that file is one the user's shell actually reads is a different question, and once source is skipped there is no output at all — [ -f ... ] returns false and && short-circuits.
So the failure mode reads as success from every angle: the pull reports fine, the profile contains the block, and no variable is ever exported.
Suggested fix
Choose the profile the shell will actually read, per platform:
- Windows — prefer an existing
~/.bash_profile, then ~/.bash_login, then ~/.profile; use ~/.bashrc only when none of them exists (which is exactly the case where Git for Windows generates a .bash_profile that sources it, so both choices work).
- POSIX — keep the current
SHELL-based behaviour.
If the file that ends up chosen is not one the current SHELL would read, a log.warn naming it would make the situation visible without changing the default.
Two things to keep in mind while fixing this:
src/uninstall.ts carries a second, independent copy of detectShellProfile() (around line 133). It has to resolve to the same file, otherwise teamai uninstall leaves a block behind — or removes the wrong one.
sharing.env.shellProfilePath overrides this path entirely and must keep taking precedence.
Environment
- teamai-cli 0.24.0
- Windows 11 Pro 25H2, Git for Windows 2.45.2 (
git-bash.exe, launched with --login)
~/.bashrc present, ~/.bash_profile absent, ~/.profile present (two lines, no teamai block)
On Windows
SHELLis never set, sodetectShellProfile()always falls back to~/.bashrc. Git Bash — the shell Windows users actually run teamai's block under — starts as a login shell, and a login shell reads~/.bash_profile,~/.bash_loginor~/.profile, never~/.bashrc. The block gets written, the file looks correct, and the variables are never loaded.Filed separately at @jeff-r2026's request in #661.
Where
src/resources/env.ts:SHELLis a POSIX convention. Windows defines it neither at the user nor the machine level, so thezshbranch is unreachable there and.bashrcis the only possible outcome.Reproduction on Windows 11
After
teamai pullwith anenv/env.yamlthat has at least one variable,~/.bashrcreceives:while
~/.profilegets no block at all. The two shell types then diverge:git-bash.exe, and Git Bash launched from Windows Terminal, pass--login, so the first case is the one users get.Git for Windows tries to paper over this in
/etc/profile.d/bash_profile.sh, but only when none of the three files exist:A
~/.profileis common — many installers create one, and~/.local/bin/envadds a line to it. The moment it exists, Git for Windows does not create the.bash_profilethat would have sourced.bashrc.Why nothing catches it
doctorchecks that the block is present in the profile file, and it is. Whether that file is one the user's shell actually reads is a different question, and oncesourceis skipped there is no output at all —[ -f ... ]returns false and&&short-circuits.So the failure mode reads as success from every angle: the pull reports fine, the profile contains the block, and no variable is ever exported.
Suggested fix
Choose the profile the shell will actually read, per platform:
~/.bash_profile, then~/.bash_login, then~/.profile; use~/.bashrconly when none of them exists (which is exactly the case where Git for Windows generates a.bash_profilethat sources it, so both choices work).SHELL-based behaviour.If the file that ends up chosen is not one the current
SHELLwould read, alog.warnnaming it would make the situation visible without changing the default.Two things to keep in mind while fixing this:
src/uninstall.tscarries a second, independent copy ofdetectShellProfile()(around line 133). It has to resolve to the same file, otherwiseteamai uninstallleaves a block behind — or removes the wrong one.sharing.env.shellProfilePathoverrides this path entirely and must keep taking precedence.Environment
git-bash.exe, launched with--login)~/.bashrcpresent,~/.bash_profileabsent,~/.profilepresent (two lines, no teamai block)