Skip to content

Commit ee7c8bd

Browse files
committed
feat(ai): run postCd hooks before launching AI process (#144)
When users configure postCd hooks (e.g., source ./vars.sh), gtr cd runs them but gtr ai did not. This meant AI tools launched via gtr ai didn't get the worktree's environment setup. Add run_hooks_export to lib/hooks.sh — a variant of run_hooks that evals hooks without subshell isolation so exported env vars persist. Call sites wrap it in an explicit subshell for safety with set -e. Closes #144
1 parent d6fd5da commit ee7c8bd

File tree

5 files changed

+110
-3
lines changed

5 files changed

+110
-3
lines changed

lib/commands/ai.sh

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,13 @@ cmd_ai() {
3939
log_info "Directory: $worktree_path"
4040
log_info "Branch: $branch"
4141

42-
ai_start "$worktree_path" "${ai_args[@]}"
42+
# Run postCd hooks + AI in a subshell so hook env vars propagate to AI
43+
(
44+
cd "$worktree_path" || exit 1
45+
run_hooks_export "postCd" \
46+
REPO_ROOT="$repo_root" \
47+
WORKTREE_PATH="$worktree_path" \
48+
BRANCH="$branch"
49+
ai_start "$worktree_path" "${ai_args[@]}"
50+
)
4351
}

lib/commands/create.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,7 @@ cmd_create() {
192192

193193
# Auto-launch editor/AI or show next steps
194194
[ "$open_editor" -eq 1 ] && { _auto_launch_editor "$worktree_path" || true; }
195-
[ "$start_ai" -eq 1 ] && { _auto_launch_ai "$worktree_path" || true; }
195+
[ "$start_ai" -eq 1 ] && { _auto_launch_ai "$worktree_path" "$repo_root" "$branch_name" || true; }
196196
if [ "$open_editor" -eq 0 ] && [ "$start_ai" -eq 0 ]; then
197197
_post_create_next_steps "$branch_name" "$folder_name" "$folder_override" "$repo_root" "$base_dir" "$prefix"
198198
fi

lib/hooks.sh

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -84,3 +84,42 @@ run_hooks_in() {
8484

8585
return $result
8686
}
87+
88+
# Run hooks in current shell without subshell isolation
89+
# Env vars set by hooks (e.g., source ./vars.sh) persist in the calling shell.
90+
# IMPORTANT: Call from within a subshell to avoid polluting the main script.
91+
# Usage: run_hooks_export phase [env_vars...]
92+
# Example: ( cd "$dir" && run_hooks_export postCd REPO_ROOT="$root" )
93+
run_hooks_export() {
94+
local phase="$1"
95+
shift
96+
97+
local hooks
98+
hooks=$(cfg_get_all "gtr.hook.$phase" "hooks.$phase")
99+
100+
if [ -z "$hooks" ]; then
101+
return 0
102+
fi
103+
104+
log_step "Running $phase hooks..."
105+
106+
# Export env vars so hooks and child processes can see them
107+
local kv
108+
for kv in "$@"; do
109+
# shellcheck disable=SC2163
110+
export "$kv"
111+
done
112+
113+
local hook_count=0
114+
while IFS= read -r hook; do
115+
[ -z "$hook" ] && continue
116+
117+
hook_count=$((hook_count + 1))
118+
log_info "Hook $hook_count: $hook"
119+
120+
# eval directly (no subshell) so exports persist
121+
eval "$hook" || log_warn "Hook $hook_count failed (continuing)"
122+
done <<EOF
123+
$hooks
124+
EOF
125+
}

lib/launch.sh

Lines changed: 15 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,15 +41,29 @@ _auto_launch_editor() {
4141
}
4242

4343
# Auto-launch AI tool for a worktree
44+
# Usage: _auto_launch_ai <worktree_path> [repo_root] [branch]
45+
# When repo_root and branch are provided, runs postCd hooks before launch.
4446
_auto_launch_ai() {
4547
local worktree_path="$1"
48+
local repo_root="${2:-}" branch="${3:-}"
4649
local ai_tool
4750
ai_tool=$(_cfg_ai_default)
4851
if [ "$ai_tool" = "none" ]; then
4952
log_warn "No AI tool configured. Set with: git gtr config set gtr.ai.default claude"
5053
else
5154
load_ai_adapter "$ai_tool" || return 1
5255
log_step "Starting $ai_tool..."
53-
ai_start "$worktree_path" || log_warn "Failed to start AI tool"
56+
if [ -n "$repo_root" ] && [ -n "$branch" ]; then
57+
(
58+
cd "$worktree_path" || exit 1
59+
run_hooks_export "postCd" \
60+
REPO_ROOT="$repo_root" \
61+
WORKTREE_PATH="$worktree_path" \
62+
BRANCH="$branch"
63+
ai_start "$worktree_path"
64+
) || log_warn "Failed to start AI tool"
65+
else
66+
ai_start "$worktree_path" || log_warn "Failed to start AI tool"
67+
fi
5468
fi
5569
}

tests/hooks.bats

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,3 +88,49 @@ teardown() {
8888
run_hooks postCreate REPO_ROOT="$TEST_REPO" BRANCH="test-branch"
8989
[ "$(cat "$TEST_REPO/vars")" = "$TEST_REPO|test-branch" ]
9090
}
91+
92+
# ── run_hooks_export tests ───────────────────────────────────────────────────
93+
94+
@test "run_hooks_export returns 0 when no hooks configured" {
95+
run run_hooks_export postCd REPO_ROOT="$TEST_REPO"
96+
[ "$status" -eq 0 ]
97+
}
98+
99+
@test "run_hooks_export executes hook" {
100+
git config --add gtr.hook.postCd 'touch "$REPO_ROOT/hook-ran"'
101+
(cd "$TEST_REPO" && run_hooks_export postCd REPO_ROOT="$TEST_REPO")
102+
[ -f "$TEST_REPO/hook-ran" ]
103+
}
104+
105+
@test "run_hooks_export env vars propagate to child processes" {
106+
git config --add gtr.hook.postCd 'export MY_CUSTOM_VAR="from-hook"'
107+
# Run hook then check env in same subshell — simulates ai_start inheriting env
108+
result=$(
109+
cd "$TEST_REPO"
110+
run_hooks_export postCd REPO_ROOT="$TEST_REPO"
111+
echo "$MY_CUSTOM_VAR"
112+
)
113+
[ "$result" = "from-hook" ]
114+
}
115+
116+
@test "run_hooks_export continues after hook failure" {
117+
git config --add gtr.hook.postCd "false"
118+
git config --add gtr.hook.postCd 'touch "$REPO_ROOT/second-ran"'
119+
(cd "$TEST_REPO" && run_hooks_export postCd REPO_ROOT="$TEST_REPO") || true
120+
[ -f "$TEST_REPO/second-ran" ]
121+
}
122+
123+
@test "run_hooks_export passes REPO_ROOT WORKTREE_PATH BRANCH" {
124+
git config --add gtr.hook.postCd 'echo "$REPO_ROOT|$WORKTREE_PATH|$BRANCH" > "$REPO_ROOT/env-check"'
125+
(cd "$TEST_REPO" && run_hooks_export postCd \
126+
REPO_ROOT="$TEST_REPO" \
127+
WORKTREE_PATH="/tmp/wt" \
128+
BRANCH="my-branch")
129+
[ "$(cat "$TEST_REPO/env-check")" = "$TEST_REPO|/tmp/wt|my-branch" ]
130+
}
131+
132+
@test "run_hooks_export does not leak env to parent shell" {
133+
git config --add gtr.hook.postCd 'export LEAK_TEST="leaked"'
134+
(cd "$TEST_REPO" && run_hooks_export postCd REPO_ROOT="$TEST_REPO")
135+
[ -z "${LEAK_TEST:-}" ]
136+
}

0 commit comments

Comments
 (0)