sds/overcommit · error · Overcommit::Exceptions::InvalidHookDefinition
Hook specified a `required_executable` or `command` that is
Error message
Hook specified a `required_executable` or `command` that is a path relative to the root of the repository, and so must be tracked by Git in order to be signed
What it means
With verify_plugin_signatures on (the default), HookSigner#hook_path signs plugin file contents; for an ad hoc hook there is no plugin file, so the hook's command/required_executable is signed instead - but only repo-relative paths ('./...' or under the repo root, per signable_file?) are signable, and a signable path must be tracked by Git so its signature is meaningful. This InvalidHookDefinition fires when the path is repo-relative yet GitRepo.tracked? returns false.
Source
Thrown at lib/overcommit/hook_signer.rb:42
# @return [String]
def hook_path
@hook_path ||= begin
plugin_path = File.join(@config.plugin_directory,
@context.hook_type_name,
"#{Overcommit::Utils.snake_case(@hook_name)}.rb")
if File.exist?(plugin_path)
plugin_path
else
# Otherwise this is an ad hoc hook using an existing hook script
hook_config = @config.for_hook(@hook_name, @context.hook_class_name)
command = Array(hook_config['command'] || hook_config['required_executable'])
if @config.verify_signatures? &&
signable_file?(command.first) &&
!Overcommit::GitRepo.tracked?(command.first)
raise Overcommit::Exceptions::InvalidHookDefinition,
'Hook specified a `required_executable` or `command` that ' \
'is a path relative to the root of the repository, and so ' \
'must be tracked by Git in order to be signed'
end
File.join(Overcommit::Utils.repo_root, command.first.to_s)
end
end
end
def signable_file?(file)
return unless file
sep = Overcommit::OS.windows? ? '\\' : File::SEPARATOR
file.start_with?(".#{sep}") ||
file.start_with?(Overcommit::Utils.repo_root)
end
View on GitHub (pinned to fee0cd74b2)
Solutions
- Track the executable: 'git add bin/check.sh && git commit -m "Track hook script for signing"'
- If the tool must stay out of Git, reference it by bare PATH name (required_executable: eslint) or an absolute path outside the repo so it is not treated as signable
- As a last resort set 'verify_plugin_signatures: false' in .overcommit.yml - this disables the tamper check, so prefer the first two options
Example fix
# before (.overcommit.yml)
PreCommit:
LocalCheck:
command: ./bin/local_check.sh # untracked -> raises
# after
$ git add bin/local_check.sh
$ git commit -m 'Track hook script so Overcommit can sign it' Defensive patterns
Strategy: validation
Validate before calling
cmd = Array(hook_config['command'] || hook_config['required_executable']).first.to_s
repo_relative = cmd.start_with?('./', Overcommit::Utils.repo_root)
if config.verify_plugin_signatures? && repo_relative && !Overcommit::GitRepo.tracked?(cmd)
abort "#{cmd} must be git-tracked before enabling the hook"
end Type guard
def signable_command_ready?(cmd, verify: true)
return true unless verify
return true unless cmd.start_with?('./', Overcommit::Utils.repo_root)
Overcommit::GitRepo.tracked?(cmd)
end Try / catch
begin
signer.hook_path
rescue Overcommit::Exceptions::InvalidHookDefinition => e
abort "Track the hook script in Git or use a PATH executable: #{e.message}"
end Prevention
- Commit hook wrapper scripts in the same commit that adds the hook config
- Never gitignore files referenced by command/required_executable
- Run 'git ls-files --error-unmatch <script>' in CI for every repo-relative hook command
When it happens
Trigger: An ad hoc hook (config-only, no plugin file) sets command: ['./bin/check.sh'] or required_executable: './scripts/lint' while that file is untracked - created but never git-added, or matched by .gitignore - and signature verification is enabled.
Common situations: New wrapper script written for a hook but not committed yet; hook scripts generated at checkout time and gitignored; teammates cloned the repo but the script was never checked in.
Related errors
- Unable to write to local repo git config: #{result.stderr}
- Unable to read from local repo git config: #{result.stderr}
- does not appear to be a git repository
- Hook '#{File.expand_path(hook_type)}' already exists and was
- Class #{hook_name} is not a subclass of #{hook_base_class}.
AI-assisted analysis of sds/overcommit@fee0cd74b2 (2026-08-23).
Data as JSON: /api/errors/ecf92f34cb37c832.
Report an issue: GitHub.