A practical guide for running the TeamAI CLI (
teamai) hook wiring on Windows for every enabled agent (Claude Code, Codex, ZCode, CodeBuddy, Qoder, WorkBuddy, Cline, Cursor, OpenCode), plus the upstream bug that makes this necessary and a suggested fix for maintainers.
On older TeamAI versions the hooks injected on Windows used a bare bash
launcher that silently crashes (the WSL bash ships Node 18, which can't parse
the TeamAI bundle), so the hooks were effectively dead — || true hid the
failure. The same versions never wrote codebuddy / workbuddy hooks at all,
because shell detection (fs.existsSync('/bin/sh')) is always false on Windows.
Current teamai handles Windows itself, so the user-side workaround below is
only needed on an older version: hook commands launch through an absolute Git
Bash path, and each GUI tool resolves its own hook shell — WorkBuddy through its
bundled PortableGit sh.exe, and CodeBuddy through cmd.exe (%ComSpec%),
which every Windows install provides. Neither tool is skipped.
The durable user-side fix combines two mechanisms so hooks fire no matter what
teamai writes:
- Git Bash absolute path in every agent settings file (works today).
- A WSL wrapper that delegates to the native Windows
teamaiviacmd.exe(survives a laterteamai pullthat reverts the hooks to barebash).
After applying both, teamai doctor reports the six installed tools as healthy
and every hook-dispatch call returns exit=0 through both mechanisms.
teamai hooks inject writes a command like this into each agent's settings
file:
"command": "bash -lc \"teamai hook-dispatch session-start --tool claude 2>/dev/null\" || true"There are six hooks per tool: SessionStart, Stop, PostToolUse (three
matchers: *, Skill, TodoWrite), and UserPromptSubmit. They let the team
repo record session stats and apply shared rules/skills across agents.
On Windows a bare bash on PATH resolves to the WSL launcher
(C:\Windows\System32\bash.exe). WSL's bundled Node is v18, but the TeamAI
bundle needs a newer Node, so every hook invocation crashes silently. Because
the command ends in || true, the crash is swallowed and nothing is logged —
hooks never fire, yet teamai doctor still reports them as "present".
src/builtin-hooks.ts gates shell-dependent tools on hasShell():
export function hasShell(): boolean {
if (_hasShellCache === undefined) {
try {
_hasShellCache = fs.existsSync('/bin/sh');
} catch {
_hasShellCache = false;
}
}
return _hasShellCache;
}/bin/sh does not exist on Windows, so hasShell() is false and
skipToolsWithoutShell() added codebuddy / workbuddy
(SHELL_DEPENDENT_TOOLS) to the skip set — those two agents got no hooks at
all on Windows, even when everything else worked.
That skip is gone: gating now asks each tool for its own hook shell first
(hasShellFor() → bundledShellFor()). workbuddy resolves through the
PortableGit sh.exe it ships; codebuddy resolves through cmd.exe, because
CodeBuddy's Windows hook runner is %ComSpec% — it executes a hook's command
via child_process.spawn(command, [], { shell: true }) — and every Windows
install provides cmd.exe. Only a tool with no resolvable shell is skipped.
- Bare
bash→ WSL Node 18. WindowsPATHresolvesbashto the WSL launcher before Git Bash; WSL Node 18 can't parse the TeamAI bundle. hasShell()Windows bug.fs.existsSync('/bin/sh')is never true on Windows, which used to skip hook injection forcodebuddy/workbuddy; the per-toolbundledShellFor()resolver now covers them.- WSL path translation. A WSL-side wrapper that
execs the Windows Node with a/mnt/c/...path gets mangled intoC:\mnt\c\..., causingMODULE_NOT_FOUND.
Replace the bare bash in each hook command with Git Bash's absolute Windows
path (adjust if Git is installed elsewhere):
"command": "\"C:\\Program Files\\Git\\bin\\bash.exe\" -lc \"teamai hook-dispatch session-start --tool <agent> 2>/dev/null\" || true"Apply this to every agent that has hooks:
~/.claude/settings.json— 6 hooks~/.codex/hooks.json— 6 hooks~/.zcode/cli/config.json—commandfield → Git Bash path; 6 hooks~/.codebuddy/settings.json— create if missing; 6 hooks~/.qoder/settings.json— create if missing; 6 hooks~/.qoder-cn/settings.json— Qoder CN; same as Qoder but under its own user root- WorkBuddy / Cline / Cursor / OpenCode settings as applicable
Use a JSON-aware edit (don't hand-edit with sed — the double quotes must stay
escaped). Keep a *.teamai-bak copy of each original file so you can roll back.
teamai pull / hooks inject rewrites the agent settings back to bare bash.
Mechanism A gets overwritten, but a WSL wrapper keeps bare bash working.
Create a wrapper at your WSL home (e.g. /home/<user>/.teamai-wsl/bin/teamai):
#!/bin/sh
# delegate to the native Windows teamai (correct Node + paths) via cmd.exe
exec cmd.exe /c teamai "$@"Prepend it to PATH from ~/.profile / ~/.bashrc under a marker:
# [teamai-wsl-fix]
export PATH="$HOME/.teamai-wsl/bin:$PATH"Now a bare bash hook finds teamai → cmd.exe → native Windows TeamAI. The
cmd.exe delegation sidesteps the WSL /mnt/c path-translation bug entirely.
This requires WSL to stay installed.
- Add
qoder,qoder-cn, andcodebuddytoenabledAgentsin~/.teamai/config.yaml. - Copy the team's skills and rules from the team repo into
~/.qoder,~/.qoder-cn, and~/.codebuddyso those agents are fully equipped, not just hooked. (Qoder CN reads~/.qoder-cn/for its user scope; its project scope stays<project>/.qoder/, shared with Qoder.)
teamai doctorExpected: hooks present for claude, codex, qoder, qoder-cn, zcode, codebuddy, workbuddy.
Per-tool dispatch check, both ways:
# Mechanism A (Git Bash absolute path)
"C:\Program Files\Git\bin\bash.exe" -lc "teamai hook-dispatch session-start --tool claude 2>/dev/null"; echo $?
# Mechanism B (bare bash via WSL wrapper)
wsl bash -lc "teamai hook-dispatch session-start --tool claude 2>/dev/null"; echo $?Every tool should print 0 through both mechanisms.
doctor: ✔ claude ✔ codex ✔ qoder ✔ qoder-cn ✔ zcode ✔ codebuddy ✔ workbuddy
dispatch (Git-Bash path): claude=0 codex=0 zcode=0 codebuddy=0 qoder=0 qoder-cn=0
dispatch (bare bash/WSL): claude=0 codex=0 zcode=0 codebuddy=0 qoder=0 qoder-cn=0 workbuddy=0
- Durability depends on the WSL wrapper.
teamai pullreverts agent settings to barebash; Mechanism A is overwritten, Mechanism B keeps it working only while WSL is installed. If WSL is removed, bare-bashhooks break again. - Requires WSL for Mechanism B. On a machine without WSL, only Mechanism A (the Git-Bash absolute path currently in the files) works.
- Does not patch older TeamAI versions. The Windows gaps listed above
(the
hasShell()skip, the bare-bashdefault, and the bundled git missing from a detached pull's PATH) are fixed in currentteamai-cli;npm update teamai-clipicks the fixes up and the workaround can then be dropped. - macOS / Linux need no fix. There, bare
bashalready resolves to the system Node and works natively. teamai doctorghcheck can be a false negative. It may spawnghwithoutAPPDATA, so it can't see your login even thoughgh auth statusshows you as logged in. Ignore it if your other checks pass.- Backup files remain.
*.teamai-bakcopies of the edited agent settings are kept as rollback safety.
Three small changes would make Windows work out of the box — all have since
shipped in teamai-cli, so this section is kept for context:
On Windows, /bin/sh is absent but a usable POSIX shell is provided by Git for
Windows (sh.exe / bash.exe) or WSL. hasShell() should detect that instead
of always returning false:
export function hasShell(): boolean {
if (_hasShellCache === undefined) {
try {
if (process.platform === 'win32') {
// Git for Windows ships sh.exe/bash.exe; WSL also provides bash.
// Hooks are launched via `bash -lc ...`, so any of these counts.
const candidates = [
'C:\\Program Files\\Git\\bin\\bash.exe',
'C:\\Program Files\\Git\\usr\\bin\\sh.exe',
'C:\\Windows\\System32\\bash.exe',
];
_hasShellCache = candidates.some((p) => fs.existsSync(p)) ||
whichBash() !== null;
} else {
_hasShellCache = fs.existsSync('/bin/sh');
}
} catch {
_hasShellCache = false;
}
}
return _hasShellCache;
}Current teamai achieves this through hasShellFor() → bundledShellFor(), so
codebuddy / workbuddy hooks are injected on Windows today.
getDispatchCommand() hard-codes bash -lc "...". On Windows that resolves to
WSL's Node 18 and crashes. Prefer the Git Bash absolute path (or the bundled
PortableGit sh.exe) when process.platform === 'win32'.
Both changes are backward compatible: macOS/Linux keep /bin/sh, and Windows
users stop needing the manual workaround above.
The session-start pull is spawned through WMI (to escape the host's job
object), and a WMI-created process inherits the provider's environment — the
PATH that ran teamai (with the GUI host's bundled git on it) never reaches
the pull. On machines without a system git, every bare-name git spawn in the
pull fails with spawn git ENOENT and the clone silently freezes while the
post-pull deploy keeps running against the stale tree.
Current teamai resolves the host's bundled git at CLI startup
(ensureBundledRuntimeOnPath) and puts its cmd dir first on PATH — the msys
dirs are appended, and machines that already resolve git are left untouched —
so detached pulls work with no system git installed.
teamai doctor
# per tool, both ways:
"C:\Program Files\Git\bin\bash.exe" -lc "teamai hook-dispatch session-start --tool claude 2>/dev/null"; echo $?
wsl bash -lc "teamai hook-dispatch session-start --tool claude 2>/dev/null"; echo $?
# if a tool's hooks stop firing:
# 1. confirm the wrapper exists: ls ~/.teamai-wsl/bin/teamai
# 2. confirm PATH export in ~/.profile (marker # [teamai-wsl-fix])
# 3. otherwise re-apply Mechanism A (Git-Bash absolute path in the agent settings)| File | Change |
|---|---|
~/.claude/settings.json |
hook commands → Git Bash absolute path (backup: *.teamai-bak) |
~/.codex/hooks.json |
hook commands → Git Bash absolute path (backup: *.teamai-bak) |
~/.zcode/cli/config.json |
command field → Git Bash path (backup: *.teamai-bak) |
~/.codebuddy/settings.json |
created with 6 hooks (if missing) |
~/.qoder/settings.json |
created with 6 hooks (if missing) |
~/.qoder-cn/settings.json |
Qoder CN; created with 6 hooks (if missing) |
~/.teamai/config.yaml |
enabledAgents += qoder, qoder-cn, codebuddy |
~/.qoder/{skills,rules}, ~/.qoder-cn/{skills,rules}, ~/.codebuddy/{skills,rules} |
team resources copied |
~/.teamai-wsl/bin/teamai (Win) + <wsl-home>/.teamai-wsl/bin/teamai (WSL) |
durability wrapper |
~/.profile, ~/.bashrc |
PATH export (marker # [teamai-wsl-fix]) |