Skip to content

feat: Add post-hook support for start and checkout commands#22

Merged
sotarok merged 4 commits intomainfrom
feat/post-hooks
Apr 6, 2026
Merged

feat: Add post-hook support for start and checkout commands#22
sotarok merged 4 commits intomainfrom
feat/post-hooks

Conversation

@sotarok
Copy link
Copy Markdown
Owner

@sotarok sotarok commented Apr 6, 2026

Summary

  • Add configurable post_start_hook and post_checkout_hook options to .gwrc
  • Hook commands are executed via sh -c with environment variables (GW_WORKTREE_PATH, GW_BRANCH_NAME, GW_REPO_NAME, GW_COMMAND)
  • Enables integration with tmux, iTerm2, or any custom workflow without hardcoding terminal-specific behavior
  • Include example hook scripts in examples/hooks/ for tmux and iTerm2

Inspired by #20 (thanks @pnikonowicz).

Closes #20

Test plan

  • make check passes (lint + all tests)
  • Manual test: post_start_hook = echo ... fires after gw start
  • Manual test: tmux new-window hook works
  • Manual test: iTerm2 new-tab hook works
  • Hook failure does not block command execution
  • Backward compatible: no hooks configured = existing behavior unchanged

🤖 Generated with Claude Code

Add configurable post_start_hook and post_checkout_hook to .gwrc,
enabling users to integrate with tmux, iTerm2, or any custom workflow
after worktree creation. Includes example hook scripts.

Inspired by #20 (thanks @pnikonowicz).

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@codecov
Copy link
Copy Markdown

codecov Bot commented Apr 6, 2026

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 88.68%. Comparing base (f4d53dc) to head (d3c718e).
⚠️ Report is 5 commits behind head on main.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main      #22      +/-   ##
==========================================
+ Coverage   88.37%   88.68%   +0.30%     
==========================================
  Files          29       30       +1     
  Lines        1832     1882      +50     
==========================================
+ Hits         1619     1669      +50     
  Misses        173      173              
  Partials       40       40              
Flag Coverage Δ
unittests 88.68% <100.00%> (+0.30%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
cmd/command_checkout.go 82.07% <100.00%> (+1.46%) ⬆️
cmd/command_start.go 76.66% <100.00%> (+4.66%) ⬆️
internal/config/config.go 91.93% <100.00%> (+1.19%) ⬆️
internal/hook/hook.go 100.00% <100.00%> (ø)

Continue to review full report in Codecov by Sentry.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update f4d53dc...d3c718e. Read the comment docs.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@claude
Copy link
Copy Markdown

claude Bot commented Apr 6, 2026

PR Review: feat/post-hooks

This is a clean, well-structured addition. The hook abstraction is minimal and correct, test coverage is solid, and the failure-as-warning behaviour is exactly right. A few issues worth addressing before merging.

Bug: Stale comment implies end hook support

In internal/hook/hook.go line 15, the comment says // "start", "checkout", or "end" but no end hook is wired up in this PR. This will confuse users. Either remove "or end" from the comment, or add a TODO clarifying it is planned.

Inconsistency: worktreePath vs absolutePath for GW_WORKTREE_PATH

command_checkout.go explicitly resolves to an absolute path before passing it to the hook (absolutePath, err := filepath.Abs(worktreePath)), but command_start.go passes worktreePath directly. In practice this works because git.CreateWorktree() builds the path from git rev-parse --show-toplevel (which is absolute), but it relies on an undocumented implementation detail. The README promises an absolute path, so enforce it explicitly with filepath.Abs the same way checkout does.

Nit: Unnecessary intermediate variable

command_start.go line 115: repoNameForHook := repoName is redundant. Use repoName directly.

Gap: Hooks can't be configured via the interactive editor

The test comment acknowledges this: // Test setting post_start_hook (not supported via SetConfigItem, it's bool-only). Since SetConfigItem only handles booleans, the interactive config editor cannot set hook values. The docs should explicitly note that users must edit ~/.gwrc by hand for these fields.

No execution timeout

A misbehaving hook that blocks on input will hang gw indefinitely. Consider exec.CommandContext with a 30-second timeout so users get a clear error instead of a silent hang.

Security note: example script path injection

In examples/hooks/iterm2-new-tab.sh, GW_WORKTREE_PATH is embedded inside single quotes in AppleScript: write text "cd '${GW_WORKTREE_PATH}' && clear". A path containing a single quote will break the script. The tmux example is safe. Consider documenting this limitation in the example.

What is working well

  • The internal/hook package is clean and well-tested (7 tests covering empty command, all env vars, failure, stderr, and parent env inheritance)
  • Hook failures printing as warnings without blocking is the correct design
  • append(os.Environ(), envToSlice(env)...) correctly inherits the parent environment
  • Config load/save round-trip handles values containing = signs correctly
  • CHANGELOG and README are both updated

The core feature is solid. The absolutePath inconsistency and stale end comment are the most important fixes before merging.

Cover hook execution path and hook failure warning behavior
for both StartCommand and CheckoutCommand.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@claude
Copy link
Copy Markdown

claude Bot commented Apr 6, 2026

Code Review: feat: Add post-hook support for start and checkout commands

Overall this is a clean, well-scoped feature with solid test coverage. The design is backward-compatible and the hook-failure-as-warning behavior is the right call. A few issues worth addressing before merging:


Bug: Inconsistent WorktreePath between start and checkout

command_checkout.go explicitly resolves to an absolute path before passing it to the hook:

// command_checkout.go
absolutePath, err := filepath.Abs(worktreePath)
...
hookEnv := hook.Env{
    WorktreePath: absolutePath,   // ✅ absolute

But command_start.go passes worktreePath directly without resolution:

// command_start.go
hookEnv := hook.Env{
    WorktreePath: worktreePath,   // ⚠️ potentially relative

If git.CreateWorktree returns a relative path, GW_WORKTREE_PATH will be relative in the start hook, breaking integrations like tmux new-window -c "$GW_WORKTREE_PATH". Should use filepath.Abs(worktreePath) here for consistency.


Minor: Unnecessary variable alias

In command_start.go:

repoNameForHook := repoName
hookEnv := hook.Env{
    RepoName: repoNameForHook,

repoNameForHook is an alias that adds no clarity. Use repoName directly.


Misleading comment in hook.go

// Env holds environment variables passed to hook commands
type Env struct {
    ...
    Command string // "start", "checkout", or "end"
}

end hooks are not implemented — this comment will confuse future readers. Change to // "start" or "checkout" or remove the value examples.


Potential issue: Hook values with = in config parsing

The .gwrc parser appears to split on the first =. The test case "hook with equals sign in value" validates this works for env GW_EXTRA=1 my-script. However, the Save() method formats hook values without quoting:

hookLines += fmt.Sprintf("post_start_hook = %s\n", c.PostStartHook)

If a user sets a hook with leading/trailing whitespace or a value that starts with =, it could round-trip incorrectly. This may be an acceptable limitation but worth a comment or a note in the README.


Security note: paths with special characters in example scripts

The iTerm2 example hook script uses single-quoted paths in AppleScript:

write text "cd '${GW_WORKTREE_PATH}' && clear"

If GW_WORKTREE_PATH contains a single quote (e.g., /Users/it's-me/work/repo-123), the AppleScript will break. The tmux example is fine since tmux handles this via -c. Worth adding a comment in iterm2-new-tab.sh about this limitation.


No hook timeout

Hooks run synchronously with no timeout. A misbehaving hook (e.g., a script waiting for input) will block gw indefinitely. Not a blocker, but worth a note in the README warning users, or a future post_hook_timeout config option.


Test coverage: Good

The test suite is comprehensive:

  • internal/hook/hook_test.go covers empty command, env vars, failure, stderr, and env inheritance
  • cmd/command_test.go covers success and failure cases for both start and checkout
  • internal/config/config_test.go covers load, save, round-trip, and default values

The comment // Test setting post_start_hook (not supported via SetConfigItem, it's bool-only) at line ~546 of config_test.go documents a gap worth a follow-up — it may be useful to support SetConfigItem for string fields so users can configure hooks via gw config set.


Summary

Issue Severity
worktreePath not resolved to absolute in start hook Bug
Misleading // "end" comment in hook.go Minor
repoNameForHook unnecessary alias Nit
iTerm2 script breaks on paths with single quotes Minor
No hook timeout Design consideration

The path inconsistency is the one concrete bug — everything else is polish. Great feature overall.

🤖 Generated with Claude Code

sotarok and others added 2 commits April 6, 2026 22:26
- Remove stale "end" from hook command comment (not yet implemented)
- Remove unnecessary intermediate variable repoNameForHook
- Use filepath.Abs for worktreePath in start command to match checkout behavior

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
@sotarok sotarok merged commit 953beea into main Apr 6, 2026
10 of 11 checks passed
@sotarok sotarok deleted the feat/post-hooks branch April 6, 2026 13:37
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.

Add iTerm2 tab orchestration for new worktrees opened via shell integration

1 participant