Skip to content

Consolidate hook system into internal/githook, fix early exit bug#300

Merged
wesm merged 6 commits intomainfrom
hook-maintenance
Feb 18, 2026
Merged

Consolidate hook system into internal/githook, fix early exit bug#300
wesm merged 6 commits intomainfrom
hook-maintenance

Conversation

@wesm
Copy link
Collaborator

@wesm wesm commented Feb 18, 2026

Summary

  • Consolidates all hook install/upgrade/uninstall logic from cmd/roborev/main.go and internal/daemon/server.go into a new internal/githook/ package, eliminating ~300 lines of duplicated code
  • Fixes early exit bug (roborev init hook fails when appended after hooks with early exits #298): embedded hook snippets are now function-wrapped and inserted after the shebang line instead of appended at the end, so husky/lint-staged hooks with exit 0 no longer block roborev
  • Auto-upgrades outdated hooks and installs missing companion hooks (e.g. post-rewrite) from roborev review, without conflicting with explicit uninstall-hook
  • Bumps version markers to v3/v2 to trigger upgrades for existing installations
  • Adds HasRealErrors helper so init correctly distinguishes non-shell hook warnings from real install failures in joined errors
  • Moves autoInstallHooks after CLI argument validation so invalid flag combos don't cause hook side effects
  • Missing and NotInstalled now return false on non-ENOENT read errors instead of triggering false positives

Test plan

  • go test ./internal/githook/... — 1390 lines of new package tests covering install, uninstall, embed, upgrade, version markers, HasRealErrors, Missing/NotInstalled edge cases
  • go test ./cmd/roborev/... — updated CLI tests including mixed-error init, non-shell warning, review side-effect assertions
  • go test -tags integration ./cmd/roborev/... — integration tests for init upgrade, early exit hook, hooks directory creation
  • golangci-lint run — 0 issues
  • go vet ./... — clean

🤖 Generated with Claude Code

wesm and others added 6 commits February 18, 2026 13:57
Move all hook install/upgrade/uninstall logic from cmd/roborev/main.go
and internal/daemon/server.go into a new internal/githook package,
eliminating ~300 lines of duplicated code.

Fix #298: hooks embedded in existing scripts (e.g. husky) now use a
function wrapper inserted after the shebang instead of appending at
the end, so they run before any `exit 0` in the existing hook.

Key changes:
- New internal/githook package with Install, InstallAll, Uninstall,
  NeedsUpgrade, Missing, and content generators
- Embeddable snippets use _roborev_hook()/_roborev_remap() function
  wrappers with `return 0` instead of `exit 0`
- embedSnippet() inserts after shebang, not at end of file
- Bump version markers to v3/v2 to trigger upgrades
- Auto-install missing/outdated hooks from `roborev review` (non-quiet)
- Uninstall handles v3 function-wrapped format (} and return 0)
- Integration test for husky-style early exit hook

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…edge case

1. initCmd now warns and continues (instead of failing) when a
   non-shell hook exists, so daemon setup still completes.

2. autoInstallHooks uses new NotInstalled() instead of Missing()
   so a completely absent post-commit hook is also auto-installed.
   Missing() only detects absent dependent hooks (post-rewrite)
   when post-commit already has roborev content.

3. embedSnippet normalizes the shebang line to end with a newline
   before concatenating the snippet, preventing corrupted output
   like "#!/bin/sh# roborev ..." when the input lacks a trailing
   newline.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1. initCmd now only downgrades ErrNonShellHook to a warning;
   real install failures (permission, I/O) still abort init.
   Install() wraps the sentinel ErrNonShellHook so callers can
   use errors.Is() to distinguish it.

2. NotInstalled returns true only for os.IsNotExist errors;
   other read failures (permission, I/O) return false to avoid
   masking problems and triggering repeated auto-install attempts.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
InstallAll now uses errors.Join to attempt all hooks instead of
stopping at first error. autoInstallHooks silently suppresses
ErrNonShellHook warnings. Added TestInitCmdWarnsOnNonShellHook
and TestInitCmdFailsOnHookWriteError for init branch coverage.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…sking

- autoInstallHooks uses Missing (not NotInstalled) so explicit
  uninstall-hook is respected — review only upgrades outdated hooks
  and installs companion hooks when roborev is already present.
- Move autoInstallHooks after CLI argument validation so invalid
  flag combinations don't cause hook side effects.
- Add HasRealErrors helper to distinguish joined errors containing
  only ErrNonShellHook from those with real failures. init now
  correctly fails when a non-shell warning is joined with an
  actual write error.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Missing returns false on non-ENOENT read errors (e.g. permission
  denied, is-a-directory) instead of treating them as missing.
- Add TestInitCmdFailsOnMixedHookErrors: non-shell post-commit +
  unwritable post-rewrite produces a fatal error, not a warning.
- Add TestReviewInvalidArgsNoSideEffects: invalid flag combos
  (--branch --dirty) produce no hook writes or daemon contact.
- Add TestMissing non-ENOENT subtest for the read-error edge case.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@roborev-ci
Copy link

roborev-ci bot commented Feb 18, 2026

roborev: Combined Review (4995ad31)

Verdict: No Medium/High/Critical issues identified; this diff is clean at actionable severity.

All reported concerns were below Medium severity and were omitted per your rules.
Security and default reviews otherwise converged on no actionable regressions.


Synthesized from 4 reviews (agents: codex, gemini | types: security, default)

@wesm wesm merged commit 18a7b54 into main Feb 18, 2026
8 checks passed
@wesm wesm deleted the hook-maintenance branch February 18, 2026 21:34
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.

1 participant

Comments