Skip to content

feat: Add ability to download specific tool versions used by hooks. TLDR: add --hook-config=--tool-version=1.2.3 for pinning wrapped tool version. Works for all tools (even terraform and tofu) except checkov - #1002

Merged
MaxymVlasov merged 29 commits into
masterfrom
downloadable_and_version_controlled_hooks
Aug 21, 2026
Merged

feat: Add ability to download specific tool versions used by hooks. TLDR: add --hook-config=--tool-version=1.2.3 for pinning wrapped tool version. Works for all tools (even terraform and tofu) except checkov#1002
MaxymVlasov merged 29 commits into
masterfrom
downloadable_and_version_controlled_hooks

Conversation

@MaxymVlasov

@MaxymVlasov MaxymVlasov commented Jul 23, 2026

Copy link
Copy Markdown
Collaborator
  • This PR introduces breaking change.
  • This PR fixes a bug.
  • This PR adds new functionality.
  • This PR enhances existing functionality.

Description of your changes

What

This section was generated by AI.

  • Adds --hook-config=--tool-version=X.Y.Z version pinning to every hook that wraps a downloadable release binary (terraform_tflint, terraform_docs, terraform_tfsec/terraform_trivy, terrascan, infracost_breakdown, tfupdate, terragrunt_*, and Terraform/OpenTofu via terraform_validate/terraform_fmt/terraform_providers_lock), resolved and cached on demand by invoking the existing tools/install/<tool>.sh installer scripts - opt-in, zero effect on hooks that don't set it.
  • Adds --hook-config=--tool-version-mode=strict|prefer-local to control whether a pinned version always wins (strict, default) or an already-on-$PATH binary is preferred when present (prefer-local).
  • On terraform_validate/terraform_fmt/terraform_providers_lock, --tf-path can now also be set to the literal value terraform, opentofu, or tofu (instead of an actual binary path) when combined with --tool-version, to explicitly pick which of the two to pin rather than relying on $PATH-based auto-detection.
  • Adds an actionable error (naming the missing tool and the hook, suggesting --hook-config=--tool-version=) when a hook's tool is neither pinned nor found on $PATH, instead of deferring to a generic "command not found" failure later.
  • checkov/terraform_checkov is explicitly excluded (pip-distributed, not a GitHub-release binary - forcing it through this path would fight its actual, already-working distribution model).
  • New README.md section documenting usage, a cache-directory-mounting note for persisting across Docker container runs, and a Renovate customManagers recipe for keeping pins auto-bumped.
  • Updates .github/CONTRIBUTING.md's "Add new hook" checklist to mention the new runtime call site alongside the existing Docker build-time one.
  • One small, backward-compatible change to tools/install/_common.sh: source /.env is now conditional on the file existing, so the installer scripts also run correctly when invoked directly at hook run-time (not just inside a Docker build).
  • Fixes a bug where a failed tool resolution (common::resolve_tool_path) was silently swallowed by local -r tool_path=$(...), letting terraform_tflint, terraform_docs, infracost_breakdown, and the shared per_dir_hook continue with an empty tool path instead of aborting with the resolver's own error message.
  • Corrects stale tool_path doc comments left over from the tf_path->tool_path rename in 9 hooks, and fixes invalid YAML indentation in 3 --tool-version README examples.
  • Fixes terragrunt_validate/terragrunt_validate_inputs/terragrunt_fmt/terragrunt_providers_lock picking their pre/post-0.78 CLI syntax off whatever terragrunt is on $PATH instead of the resolved/pinned tool_path binary that actually runs - a pinned version on the other side of the 0.78 boundary from $PATH picked the wrong syntax and broke.
  • Fixes 5 bugs flagged by Copilot's automated review (#4992979471): prefer-local mode probed opentofu on $PATH instead of the real tofu binary so the pin always won regardless of mode; the published Docker image had no curl, breaking cache-miss downloads; terrascan's installer used bash4-only ${TARGETOS^}, breaking under macOS's bash 3.2; the cache key omitted OS/arch, so a cache dir shared across platforms (e.g. a Docker volume mount) could serve an incompatible binary as a false hit; and specific-version resolution only searched GitHub's unpaginated, 30-release page, so pinning anything older silently failed.
  • Wires hcledit through the same resolver for terraform_wrapper_module_for_each - the last release-binary hook --tool-version didn't yet cover.
  • Fixes the Docker cache-mount README example, which mounted under /root while also dropping to a non-root USERID (unreachable, permission denied).
  • Silences a hadolint DL3064 false positive on the pre-existing ARG GITHUB_TOKEN/ENV INFRACOST_API_KEY (both empty-default placeholders, real values supplied externally, never hardcoded).
  • Fixes CI Docker buildx builds failing with e.g. tflint.sh ... exit code: 3: build-image.yaml/build-image-test.yaml passed GITHUB_TOKEN via buildx secrets:, which nothing in the Dockerfile mounts - dead config left over from before --build-arg-based GitHub API auth was added, so every image build resolved tool versions via unauthenticated (60/hr, shared across all Actions runners) GitHub API calls. Now passed as a GITHUB_TOKEN build-arg, matching what tools/install/_common.sh already expects.

Why

This enables multi-version testing and the ability to pin the same versions of underlying tools by users, which is a decent improvement towards reproducibility.

Fix: #188, #270, #1009
Related: #722

How can we test changes

This section was generated by AI.

- id: terraform_tflint
  args:
    - --hook-config=--tool-version=0.50.0

Run pre-commit run terraform_tflint - first run downloads and caches tflint 0.50.0 into $XDG_CACHE_HOME/pre-commit-terraform (or $HOME/.cache/pre-commit-terraform), subsequent runs hit the cache with no network call. Same pattern works for terraform_validate/terraform_fmt/terraform_providers_lock (pins Terraform/OpenTofu), optionally combined with --hook-config=--tf-path=opentofu to force OpenTofu specifically regardless of what's on $PATH.

Assisted-by

Specific models used per commit are specified in the commit messages.

Adds openspec/config.yaml plus the propose/explore/apply/update/
sync/archive skills and opsx slash commands, then registers the
new skills in the AGENTS.md skill routing table so agents load
them automatically.

Assisted-by: Sisyphus:claude-sonnet-5 claude
@coderabbitai

coderabbitai Bot commented Jul 23, 2026

Copy link
Copy Markdown

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Hooks now support version-pinned tool resolution, cached installers, and resolved executable paths across Terraform, Terragrunt, and auxiliary tools. Documentation covers version pinning, Renovate configuration, cache persistence, and runtime installer usage.

Changes

Version-aware hook execution

Layer / File(s) Summary
Shared resolver and cache foundation
hooks/_common.sh, tools/install/_common.sh
Adds shared configuration parsing, OS/architecture detection, version-aware resolution, caching, Terraform/OpenTofu selection, and resolved Terragrunt version checks.
Generic hook wiring
hooks/infracost_breakdown.sh, hooks/terraform_docs.sh, hooks/terraform_tflint.sh, hooks/terraform_checkov.sh, hooks/terraform_tfsec.sh, hooks/terraform_trivy.sh, hooks/terrascan.sh, hooks/tfupdate.sh
Passes tool names and resolved paths through wrappers and executes the selected binaries.
Terraform hook path propagation
hooks/terraform_fmt.sh, hooks/terraform_providers_lock.sh, hooks/terraform_validate.sh
Routes formatting, provider locking, validation, initialization, and retry commands through resolved Terraform/OpenTofu paths.
Terragrunt hook path propagation
hooks/terragrunt_*.sh
Routes Terragrunt per-directory and whole-repository commands through resolved binaries and version-dependent subcommands.
Documentation and contributor workflow
README.md, .github/CONTRIBUTING.md
Documents pinned versions, Renovate configuration, cache mounting, and runtime installer usage.

Estimated code review effort: 4 (Complex) | ~45 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Hook as Hook
  participant Common as common::per_dir_hook
  participant Resolver as common::resolve_tool_path
  participant Tool as Resolved tool
  Hook->>Common: pass tool_name and HOOK_CONFIG
  Common->>Resolver: resolve version and executable
  Resolver-->>Common: return tool_path
  Common->>Tool: execute per-directory or whole-repository command
Loading

Suggested reviewers: yermulnik

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 68.42% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Title check ✅ Passed The title clearly summarizes the main feature: pinning wrapped tool versions through --hook-config, while accurately noting the checkov exception.
Description check ✅ Passed The description directly explains the version-pinning feature, resolver behavior, documentation updates, fixes, and testing guidance.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch downloadable_and_version_controlled_hooks

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@MaxymVlasov
MaxymVlasov force-pushed the downloadable_and_version_controlled_hooks branch from b55dcfc to e24e21b Compare July 24, 2026 15:24
@MaxymVlasov
MaxymVlasov marked this pull request as ready for review July 24, 2026 15:59

@yermulnik yermulnik left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I didn't read any of the Spec DMs — that (along with coding part of the PR) is too much of text for a single PR to be read be a human. Please split into separate PRs to ease review process and to separate entities. Thanks.

Comment thread hooks/_common.sh Outdated
Comment thread hooks/_common.sh Outdated
Comment thread hooks/_common.sh Outdated
Comment thread hooks/_common.sh Outdated
Comment thread hooks/_common.sh
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
Comment thread README.md Outdated
MaxymVlasov and others added 3 commits July 28, 2026 18:29
Co-authored-by: George Yermulnik (Georgii Iermulnik) <yz@yz.kiev.ua>
Co-authored-by: George Yermulnik (Georgii Iermulnik) <yz@yz.kiev.ua>
Comment thread hooks/terraform_checkov.sh Outdated
Co-authored-by: Maksym Vlasov <MaxymVlasov@users.noreply.github.com>
@MaxymVlasov
MaxymVlasov changed the base branch from openspec to master July 28, 2026 17:52
.agents/commands/opsx/, .agents/skills/openspec-*/, and openspec/ are
personal local tooling, not meant to be shared via this repo's history.
They stay on disk, untracked, ignored via a per-repo excludesFile
configured outside this repo (~/.gitconfig includeIf).

Assisted-by: Sisyphus:claude-sonnet-5 opencode
Copilot AI review requested due to automatic review settings July 28, 2026 17:59

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds opt-in, per-hook tool version pinning via --hook-config=--tool-version=... for hooks that wrap downloadable binaries, resolving/caching pinned versions on demand by reusing the existing tools/install/<tool>.sh installers. It also updates docs to explain usage (including a Docker cache mount pattern) and adjusts hook plumbing so wrapped tools are invoked via a resolved tool_path.

Changes:

  • Extend common::per_dir_hook to resolve a wrapped tool binary path (optionally pinned + cached) and pass it into hook implementations.
  • Add common::resolve_tool_path + supporting helpers (--tool-version, --tool-version-mode, OS/arch detection) and wire affected hooks to use the resolved tool path.
  • Document the new pinning feature and add contributor guidance about the new runtime installer call site.

Reviewed changes

Copilot reviewed 21 out of 21 changed files in this pull request and generated 10 comments.

Show a summary per file
File Description
tools/install/_common.sh Make /.env sourcing conditional to allow installer scripts to run outside Docker builds.
README.md Document --tool-version / --tool-version-mode, Renovate custom manager, and Docker cache mounting.
hooks/_common.sh Add tool resolution + caching utilities and update common::per_dir_hook signature/behavior to pass a resolved tool_path.
hooks/terraform_fmt.sh Route hook execution through resolved Terraform/OpenTofu binary path.
hooks/terraform_validate.sh Route validate/init flow through resolved Terraform/OpenTofu binary path.
hooks/terraform_providers_lock.sh Route providers lock through resolved Terraform/OpenTofu binary path.
hooks/terraform_tflint.sh Resolve tflint path (incl. for --init) and pass into per-dir execution.
hooks/terraform_docs.sh Resolve terraform-docs path (optionally pinned) before building the command.
hooks/terraform_tfsec.sh Resolve tfsec path and pass into per-dir/whole-repo execution.
hooks/terraform_trivy.sh Resolve trivy path and pass into per-dir/whole-repo execution.
hooks/terrascan.sh Resolve terrascan path and pass into per-dir/whole-repo execution.
hooks/tfupdate.sh Resolve tfupdate path and pass into per-dir/whole-repo execution.
hooks/terragrunt_fmt.sh Resolve terragrunt path and pass into per-dir/whole-repo execution.
hooks/terragrunt_validate.sh Resolve terragrunt path and pass into per-dir/whole-repo execution.
hooks/terragrunt_validate_inputs.sh Resolve terragrunt path and pass into per-dir/whole-repo execution.
hooks/terragrunt_providers_lock.sh Resolve terragrunt path and pass into per-dir/whole-repo execution.
hooks/terraform_checkov.sh Adapt to new per_dir_hook signature while keeping checkov unpinned/no resolved binary.
hooks/infracost_breakdown.sh Resolve infracost path (optionally pinned) before running breakdown.
AGENTS.md Extend skill routing table with OpenSpec-related skills.
.github/CONTRIBUTING.md Note that tools/install/<tool>.sh is now used at runtime for --tool-version pinning.
.github/AI_POLICY.md Add a “Recommended tooling” section referencing OpenSpec workflow.
Comments suppressed due to low confidence (2)

README.md:460

  • This YAML example is not valid as written because args must be indented under the hook item. Update the indentation so users can copy/paste it.
    - id: terraform_validate
    args:
        - --hook-config=--tf-path=opentofu
        - --hook-config=--tool-version=1.12.0
    ```

README.md:481

  • This YAML example is not valid as written because args must be indented under the hook item. The current indentation will fail YAML parsing if copied.
    - id: terraform_tflint
    args:
        - --hook-config=--tool-version=0.50.0
        - --hook-config=--tool-version-mode=prefer-local
    ```

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread hooks/tfupdate.sh Outdated
Comment thread hooks/terrascan.sh Outdated
Comment thread hooks/terragrunt_validate.sh Outdated
Comment thread hooks/terragrunt_validate_inputs.sh Outdated
Comment thread hooks/terragrunt_providers_lock.sh Outdated
Comment thread hooks/terragrunt_fmt.sh Outdated
Comment thread hooks/terraform_trivy.sh Outdated
Comment thread hooks/terraform_tfsec.sh Outdated
Comment thread hooks/terraform_tflint.sh Outdated
Comment thread README.md

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

🧹 Nitpick comments (2)
hooks/_common.sh (1)

615-628: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Unrecognized --tool-version-mode values fall through to strict silently.

A typo (prefer_local, preferlocal) is indistinguishable from strict, so users get a download instead of the local binary with no hint why. A small validation on the accepted set would surface config mistakes.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hooks/_common.sh` around lines 615 - 628, Validate tool_version_mode in the
configuration handling around common::get_hook_config_value and accept only the
supported modes, including prefer-local and strict. For any unrecognized value,
emit a clear configuration warning or error instead of silently proceeding
through the strict download behavior; preserve the existing behavior for valid
values.
hooks/terragrunt_fmt.sh (1)

40-40: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Terragrunt hooks document tool_path as a Terraform/OpenTofu binary. The tf_pathtool_path rename carried the Terraform-specific wording into all four Terragrunt wrappers, which actually receive a resolved terragrunt binary; each file's run_hook_on_whole_repo block already uses the correct generic wording.

  • hooks/terragrunt_fmt.sh#L40-L40: change the description to "resolved path to the wrapped tool's binary".
  • hooks/terragrunt_providers_lock.sh#L42-L42: apply the same wording change.
  • hooks/terragrunt_validate.sh#L42-L42: apply the same wording change.
  • hooks/terragrunt_validate_inputs.sh#L42-L42: apply the same wording change.
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hooks/terragrunt_fmt.sh` at line 40, Update the tool_path description in the
run_hook_on_whole_repo blocks to say “resolved path to the wrapped tool's
binary” instead of Terraform/OpenTofu-specific wording. Apply this change in
hooks/terragrunt_fmt.sh (line 40), hooks/terragrunt_providers_lock.sh (line 42),
hooks/terragrunt_validate.sh (line 42), and hooks/terragrunt_validate_inputs.sh
(line 42).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@hooks/_common.sh`:
- Around line 333-340: Check the exit status of common::resolve_tool_path
immediately after the tool_path assignment in the hook flow, and return or exit
non-zero before invoking run_hook_on_whole_repo when resolution fails. Apply the
same propagation to the early tool-path resolution in terraform_tflint.sh, and
ensure resolve_tool_path consistently returns a non-zero status for every
failure path.

In `@hooks/terraform_docs.sh`:
- Around line 24-26: Update the argument-rewriting loop in terraform_docs.sh so
the --config value is prefixed with the repository path only when it is
relative. Preserve values beginning with / unchanged, while retaining the
existing conversion for relative --config paths.

In `@README.md`:
- Line 443: Update the version-pinning description near the wrapped-tool
download behavior to replace the GitHub-specific wording with “the tool’s
upstream release assets” or equivalent, while preserving the existing caching
and first-use download behavior.
- Around line 447-460: Correct the indentation of args in both YAML examples,
terraform_tflint and terraform_validate, so args is nested beneath its
corresponding - id entry and the examples form valid pre-commit configuration.

---

Nitpick comments:
In `@hooks/_common.sh`:
- Around line 615-628: Validate tool_version_mode in the configuration handling
around common::get_hook_config_value and accept only the supported modes,
including prefer-local and strict. For any unrecognized value, emit a clear
configuration warning or error instead of silently proceeding through the strict
download behavior; preserve the existing behavior for valid values.

In `@hooks/terragrunt_fmt.sh`:
- Line 40: Update the tool_path description in the run_hook_on_whole_repo blocks
to say “resolved path to the wrapped tool's binary” instead of
Terraform/OpenTofu-specific wording. Apply this change in
hooks/terragrunt_fmt.sh (line 40), hooks/terragrunt_providers_lock.sh (line 42),
hooks/terragrunt_validate.sh (line 42), and hooks/terragrunt_validate_inputs.sh
(line 42).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: bc8e303e-4ccc-4c41-bad1-d1e6a6a03c0a

📥 Commits

Reviewing files that changed from the base of the PR and between bf9dd9c and a5e62cc.

📒 Files selected for processing (21)
  • .github/AI_POLICY.md
  • .github/CONTRIBUTING.md
  • AGENTS.md
  • README.md
  • hooks/_common.sh
  • hooks/infracost_breakdown.sh
  • hooks/terraform_checkov.sh
  • hooks/terraform_docs.sh
  • hooks/terraform_fmt.sh
  • hooks/terraform_providers_lock.sh
  • hooks/terraform_tflint.sh
  • hooks/terraform_tfsec.sh
  • hooks/terraform_trivy.sh
  • hooks/terraform_validate.sh
  • hooks/terragrunt_fmt.sh
  • hooks/terragrunt_providers_lock.sh
  • hooks/terragrunt_validate.sh
  • hooks/terragrunt_validate_inputs.sh
  • hooks/terrascan.sh
  • hooks/tfupdate.sh
  • tools/install/_common.sh

Comment thread hooks/_common.sh Outdated
Comment thread hooks/terraform_docs.sh
Comment thread README.md

> All hooks, which wrap a tool distributed as a downloadable release asset. Not supported for `checkov`/`terraform_checkov` (distributed via PyPi) and for deprecated `terraform_docs_replace` hook.

1. You can pin a specific version of the wrapped tool per hook, independent of whatever is on your `$PATH` or baked into the Docker image. If that version isn't already cached locally, it's downloaded from the tool's GitHub releases on first use, then reused (without re-downloading) on every subsequent run.

@coderabbitai coderabbitai Bot Jul 28, 2026

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Avoid claiming every binary is downloaded from GitHub releases.

Terraform/OpenTofu resolution uses HashiCorp release installers, so this should say “the tool’s upstream release assets” (or similar) rather than specifically “GitHub releases.”

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@README.md` at line 443, Update the version-pinning description near the
wrapped-tool download behavior to replace the GitHub-specific wording with “the
tool’s upstream release assets” or equivalent, while preserving the existing
caching and first-use download behavior.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't see an issue here. "Github release" is more undersandable, IMO.
@yermulnik ?

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"GitHub releases" describes the feature, but e.g. "GitHub releases page" (or with capitalized "Releases" to mark the section on the web-site) describes the actual location where we download assets from.
So I'd add "page" after "releases" to provide clarity. However, tech-savvies do indeed often say "Github releases" meaning "Github Releases page" =)
Up to you 👍

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Seems like the humans are having a chat. I'll hop back into my burrow for now. If you need me again, just tag @coderabbitai in a new comment, and I'll come hopping out!

Comment thread README.md
Comment thread .github/AI_POLICY.md
`local -r tool_path=$(common::resolve_tool_path ...)` discarded the
resolver's exit code, so a failed lookup (missing binary, bad
--tool-version) left tool_path empty instead of aborting the hook.
Split into declare/plain-assign-with-guard/readonly so the failure
actually propagates. Applies to per_dir_hook, terraform_tflint,
terraform_docs and infracost_breakdown.

Also corrects stale tool_path doc comments (9 hooks, still described
as "Terraform/OpenTofu binary" after the tf_path->tool_path refactor)
and invalid YAML indentation in 3 README --tool-version examples.

Assisted-by: Sisyphus:claude-sonnet-5 opencode

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
hooks/terragrunt_validate_inputs.sh (1)

25-28: 🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Use the configured Terragrunt binary for version-gated subcommands.

common::terragrunt_version_ge_0.78 checks the terragrunt binary in PATH, but terragrunt_validate_inputs.sh selects SUBCOMMAND/RUN_ALL_SUBCOMMAND before common::per_dir_hook resolves --tool-version into tool_path. A pinned Terragrunt target crossing the 0.78 boundary can use the wrong CLI syntax; gate the subcommand selection against the configured/resolved tool version, or perform it where tool_path is available.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@hooks/terragrunt_validate_inputs.sh` around lines 25 - 28, Update
terragrunt_validate_inputs.sh so SUBCOMMAND and RUN_ALL_SUBCOMMAND selection
uses the configured or resolved Terragrunt binary represented by tool_path,
rather than the PATH terragrunt checked by common::terragrunt_version_ge_0.78.
Move the version-gated selection into the flow where tool_path is available, or
pass that binary explicitly, preserving the correct CLI syntax across the 0.78
boundary.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@hooks/terragrunt_validate_inputs.sh`:
- Around line 25-28: Update terragrunt_validate_inputs.sh so SUBCOMMAND and
RUN_ALL_SUBCOMMAND selection uses the configured or resolved Terragrunt binary
represented by tool_path, rather than the PATH terragrunt checked by
common::terragrunt_version_ge_0.78. Move the version-gated selection into the
flow where tool_path is available, or pass that binary explicitly, preserving
the correct CLI syntax across the 0.78 boundary.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 12a46dd1-a722-4db4-ae27-3d3c5d421e45

📥 Commits

Reviewing files that changed from the base of the PR and between 4438ee8 and b85f444.

📒 Files selected for processing (13)
  • README.md
  • hooks/_common.sh
  • hooks/infracost_breakdown.sh
  • hooks/terraform_docs.sh
  • hooks/terraform_tflint.sh
  • hooks/terraform_tfsec.sh
  • hooks/terraform_trivy.sh
  • hooks/terragrunt_fmt.sh
  • hooks/terragrunt_providers_lock.sh
  • hooks/terragrunt_validate.sh
  • hooks/terragrunt_validate_inputs.sh
  • hooks/terrascan.sh
  • hooks/tfupdate.sh
🚧 Files skipped from review as they are similar to previous changes (12)
  • hooks/infracost_breakdown.sh
  • hooks/terraform_trivy.sh
  • hooks/terraform_tflint.sh
  • hooks/tfupdate.sh
  • README.md
  • hooks/terragrunt_validate.sh
  • hooks/terraform_tfsec.sh
  • hooks/terragrunt_fmt.sh
  • hooks/terrascan.sh
  • hooks/terraform_docs.sh
  • hooks/terragrunt_providers_lock.sh
  • hooks/_common.sh

yermulnik
yermulnik previously approved these changes Aug 19, 2026
Fix race condition during concurrent (pre-commit usually proceeds with 4 bunches simultaneously), which end in over9999 errors like this:

```bash
NOTE: The requested 'terraform' version '0.12.0' will be downloaded/used instead of whatever is on $PATH.
Downloading 'terraform' version '0.12.0'...
  % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                 Dload  Upload   Total   Spent    Left  Speed
100 14.2M  100 14.2M    0     0  1333k      0  0:00:10  0:00:10 --:--:-- 1721k
unzip:  cannot find or open terraform.zip, terraform.zip.zip or terraform.zip.ZIP.
ERROR: Failed to download 'terraform' version '0.12.0' via '/home/vm/.cache/pre-commit/repo29gptgn5/hooks/../tools/install/terraform.sh'.
```
@MaxymVlasov
MaxymVlasov marked this pull request as ready for review August 20, 2026 19:30
@MaxymVlasov
MaxymVlasov requested review from yermulnik and a lite review from Copilot August 21, 2026 10:30

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated no new comments.

Suppressed comments (2)

README.md:442

  • The introductory blockquote is grammatically awkward and slightly contradicts the section title (“Most hooks” vs “All hooks”). Rewording it to “Most hooks that wrap…” reads clearer and matches the heading.
> All hooks, which wrap a tool distributed as a downloadable release asset. Not supported for `checkov`/`terraform_checkov` (distributed via PyPi) and for deprecated `terraform_docs_replace` hook.

README.md:485

  • “as opposite to” is incorrect phrasing; it should be “as opposed to”.
4. By default, if a different version of the tool is already on `$PATH`, the pinned version still wins (with a warning message logged) based on `--hook-config=--tool-version-mode=strict`. Set it to `prefer-local` (as opposite to `strict`) to invert that: if the tool already resolves via `$PATH`, this local binary is used as-is and no download is attempted; the pinned version is only downloaded/used as a fallback when nothing is found locally.

yermulnik
yermulnik previously approved these changes Aug 21, 2026
Comment thread tools/install/_common.sh Outdated
Co-authored-by: George Yermulnik (Georgii Iermulnik) <yz@yz.kiev.ua>

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 24 out of 24 changed files in this pull request and generated 5 comments.

Suppressed comments (3)

hooks/_common.sh:596

  • Specific-version resolution only searches the first, unpaginated /releases response in tools/install/_common.sh:77, so pins older than GitHub's default 30-release page produce an empty URL and fail even though the release exists. For example, TFLint v0.44.1 is currently release 31. Query the release/tag directly or paginate until the requested version is found; otherwise this feature cannot provide general historical pinning.
  if ! (
    cd "$tmp_dir" || exit 1
    export "$env_var_name=$version"
    "$installer_script" 1>&2

README.md:442

  • This claim omits terraform_wrapper_module_for_each, which wraps the downloadable hcledit release and has a matching tools/install/hcledit.sh, but still calls bare hcledit and errors when it is absent. Thus --tool-version is not supported by all release-binary hooks as documented and as stated in the PR; wire that hook through the resolver or explicitly document it as another exclusion.
> All hooks, which wrap a tool distributed as a downloadable release asset. Not supported for `checkov`/`terraform_checkov` (distributed via PyPi) and for deprecated `terraform_docs_replace` hook.

README.md:1394

  • This example combines a non-root USERID with a cache mounted below /root. The entrypoint switches UID via su-exec without changing HOME, and the resulting process cannot traverse/write /root, so pinned-tool resolution fails instead of using this cache. Mount a user-accessible path, set PCT_TOOL_CACHE_DIR, and create the host directory with the invoking user's ownership first.
    -e "USERID=$(id -u):$(id -g)" \
    -v ~/.cache/pre-commit-terraform:/root/.cache/pre-commit-terraform \
    -v $(pwd):/lint -w /lint \

Comment thread hooks/_common.sh Outdated
Comment thread .github/workflows/reusable-tox.yml
Comment thread hooks/_common.sh
Comment thread hooks/terrascan.sh
Comment thread hooks/_common.sh
Copilot flagged several bugs in the --tool-version implementation
(review #4992979471):

- prefer-local mode probed "opentofu" on $PATH; the real binary is
  "tofu", so the pin always won even when local should be preferred
- the final Docker image had no curl, so cache-miss downloads failed
  in the published image
- terrascan's install script used bash4-only ${TARGETOS^}, breaking
  under macOS's default bash 3.2
- the cache key omitted OS/arch, so a cache dir shared across
  platforms (e.g. a Docker volume mount) could serve an incompatible
  binary as a false hit
- specific-version resolution only searched GitHub's unpaginated,
  30-release page, so pinning anything older silently failed

Also wires hcledit through the same resolver for
terraform_wrapper_module_for_each - the one release-binary hook
--tool-version didn't cover yet - and fixes the Docker cache-mount
README example, which mounted under /root while also dropping to a
non-root USERID.

Assisted-by: Sisyphus:claude-sonnet-5 opencode
@MaxymVlasov

Copy link
Copy Markdown
Collaborator Author

Generated by Sisyphus:claude-sonnet-5 opencode

Also addressed the 3 additional findings Copilot suppressed (lower-confidence, listed in the review body but never posted as separate review comments, so there's no thread to resolve here):

  • hooks/_common.sh (specific-version pagination) - common::install_from_gh_release only searched the first, unpaginated /releases page (GitHub's default: 30 newest), so pinning anything older silently failed. Fixed in 7892df6 - pages through in batches of 100 until matched or exhausted. Verified against a real old release (tflint v0.44.1, release Replace sed call with native python #31) in tests/pytest/tool_version_test.py::test_real_download_beyond_release_page_one.
  • README claiming all release-binary hooks support --tool-version - terraform_wrapper_module_for_each's hcledit wasn't wired through the resolver despite being a downloadable release binary. Fixed in 7892df6 by wiring it through common::resolve_tool_path, same as every other hook, rather than just documenting it as another exclusion.
  • Docker cache-mount README example - mounted the cache under /root/.cache/... while also setting a non-root USERID; that UID has no permission to traverse /root, and su-exec doesn't change $HOME either, so the mount was unreachable. Fixed in 7892df6 - example now mounts under /tmp with an explicit PCT_TOOL_CACHE_DIR, and pre-creates the host dir.

yermulnik
yermulnik previously approved these changes Aug 21, 2026
`ARG GITHUB_TOKEN` and `ENV INFRACOST_API_KEY` both have empty
defaults; the real values are supplied externally (--build-arg /
-e), never hardcoded. DL3064 can't tell that from the variable name
alone, so it flags both as "potentially sensitive data".

Assisted-by: Sisyphus:claude-sonnet-5 opencode

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 27 out of 27 changed files in this pull request and generated no new comments.

Suppressed comments (3)

hooks/infracost_breakdown.sh:43

  • --tool-version is still present in hook_config, and this function later treats every semicolon-delimited entry as an Infracost jq check. A pin such as --tool-version=0.65.0 is therefore parsed as an expression and makes the hook fail after running Infracost. Filter the resolver-only keys before evaluating the cost checks.
  local -r tool_version=$(common::get_hook_config_value "--tool-version")
  local tool_path
  tool_path=$(common::resolve_tool_path "$tool_name" "$tool_version") || exit $?

tools/install/_common.sh:83

  • These new paginated requests use CURL_CMD, but when GITHUB_TOKEN is set that command sends the literal header Authorization: ****** (line 70), not the token. GitHub rejects it as bad credentials, so the newly exported CI token—and any user's token—causes cache-miss resolution to fail rather than raising the rate limit. Build the authorization header from GITHUB_TOKEN instead of the placeholder.
      page_releases=$("${CURL_CMD[@]}" -s "${RELEASES}?per_page=100&page=${page}")

tests/pytest/tool_version_test.py:889

  • The generic wiring case cannot cover Infracost's new pinning path because the test intentionally ignores the hook result and its echo-only stub is not valid Infracost JSON. Add a dedicated test with valid API 0.2 JSON plus --tool-version/a real cost check, so resolver settings being misinterpreted as jq expressions is caught.
            'infracost_breakdown.sh',
            _CachedTool('infracost', 'infracost'),
            (),
        ),
        id='infracost',

@MaxymVlasov
MaxymVlasov requested a review from yermulnik August 21, 2026 13:51
tools/install/_common.sh calls api.github.com unauthenticated to
resolve each tool's latest-release asset URL. build-image.yaml and
build-image-test.yaml passed GITHUB_TOKEN via buildx `secrets:`,
but no Dockerfile RUN mounts that secret - dead config since #496,
never reconnected when #947 added ARG-based auth to the Dockerfile.

Result: every docker buildx build (PR test, nightly, release) hits
~10 unauthenticated GH API calls x up to 2 platforms. GitHub's
60/hr unauthenticated limit is shared across all Actions runners,
so any call landing on the limit gets no asset match -> curl gets
an empty URL -> exit 3. Observed here as tflint, but any GH-hosted
tool install can trip it.

Same class of bug as 65fe9e4 (reusable-tox.yml), different surface.

Assisted-by: Sisyphus:claude-sonnet-5 opencode
@MaxymVlasov
MaxymVlasov merged commit f707bc5 into master Aug 21, 2026
51 checks passed
@MaxymVlasov
MaxymVlasov deleted the downloadable_and_version_controlled_hooks branch August 21, 2026 15:14
antonbabenko pushed a commit that referenced this pull request Aug 21, 2026
# [1.109.0](v1.108.1...v1.109.0) (2026-08-21)

### Features

* Add ability to download specific tool versions used by hooks. TLDR: add `--hook-config=--tool-version=1.2.3` for pinning wrapped tool version. Works for all tools (even `terraform` and `tofu`) except `checkov` ([#1002](#1002)) ([f707bc5](f707bc5))
@antonbabenko

Copy link
Copy Markdown
Owner

This PR is included in version 1.109.0 🎉

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Allow to specify the version of terraform-docs for the hook to use

4 participants