{"record":{"id":"ecf92f34cb37c832","repo":"sds/overcommit","slug":"hook-specified-a-required-executable-or-command","errorCode":null,"errorMessage":"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","messagePattern":"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","errorType":"exception","errorClass":"Overcommit::Exceptions::InvalidHookDefinition","httpStatus":null,"severity":"error","filePath":"lib/overcommit/hook_signer.rb","lineNumber":42,"sourceCode":"    # @return [String]\n    def hook_path\n      @hook_path ||= begin\n        plugin_path = File.join(@config.plugin_directory,\n                                @context.hook_type_name,\n                                \"#{Overcommit::Utils.snake_case(@hook_name)}.rb\")\n\n        if File.exist?(plugin_path)\n          plugin_path\n        else\n          # Otherwise this is an ad hoc hook using an existing hook script\n          hook_config = @config.for_hook(@hook_name, @context.hook_class_name)\n\n          command = Array(hook_config['command'] || hook_config['required_executable'])\n\n          if @config.verify_signatures? &&\n            signable_file?(command.first) &&\n            !Overcommit::GitRepo.tracked?(command.first)\n            raise Overcommit::Exceptions::InvalidHookDefinition,\n                  'Hook specified a `required_executable` or `command` that ' \\\n                  'is a path relative to the root of the repository, and so ' \\\n                  'must be tracked by Git in order to be signed'\n          end\n\n          File.join(Overcommit::Utils.repo_root, command.first.to_s)\n        end\n      end\n    end\n\n    def signable_file?(file)\n      return unless file\n\n      sep = Overcommit::OS.windows? ? '\\\\' : File::SEPARATOR\n      file.start_with?(\".#{sep}\") ||\n        file.start_with?(Overcommit::Utils.repo_root)\n    end\n","sourceCodeStart":24,"sourceCodeEnd":60,"githubUrl":"https://github.com/sds/overcommit/blob/fee0cd74b26eb81f64b43b7fab17bbb541575cc4/lib/overcommit/hook_signer.rb#L24-L60","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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"],"exampleFix":"# before (.overcommit.yml)\nPreCommit:\n  LocalCheck:\n    command: ./bin/local_check.sh   # untracked -> raises\n\n# after\n$ git add bin/local_check.sh\n$ git commit -m 'Track hook script so Overcommit can sign it'","handlingStrategy":"validation","validationCode":"cmd = Array(hook_config['command'] || hook_config['required_executable']).first.to_s\nrepo_relative = cmd.start_with?('./', Overcommit::Utils.repo_root)\nif config.verify_plugin_signatures? && repo_relative && !Overcommit::GitRepo.tracked?(cmd)\n  abort \"#{cmd} must be git-tracked before enabling the hook\"\nend","typeGuard":"def signable_command_ready?(cmd, verify: true)\n  return true unless verify\n  return true unless cmd.start_with?('./', Overcommit::Utils.repo_root)\n  Overcommit::GitRepo.tracked?(cmd)\nend","tryCatchPattern":"begin\n  signer.hook_path\nrescue Overcommit::Exceptions::InvalidHookDefinition => e\n  abort \"Track the hook script in Git or use a PATH executable: #{e.message}\"\nend","preventionTips":["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"],"tags":["git","overcommit","signature-verification","untracked-file","security"],"backgroundTag":"git-untracked-file","analyzedSha":"fee0cd74b26eb81f64b43b7fab17bbb541575cc4","analyzedAt":"2026-08-23T04:07:28.153Z","schemaVersion":2},"datasetVersion":"2026-08-23T08:06:27.607Z"}