hk 2.5.0 makes stashing safe when hk is killed and when several hk processes run at once, loads configs much faster, and reports diagnostics from many more builtins. It also fixes a long list of cases where a check passed without checking anything, along with several Windows problems.
Highlights
- Stashing holds up under interruption and concurrency. SIGTERM, SIGHUP and crashes no longer leave your unstaged changes stuck in
git stash. Hooks running in linked worktrees now take turns stashing. Several edge cases that dropped or mixed up changes are fixed. - Configs load faster. A cold config load drops from about 1.4 s to about 0.07 s, and
hk validateis about 14x faster. Several per-step and stashing costs are lower in large repos. - Fewer checks pass when they should fail.
hk validatenow catches more config mistakes. Several builtins andhk utilchecks no longer skip binary files, case conflicts in directory names, or executable bits recorded in git.
Added
-
Diagnostics from more builtins.
actionlint,flake8,revive,buf_lint,clang_format,mypy,mado,rumdl,ktlint,buildifier_lint,cpp_lint,xmllintandsorbetnow feedhk check --sarif,--format jsonand the MCP dashboard. Their normal output doesn't change. (#1623, #1624, #1626, #1627, #1630, @jdx) -
Broader
gccdiagnostic format and a newdiagnostic_severitystep setting (#1629, #1630, @jdx). Thegccparser now reads:- lines with no column (
path:line: message) - a
vet:prefix and Go# pkgheader lines - tool output that ends with a summary line, which is no longer added to the last finding
diagnostic_severity(error,warning,noteorhelp; defaulterror) sets the severity forrule-name: messagefindings that don't name one. - lines with no column (
-
expect.diagnosticsin step tests (#1622, @jdx).hk testcan check that a step'sdiagnostic_formatturns its output into the diagnostics you expect. Fields you leave out match anything, andmessagematches a substring.tests { ["reports a warning"] { expect { code = 1 diagnostics { new { path = "src/main.c"; line = 2; severity = "warning"; rule = "W1" } } } } }
-
exec_ok()for step conditions (#1610, @jdx).exec_ok(command)is true when the command exits 0. Use it to gate a step on a shell test.exec()still returns stdout, and now gives a clear error instead of panicking on bad arguments or output that isn't valid UTF-8.condition = "exec_ok('test -f check.js')"
-
MCP
scopeargument (#1600, #1640, @jdx).start_check,start_safe_checkandstart_safe_fixacceptscope:all(default): the whole project, as beforechanged: staged, unstaged and untracked filesunstaged: unstaged and untracked filesstaged: only files in the index
-
hk agent stop-hook(#1604, @jdx). The Stop hooks thathk agent hooks --target claude-code|codexgenerates now run this command. It runshk run check --safe, always exits 0, and prints a valid{"decision":"block","reason":...}when the check fails. Before, the hooks printed raw JSON and exited with the linter's status, which the agents didn't handle. Regenerate your hook snippets to use it. -
hk validatewarnings (#1590, @jdx).hk validatenow warns about:- misspelled properties, with a did-you-mean suggestion
dependsentries that have no effect- steps with no command
- globs that can never match, such as
./src/*.js, a leading/,!patterns or comma-separated lists
min_hk_versionnow acceptsv1.2.3and two-part versions like2.5. -
hk initdetects 47 more tools from their own config files or from manifest entries that name them. Examples include.flake8,stylua.toml,deno.json,.sqlfluff,.clang-format,[tool.isort]andtypos. (#1576, @jdx) -
Stash lock timeout.
HK_STASH_LOCK_TIMEOUT(orgit config hk.stashLockTimeout) sets how long hk waits for another hk process that is stashing. The default is 300 seconds, and0fails at once. (#1636, @jdx)
Changed
-
Cold config loads are about 20x faster. A config now evaluates only the builtins it uses, which cuts a cold load from about 1.4 s to about 0.07 s (#1581). The pklr 4.0 update makes
hk validateon hk's own config about 14x faster (#1644). (@jdx) -
Other speedups:
workspace_indicatoris resolved once per directory, saving about 120 ms per step in large monorepos (#1599).- Runs that name their files, such as
hk check FILE, skip the untracked-file scan (#1587). - Stashing no longer rescans the worktree first, saving about 80 ms on large repos (#1595).
- Unchanged unstaged files are restored with one git process (#1612).
- hk now uses the mimalloc memory allocator (#1635).
(@jdx)
-
Read-only checks run in parallel in fix hooks (#1586, @jdx). In fix hooks, steps that only have a
checkdeclared witheffect = "read"now run side by side instead of one after another. They still wait for any fixer that writes the same files. The builtinmypyandtscdeclareeffect = "write", so they are not affected. -
CRLF files stay CRLF in
trailing_whitespace,newlinesandmixed_line_ending(#1621, @jdx).trailing-whitespacestrips only spaces and tabs, so it no longer strips form feeds or other Unicode whitespace.end-of-file-fixerappends the file's most common line ending.mixed-line-ending --fixconverts to the file's most common line ending instead of always LF. On a tie it uses LF.
-
User config lists are merged with the project's (#1596, @jdx).
skip_steps,skip_hooksandhide_warningsfrom the user config are now combined with the project's lists, as the docs describe. Before, the project's list replaced the user's.- hk now warns when
hk.local.pkldoesn't amendhk.pkl, because the local file then replaces the shared config. Hide the warning withHK_HIDE_WARNINGS=local-config-replaces-shared.
- hk now warns when
-
Subproject
skip_stepsnow apply to that subproject's own steps. hk now warns about other top-level subproject settings that have no effect. (#1615, @jdx) -
Clearer error messages.
- Failures print once, without Rust
Location:lines. hk no longer suggests a fix command when the tool isn't installed. (#1588) - Argv steps say "command not found" when the tool is missing (#1579).
- Pkl syntax errors show the file, line and column, including in imported files (#1598).
Hook '…' not foundexplains why: there is nohk.pkl, it's empty, or the name is wrong (it lists the defined hooks).hk initprints next steps. (#1564)
(@jdx)
- Failures print once, without Rust
-
Interrupted steps report
cancelled. In structured output and MCP, a step or run stopped by Ctrl-C now reportscancelledinstead offailed(#1643, @jdx). -
Capped structured output. Each step's output in JSON, JSONL, MCP and JUnit results is limited to 64 KiB. JSON output now includes a
failedrun_resultwhen hk fails before any step runs, for example on a broken config. (#1605, @jdx)
Fixed
Stashing
- If hk is stopped by SIGINT, SIGTERM or SIGHUP (or a Windows console close), it restores stashed changes before exiting. After a crash or
kill -9, the next hk run restores them if the working tree is clean. Otherwise it prints thegit stash applycommand to run. (#1641, @jdx) - hk processes in the same repository, including linked worktrees, now take turns stashing and restoring (#1636). hk restores its own stash entry by commit id, so other stash entries are never touched (#1637). (@jdx)
- Stashing now handles these cases (@jdx):
- A merge or cherry-pick in progress is kept (#1609).
- A staged edit is kept when its worktree copy was reverted to HEAD (#1602).
- Tracked files whose names start with
:are stashed (#1614). - Unstaged text over a staged binary file is restored (#1580).
- CRLF line endings are kept when unstaged files are restored (#1535).
Steps and file selection
- Steps that depend on a step with no files now run instead of hanging (#1584).
- Diagnostics from every job that started are kept when another job fails (#1642).
- An
excludethat names a directory now excludes the files in it (#1606). - Files are selected in directories whose names contain brackets or braces (#1592).
- Tracked files whose names aren't valid UTF-8 are skipped with a warning in
--all,--glob,--from-refand file arguments (#1613). - Diagnostic paths are relative to the repository root for steps that run in a subdirectory (#1638).
- Scripts whose
envshebang uses-S, options or variables now get the right file type (#1611). interactive = truesteps run in hk's process group, so TUI tools such as fzf, helix and gimoji display correctly (#1557).- Formatter patches are applied and rolled back without interference from other commands or staging (#1392, @nettlesh).
Configuration and CLI
- Top-level
jobsinhk.pklor the user config is now applied (#1594). HK_STASHacceptstrue/1andfalse/0, and an invalid value gives an error instead of a panic (#1594).hk configno longer crashes when a setting is unset (#1594).- Dependency cycles, steps that depend on themselves, and invalid globs or regexes are rejected when the config loads, with the hook and step named. Before,
hk checkcould hang. (#1567) hk check --stepwith an unknown name now fails instead of passing after running nothing (#1603).- Configs that amend the v1 package schema load again (#1560).
- Mirror credentials are hidden in errors from failed package downloads (#1620).
--help,--versionand shell completion keep their normal exit code when the reader closes the pipe early (#1540).
Installed hooks
hk install --globalrecords a path that still works after upgrades: the mise shim or thehkonPATH, not a versioned install path. Local hooks add hk's directory to the end ofPATH, so they work in GUI git clients with a minimalPATH. (#1597)- The message for
hk install --force-localwhen a global install exists is now correct (#1616).
Builtins and hk util
check_added_large_filesandcheck_case_conflictnow check binary files (#1571).no-commit-to-branchworks when a tag has the same name as the branch (#1577).- The executable and shebang checks use the file mode recorded in git, so they also work on Windows and with
core.fileMode=false(#1577). check-case-conflictfinds conflicts between directory names, such asFoo/andfoo/(#1577).- Text checks no longer skip files when the 8 KiB sample ends in the middle of a multibyte character. Key checks now also handle files that aren't valid UTF-8. (#1572)
fix-smart-quotesleaves files that aren't valid UTF-8, and symlinks, unchanged (#1570).check-conventional-commitaccepts merge and revert titles generated by git (#1566).
MCP and agents
cancel_runstops the hk run instead of staying incancelling(#1607).get_diffincludes untracked and binary files and reports capture errors (#1601).
Windows
- Percent signs in file names stay literal under
cmd.exe(#1568). - Batches for
cmdshims stay under thecmd.execommand-line length limit (#1618). - A custom shell runs directly, and quoted shell paths work (#1628).
- Tools still running when a fail-fast cancel stops a run are now ended (#1625).
Security
hk check --safeandhk fix --safenow refuse a hook-levelreportwhose effect is unknown or destructive.reportaccepts the same command forms ascheck, so you can declare its effect. (#1573)- Patches from
check_diffthat write inside.git, in any spelling git treats as.git, are refused, and hk runs the fixer instead. On Unix, hk creates new state directories with mode0700. (#1585) - Legacy
hk install(Git older than 2.54, or--legacy) no longer overwrites hooks that hk didn't write and no longer writes through symlinks; see Breaking Changes (#1593).
Breaking Changes
- Legacy installs refuse to replace foreign hooks. If
.git/hooks/contains a hook that hk didn't write, or a symlink,hk installchanges nothing and lists the files. Pass--forceto replace them; a symlink's target is left untouched. Setups where another tool owns hooks, such asgit lfs install, needhk install --force. Config-based installs on Git 2.54+ are not affected. (#1593) - Line-ending fixers keep CRLF. Repos that relied on
trailing_whitespaceormixed_line_endingconverting CRLF files to LF need another way to normalize line endings. (#1621) - Signal exit codes. When stopped by a signal, hk exits with
128 + signal: 130 for Ctrl-C (previously 1), 143 for SIGTERM and 129 for SIGHUP. Pressing Ctrl-C twice exits at once. (#1641) - Stricter config checks.
--safeand plain-string reports. A plain-string hookreportblocks--saferuns. Declare it as aCommandSpecwitheffect = "read"to keep using it under--safe. (#1573)
Full Changelog: v2.4.0...v2.5.0
💚 Sponsor hk
hk is built and maintained by @jdx, an open source developer at entire.io, the title sponsor of his open source work.
If hk speeds up your pre-commit loop or makes linting less painful, please consider becoming an individual or company sponsor. Your support funds ongoing development and helps keep hk fast, free, and independent.