ModischFabrications: Oh, I also added *.idea to the gitignore to prevent intellij from adding stuff that's irrelevant, feel free to remove it if it's unwanted. 
asottile: > 
> 
> Oh, I also added *.idea to the gitignore to prevent intellij from adding stuff that's irrelevant, feel free to remove it if it's unwanted.

just a general tip for open source, you should never touch `.gitignore` for other people's projects
ModischFabrications: Should be ready for merging. 
ModischFabrications: alright, any last objections? I think it's done.
asottile: also, please stop saying "it's ready to merge" or "it's done" -- it sounds very pretentious and _you_ don't decide when it's done, the project does
diff --git a/pre_commit/commands/hook_impl.py b/pre_commit/commands/hook_impl.py
index 4843fc7..d0e226f 100644
--- a/pre_commit/commands/hook_impl.py
+++ b/pre_commit/commands/hook_impl.py
@@ -150,6 +150,7 @@ def _pre_push_ns(
 _EXPECTED_ARG_LENGTH_BY_HOOK = {
     'commit-msg': 1,
     'post-checkout': 3,
+    'post-commit': 0,
     'pre-commit': 0,
     'pre-merge-commit': 0,
     'pre-push': 2,
@@ -186,7 +187,7 @@ def _run_ns(
         return _pre_push_ns(color, args, stdin)
     elif hook_type in {'commit-msg', 'prepare-commit-msg'}:
         return _ns(hook_type, color, commit_msg_filename=args[0])
-    elif hook_type in {'pre-merge-commit', 'pre-commit'}:
+    elif hook_type in {'post-commit', 'pre-merge-commit', 'pre-commit'}:
         return _ns(hook_type, color)
     elif hook_type == 'post-checkout':
         return _ns(
diff --git a/pre_commit/commands/run.py b/pre_commit/commands/run.py
index 8c8401c..8a9352d 100644
--- a/pre_commit/commands/run.py
+++ b/pre_commit/commands/run.py
@@ -221,7 +221,8 @@ def _compute_cols(hooks: Sequence[Hook]) -> int:
 
 
 def _all_filenames(args: argparse.Namespace) -> Collection[str]:
-    if args.hook_stage == 'post-checkout':  # no files for post-checkout
+    # these hooks do not operate on files
+    if args.hook_stage in {'post-checkout', 'post-commit'}:
         return ()
     elif args.hook_stage in {'prepare-commit-msg', 'commit-msg'}:
         return (args.commit_msg_filename,)
diff --git a/pre_commit/constants.py b/pre_commit/constants.py
index e2b8e3a..5150fdc 100644
--- a/pre_commit/constants.py
+++ b/pre_commit/constants.py
@@ -17,8 +17,8 @@ VERSION = importlib_metadata.version('pre_commit')
 
 # `manual` is not invoked by any installed git hook.  See #719
 STAGES = (
-    'commit', 'merge-commit', 'prepare-commit-msg', 'commit-msg', 'manual',
-    'post-checkout', 'push',
+    'commit', 'merge-commit', 'prepare-commit-msg', 'commit-msg',
+    'post-commit', 'manual', 'post-checkout', 'push',
 )
 
 DEFAULT = 'default'
diff --git a/pre_commit/main.py b/pre_commit/main.py
index 790b347..874eb53 100644
--- a/pre_commit/main.py
+++ b/pre_commit/main.py
@@ -79,7 +79,7 @@ def _add_hook_type_option(parser: argparse.ArgumentParser) -> None:
     parser.add_argument(
         '-t', '--hook-type', choices=(
             'pre-commit', 'pre-merge-commit', 'pre-push',
-            'prepare-commit-msg', 'commit-msg', 'post-checkout',
+            'prepare-commit-msg', 'commit-msg', 'post-commit', 'post-checkout',
         ),
         action=AppendReplaceDefault,
         default=['pre-commit'],

allow post-commit hook
I would like to tag git versions whenever specific conditions are true. 

I can't use pre-commit because I don't have a commit yet, pre-push would lead to the new tag not being pushed together with the rest. Did I miss something or is this hook simply not added yet?
diff --git a/tests/commands/hook_impl_test.py b/tests/commands/hook_impl_test.py
index ddf65b7..cce4a25 100644
--- a/tests/commands/hook_impl_test.py
+++ b/tests/commands/hook_impl_test.py
@@ -96,6 +96,7 @@ def test_run_legacy_recursive(tmpdir):
         ('pre-merge-commit', []),
         ('pre-push', ['branch_name', 'remote_name']),
         ('commit-msg', ['.git/COMMIT_EDITMSG']),
+        ('post-commit', []),
         ('post-checkout', ['old_head', 'new_head', '1']),
         # multiple choices for commit-editmsg
         ('prepare-commit-msg', ['.git/COMMIT_EDITMSG']),
@@ -149,6 +150,13 @@ def test_run_ns_commit_msg():
     assert ns.commit_msg_filename == '.git/COMMIT_MSG'
 
 
+def test_run_ns_post_commit():
+    ns = hook_impl._run_ns('post-commit', True, (), b'')
+    assert ns is not None
+    assert ns.hook_stage == 'post-commit'
+    assert ns.color is True
+
+
 def test_run_ns_post_checkout():
     ns = hook_impl._run_ns('post-checkout', True, ('a', 'b', 'c'), b'')
     assert ns is not None
diff --git a/tests/commands/install_uninstall_test.py b/tests/commands/install_uninstall_test.py
index 66b9190..6d75e68 100644
--- a/tests/commands/install_uninstall_test.py
+++ b/tests/commands/install_uninstall_test.py
@@ -726,6 +726,32 @@ def test_commit_msg_legacy(commit_msg_repo, tempdir_factory, store):
     assert second_line.startswith('Must have "Signed off by:"...')
 
 
+def test_post_commit_integration(tempdir_factory, store):
+    path = git_dir(tempdir_factory)
+    config = [
+        {
+            'repo': 'local',
+            'hooks': [{
+                'id': 'post-commit',
+                'name': 'Post commit',
+                'entry': 'touch post-commit.tmp',
+                'language': 'system',
+                'always_run': True,
+                'verbose': True,
+                'stages': ['post-commit'],
+            }],
+        },
+    ]
+    write_config(path, config)
+    with cwd(path):
+        _get_commit_output(tempdir_factory)
+        assert not os.path.exists('post-commit.tmp')
+
+        install(C.CONFIG_FILE, store, hook_types=['post-commit'])
+        _get_commit_output(tempdir_factory)
+        assert os.path.exists('post-commit.tmp')
+
+
 def test_post_checkout_integration(tempdir_factory, store):
     path = git_dir(tempdir_factory)
     config = [
diff --git a/tests/repository_test.py b/tests/repository_test.py
index 3c7a637..f55c34c 100644
--- a/tests/repository_test.py
+++ b/tests/repository_test.py
@@ -880,7 +880,7 @@ def test_manifest_hooks(tempdir_factory, store):
         require_serial=False,
         stages=(
             'commit', 'merge-commit', 'prepare-commit-msg', 'commit-msg',
-            'manual', 'post-checkout', 'push',
+            'post-commit', 'manual', 'post-checkout', 'push',
         ),
         types=['file'],
         verbose=False,

pip install -e .[dev]
parse_log_pytest
requirements.txt
pytest
3.9
requirements-dev.txt
pytest --no-header -rA --tb=line --color=no -p no:cacheprovider -W ignore::DeprecationWarning
cfgv==3.4.0
covdefaults==2.3.0
coverage==7.8.0
distlib==0.3.9
exceptiongroup==1.2.2
filelock==3.18.0
identify==2.6.9
iniconfig==2.1.0
nodeenv==1.9.1
packaging==24.2
platformdirs==4.3.7
pluggy==1.5.0
-e git+https://github.com/pre-commit/pre-commit.git@3b3b33ea29ee11b10a41f237b1ced8c2a4b91592#egg=pre_commit
pytest==8.3.5
pytest-env==1.1.5
PyYAML==6.0.2
toml==0.10.2
tomli==2.2.1
virtualenv==20.29.3

name: pre-commit
channels:
  - defaults
  - https://repo.anaconda.com/pkgs/main
  - https://repo.anaconda.com/pkgs/r
  - conda-forge
dependencies:
  - _libgcc_mutex=0.1=main
  - _openmp_mutex=5.1=1_gnu
  - ca-certificates=2025.2.25=h06a4308_0
  - ld_impl_linux-64=2.40=h12ee557_0
  - libffi=3.4.4=h6a678d5_1
  - libgcc-ng=11.2.0=h1234567_1
  - libgomp=11.2.0=h1234567_1
  - libstdcxx-ng=11.2.0=h1234567_1
  - ncurses=6.4=h6a678d5_0
  - openssl=3.0.16=h5eee18b_0
  - pip=25.0=py39h06a4308_0
  - python=3.9.21=he870216_1
  - readline=8.2=h5eee18b_0
  - setuptools=75.8.0=py39h06a4308_0
  - sqlite=3.45.3=h5eee18b_0
  - tk=8.6.14=h39e8969_0
  - tzdata=2025a=h04d1e81_0
  - wheel=0.45.1=py39h06a4308_0
  - xz=5.6.4=h5eee18b_1
  - zlib=1.2.13=h5eee18b_1
  - pip:
      - cfgv==3.4.0
      - covdefaults==2.3.0
      - coverage==7.8.0
      - distlib==0.3.9
      - exceptiongroup==1.2.2
      - filelock==3.18.0
      - identify==2.6.9
      - iniconfig==2.1.0
      - nodeenv==1.9.1
      - packaging==24.2
      - platformdirs==4.3.7
      - pluggy==1.5.0
      - pytest==8.3.5
      - pytest-env==1.1.5
      - pyyaml==6.0.2
      - toml==0.10.2
      - tomli==2.2.1
      - virtualenv==20.29.3
prefix: /opt/conda/envs/pre-commit

tests/commands/hook_impl_test.py::test_check_args_length_ok[post-commit-args4] tests/commands/hook_impl_test.py::test_run_ns_post_commit tests/commands/install_uninstall_test.py::test_post_commit_integration tests/repository_test.py::test_manifest_hooks
tests/commands/install_uninstall_test.py::test_install_in_submodule_and_run tests/commands/install_uninstall_test.py::test_pre_merge_commit_integration tests/repository_test.py::test_switch_language_versions_doesnt_clobber tests/repository_test.py::test_run_a_ruby_hook tests/repository_test.py::test_run_versioned_ruby_hook tests/repository_test.py::test_run_ruby_hook_with_disable_shared_gems tests/repository_test.py::test_golang_hook tests/repository_test.py::test_golang_hook_still_works_when_gobin_is_set tests/repository_test.py::test_rust_hook tests/repository_test.py::test_additional_rust_cli_dependencies_installed[cli:shellharden:3.1.0] tests/repository_test.py::test_additional_rust_cli_dependencies_installed[cli:shellharden] tests/repository_test.py::test_additional_rust_lib_dependencies_installed tests/repository_test.py::test_additional_ruby_dependencies_installed tests/repository_test.py::test_additional_golang_dependencies_installed tests/repository_test.py::test_local_golang_additional_dependencies tests/repository_test.py::test_local_rust_additional_dependencies tests/repository_test.py::test_perl_hook tests/repository_test.py::test_local_perl_additional_dependencies
tests/commands/hook_impl_test.py::test_validate_config_file_exists tests/commands/hook_impl_test.py::test_validate_config_missing tests/commands/hook_impl_test.py::test_validate_config_skip_missing_config tests/commands/hook_impl_test.py::test_validate_config_skip_via_env_variable tests/commands/hook_impl_test.py::test_run_legacy_does_not_exist tests/commands/hook_impl_test.py::test_run_legacy_executes_legacy_script tests/commands/hook_impl_test.py::test_run_legacy_pre_push_returns_stdin tests/commands/hook_impl_test.py::test_run_legacy_recursive tests/commands/hook_impl_test.py::test_check_args_length_ok[pre-commit-args0] tests/commands/hook_impl_test.py::test_check_args_length_ok[pre-merge-commit-args1] tests/commands/hook_impl_test.py::test_check_args_length_ok[pre-push-args2] tests/commands/hook_impl_test.py::test_check_args_length_ok[commit-msg-args3] tests/commands/hook_impl_test.py::test_check_args_length_ok[post-checkout-args5] tests/commands/hook_impl_test.py::test_check_args_length_ok[prepare-commit-msg-args6] tests/commands/hook_impl_test.py::test_check_args_length_ok[prepare-commit-msg-args7] tests/commands/hook_impl_test.py::test_check_args_length_ok[prepare-commit-msg-args8] tests/commands/hook_impl_test.py::test_check_args_length_error_too_many_plural tests/commands/hook_impl_test.py::test_check_args_length_error_too_many_singluar tests/commands/hook_impl_test.py::test_check_args_length_prepare_commit_msg_error tests/commands/hook_impl_test.py::test_run_ns_pre_commit tests/commands/hook_impl_test.py::test_run_ns_commit_msg tests/commands/hook_impl_test.py::test_run_ns_post_checkout tests/commands/hook_impl_test.py::test_run_ns_pre_push_updating_branch tests/commands/hook_impl_test.py::test_run_ns_pre_push_new_branch tests/commands/hook_impl_test.py::test_run_ns_pre_push_new_branch_existing_rev tests/commands/hook_impl_test.py::test_pushing_orphan_branch tests/commands/hook_impl_test.py::test_run_ns_pre_push_deleting_branch tests/commands/hook_impl_test.py::test_hook_impl_main_noop_pre_push tests/commands/hook_impl_test.py::test_hook_impl_main_runs_hooks tests/commands/install_uninstall_test.py::test_is_not_script tests/commands/install_uninstall_test.py::test_is_script tests/commands/install_uninstall_test.py::test_is_previous_pre_commit tests/commands/install_uninstall_test.py::test_shebang_windows tests/commands/install_uninstall_test.py::test_shebang_posix_not_on_path tests/commands/install_uninstall_test.py::test_shebang_posix_on_path tests/commands/install_uninstall_test.py::test_install_pre_commit tests/commands/install_uninstall_test.py::test_install_hooks_directory_not_present tests/commands/install_uninstall_test.py::test_install_multiple_hooks_at_once tests/commands/install_uninstall_test.py::test_install_refuses_core_hookspath tests/commands/install_uninstall_test.py::test_install_hooks_dead_symlink tests/commands/install_uninstall_test.py::test_uninstall_does_not_blow_up_when_not_there tests/commands/install_uninstall_test.py::test_uninstall tests/commands/install_uninstall_test.py::test_install_pre_commit_and_run tests/commands/install_uninstall_test.py::test_install_pre_commit_and_run_custom_path tests/commands/install_uninstall_test.py::test_install_in_worktree_and_run tests/commands/install_uninstall_test.py::test_commit_am tests/commands/install_uninstall_test.py::test_unicode_merge_commit_message tests/commands/install_uninstall_test.py::test_install_idempotent tests/commands/install_uninstall_test.py::test_environment_not_sourced tests/commands/install_uninstall_test.py::test_failing_hooks_returns_nonzero tests/commands/install_uninstall_test.py::test_install_existing_hooks_no_overwrite tests/commands/install_uninstall_test.py::test_legacy_overwriting_legacy_hook tests/commands/install_uninstall_test.py::test_install_existing_hook_no_overwrite_idempotent tests/commands/install_uninstall_test.py::test_failing_existing_hook_returns_1 tests/commands/install_uninstall_test.py::test_install_overwrite_no_existing_hooks tests/commands/install_uninstall_test.py::test_install_overwrite tests/commands/install_uninstall_test.py::test_uninstall_restores_legacy_hooks tests/commands/install_uninstall_test.py::test_replace_old_commit_script tests/commands/install_uninstall_test.py::test_uninstall_doesnt_remove_not_our_hooks tests/commands/install_uninstall_test.py::test_installs_hooks_with_hooks_True tests/commands/install_uninstall_test.py::test_install_hooks_command tests/commands/install_uninstall_test.py::test_installed_from_venv tests/commands/install_uninstall_test.py::test_pre_push_integration_failing tests/commands/install_uninstall_test.py::test_pre_push_integration_accepted tests/commands/install_uninstall_test.py::test_pre_push_force_push_without_fetch tests/commands/install_uninstall_test.py::test_pre_push_new_upstream tests/commands/install_uninstall_test.py::test_pre_push_environment_variables tests/commands/install_uninstall_test.py::test_pre_push_integration_empty_push tests/commands/install_uninstall_test.py::test_pre_push_legacy tests/commands/install_uninstall_test.py::test_commit_msg_integration_failing tests/commands/install_uninstall_test.py::test_commit_msg_integration_passing tests/commands/install_uninstall_test.py::test_commit_msg_legacy tests/commands/install_uninstall_test.py::test_post_checkout_integration tests/commands/install_uninstall_test.py::test_prepare_commit_msg_integration_failing tests/commands/install_uninstall_test.py::test_prepare_commit_msg_integration_passing tests/commands/install_uninstall_test.py::test_prepare_commit_msg_legacy tests/commands/install_uninstall_test.py::test_install_disallow_missing_config tests/commands/install_uninstall_test.py::test_install_allow_missing_config tests/commands/install_uninstall_test.py::test_install_temporarily_allow_mising_config tests/repository_test.py::test_grep_hook_matching tests/repository_test.py::test_grep_hook_case_insensitive tests/repository_test.py::test_grep_hook_not_matching[nope] tests/repository_test.py::test_grep_hook_not_matching[foo'bar] tests/repository_test.py::test_grep_hook_not_matching[^\\[INFO\\]] tests/repository_test.py::test_additional_dependencies_roll_forward tests/repository_test.py::test_additional_node_dependencies_installed tests/repository_test.py::test_fail_hooks tests/repository_test.py::test_unknown_keys tests/repository_test.py::test_reinstall tests/repository_test.py::test_really_long_file_paths tests/repository_test.py::test_config_overrides_repo_specifics tests/repository_test.py::test_default_language_version tests/repository_test.py::test_default_stages tests/repository_test.py::test_hook_id_not_present tests/repository_test.py::test_too_new_version tests/repository_test.py::test_versions_ok[0.1.0] tests/repository_test.py::test_versions_ok[2.3.0]
pre_commit/commands/hook_impl.py pre_commit/main.py pre_commit/constants.py pre_commit/commands/run.py
pre_commit/commands/hook_impl.py pre_commit/main.py pre_commit/constants.py pre_commit/commands/run.py