Skip to content

v1.51.0: Structured argv and disjunctive selectors

Latest

Choose a tag to compare

@github-actions github-actions released this 14 Jul 10:48
Immutable release. Only release title and notes can be modified.
8e26af2

Two configuration expansions land in this release: shell-free argv commands with per-file argument expansion, and match_any selectors that combine globs and types with OR semantics. Alongside them, correctness fixes for --quiet/--silent, config-based hook argument forwarding, transient index locks, and Linux argument-size limits.

Added

  • Structured argv commands (@jdx) #1067. A step's check/fix/check_list_files/check_diff can now be a Command with an explicit argv list that runs a binary via PATH without a shell. Standalone {{files}} and {{workspace_files}} entries expand to one argument per file (raw paths), so names with spaces or shell metacharacters are passed literally. Auto-batching, hk test, progress output, and fix suggestions all go through shared Command rendering, and many builtins (prettier, ruff, biome, ...) have moved to structured commands. Existing string and platform Script commands are unchanged; structured commands can't be combined with shell or prefix.

    check = new Command {
      argv = List("wc", "-c", "{{files}}")
    }
  • match_any disjunctive file selectors (@risu729) #1055. Clauses compose with OR semantics; glob and types within a clause compose with AND. match_any cannot be mixed with the top-level glob or types, and empty selectors are rejected at validation time. {{globs}} and progress text reflect the combined patterns. The shellcheck and shfmt builtins have adopted it so they also pick up extensionless shell scripts detected by shebang, without giving up their extension globs.

    ["shellcheck"] {
      match_any = List(
        new { glob = List("**/*.sh", "**/*.bash") },
        new { types = List("sh", "bash") }
      )
      check = "shellcheck {{files}}"
    }
  • sherif builtin (@smasato) #1062. Adds sherif, the opinionated zero-config linter for TypeScript/JavaScript monorepos. check = "sherif" / fix = "sherif --fix --no-install"; sherif always scans from the repo root, so the commands take no file arguments. Globs cover **/package.json and **/pnpm-workspace.yaml; auto-suggested when "sherif" appears in package.json.

    ["sherif"] = Builtins.sherif

Fixed

  • --quiet and --silent really suppress output now (@smasato) #1058. Previously these flags only lowered the log level and switched progress to text mode, which streams every update on a new line rather than hiding anything. They now set ProgressOutput::Quiet so progress lines and successful-step summaries are gone; --quiet still prints failed-step summaries (essential diagnostic), --silent prints nothing but exit code. --stats is suppressed under both. Informational println!s in init/install/migrate were routed through info! so the flags apply there too. CI/non-interactive text-mode behavior is unchanged.

  • Config-based hooks no longer duplicate arguments (@jdx) #1065. Git 2.54+ already appends hook arguments to hook.<name>.command, and hk was also expanding "$@", so argument-bearing hooks received every argument twice. For pre-push this meant the duplicated remote/URL were parsed as an explicit file list, which overrode push-range discovery and silently skipped file-filtered steps. hook_run_args no longer appends "$@"; legacy .git/hooks/ shims still use it via git_hook_content. Fixes discussion #1063.

  • Stash waits out transient index locks (@jdx) #1060. Shell-based stash paths now resolve the worktree lock via git rev-parse --git-path index.lock and sleep with bounded backoff (up to ~775ms) before running git stash push, so a briefly held lock from another Git process no longer aborts the hook. A persistent lock still surfaces as a normal Git error after the wait window; libgit2 stash_save is unchanged. Fixes discussion #1056.

  • Linux per-argument size limit respected in auto-batching (@jdx) #1066. hk passes each rendered command to the shell as one sh -c argument, and Linux limits each argument to 32 pages (~128 KiB) independently of the aggregate ARG_MAX. Batching now caps rendered commands using both the existing ARG_MAX / 2 margin and MAX_ARG_STRLEN, sizes each chunk independently so later, longer paths cannot overflow a batch, and returns a clear error when even a single-file command cannot fit. Fixes discussion #1061.

Documentation

  • Vertically center social link icons on the docs site (@smasato) #1059.

Full Changelog: v1.50.0...v1.51.0

💚 Sponsor hk

hk is maintained by @jdx, an open source developer for entire.io, the title sponsor of the jdx.dev open source tools including mise, aube, and more. Work on hk is funded by sponsorships.

If hk has sped up your pre-commit loop or made linting feel less painful, please consider sponsoring at jdx.dev. Sponsorships are what keep hk moving and the project independent.