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

  1. Track the executable: 'git add bin/check.sh && git commit -m "Track hook script for signing"'
  2. 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
  3. 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

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


AI-assisted analysis of sds/overcommit@fee0cd74b2 (2026-08-23). Data as JSON: /api/errors/ecf92f34cb37c832. Report an issue: GitHub.