From 3a946994d550336defb7cc5777bce109c0decc72 Mon Sep 17 00:00:00 2001 From: Arunesh Dwivedi Date: Sat, 6 Jun 2026 06:30:23 +0000 Subject: [PATCH 1/6] fix: force terraform init when plugin cache and parallelism are both active When TF_PLUGIN_CACHE_DIR is set and parallelism is enabled, the optimistic first validate attempt can fail due to race conditions with concurrent plugin cache access. Run terraform init first to ensure the cache is properly populated before validation. Fixes antonbabenko/pre-commit-terraform#640 --- hooks/terraform_validate.sh | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/hooks/terraform_validate.sh b/hooks/terraform_validate.sh index ad792b038..6cbb8a135 100755 --- a/hooks/terraform_validate.sh +++ b/hooks/terraform_validate.sh @@ -115,6 +115,16 @@ function per_dir_hook_unique_part { esac done + # When plugin cache is enabled and parallelism is active, always run + # terraform init first to avoid race conditions with concurrent access. + # https://github.com/hashicorp/terraform/issues/31964 + if [[ -n $TF_PLUGIN_CACHE_DIR && $parallelism_disabled != true ]]; then + common::terraform_init "$tf_path validate" "$dir_path" "$parallelism_disabled" "$tf_path" || { + exit_code=$? + return $exit_code + } + fi + # First try `terraform validate` with the hope that all deps are # pre-installed. That is needed for cases when `.terraform/modules` # or `.terraform/providers` missed AND that is expected. From 7695906c75b480bfb837cb2cbc5af79de284b449 Mon Sep 17 00:00:00 2001 From: Arunesh Dwivedi Date: Mon, 8 Jun 2026 05:48:31 +0000 Subject: [PATCH 2/6] fix: address review feedback on terraform validate init guard - Only run early init when .terraform dir doesn't exist (avoids unnecessary time penalty on subsequent commits) - Drop -n from [[ -n $var ]] per modern bash style - Update function docs to reflect new flow - Add provider fetch error to match_validate_errors retry list --- hooks/terraform_validate.sh | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/hooks/terraform_validate.sh b/hooks/terraform_validate.sh index 6cbb8a135..4106fa0eb 100755 --- a/hooks/terraform_validate.sh +++ b/hooks/terraform_validate.sh @@ -57,6 +57,7 @@ function match_validate_errors { "Could not load plugin") return 1 ;; "Missing required provider") return 1 ;; *"there is no package for"*"cached in .terraform/providers") return 1 ;; + *"could not retrieve the list of available versions for provider"*) return 1 ;; esac done < <(jq -rc '.diagnostics[]' <<< "$validate_output") @@ -66,9 +67,11 @@ function match_validate_errors { ####################################################################### # Unique part of `common::per_dir_hook`. The function is executed in loop # on each provided dir path. Run wrapped tool with specified arguments -# 1. Check if `.terraform` dir exists and if not - run `terraform init` -# 2. Run `terraform validate` -# 3. If at least 1 check failed - change the exit code to non-zero +# 1. Run `terraform validate` +# 2. If validate fails, run `terraform init` and retry +# 3. If plugin cache parallelism causes a race condition, the error is +# caught by match_validate_errors and retried automatically +# 4. If at least 1 check failed - change the exit code to non-zero # Arguments: # dir_path (string) PATH to dir relative to git repo root. # Can be used in error logging @@ -115,10 +118,11 @@ function per_dir_hook_unique_part { esac done - # When plugin cache is enabled and parallelism is active, always run - # terraform init first to avoid race conditions with concurrent access. + # When plugin cache is enabled and parallelism is active, run + # terraform init before validate to avoid race conditions with + # concurrent cache access. Only needed when .terraform doesn't exist. # https://github.com/hashicorp/terraform/issues/31964 - if [[ -n $TF_PLUGIN_CACHE_DIR && $parallelism_disabled != true ]]; then + if [[ ! -d $dir_path/.terraform && $TF_PLUGIN_CACHE_DIR && $parallelism_disabled != true ]]; then common::terraform_init "$tf_path validate" "$dir_path" "$parallelism_disabled" "$tf_path" || { exit_code=$? return $exit_code From 254c4d6ba209b080223978401efab5d1d7f6a369 Mon Sep 17 00:00:00 2001 From: Arunesh Dwivedi Date: Tue, 9 Jun 2026 16:42:05 +0000 Subject: [PATCH 3/6] fix: handle plugin cache race condition via retry instead of proactive init The previous approach ran terraform init before every validate when TF_PLUGIN_CACHE_DIR was set and parallelism was active. This added significant overhead (23+ seconds with providers) on every commit. Instead, add 'unexpected value returned by API' to the retryable error patterns in match_validate_errors. The existing retry mechanism at line 142 already handles running terraform init when validate fails, so the proactive init is unnecessary. This is a much lighter-weight fix: - No overhead when there's no race condition - Only runs terraform init when the specific error occurs - Backward compatible: existing behavior preserved for all other cases Signed-off-by: Arunesh Dwivedi --- hooks/terraform_validate.sh | 13 +------------ 1 file changed, 1 insertion(+), 12 deletions(-) diff --git a/hooks/terraform_validate.sh b/hooks/terraform_validate.sh index 4106fa0eb..6d3b45700 100755 --- a/hooks/terraform_validate.sh +++ b/hooks/terraform_validate.sh @@ -58,6 +58,7 @@ function match_validate_errors { "Missing required provider") return 1 ;; *"there is no package for"*"cached in .terraform/providers") return 1 ;; *"could not retrieve the list of available versions for provider"*) return 1 ;; + *"unexpected value returned by API"*) return 1 ;; esac done < <(jq -rc '.diagnostics[]' <<< "$validate_output") @@ -71,7 +72,6 @@ function match_validate_errors { # 2. If validate fails, run `terraform init` and retry # 3. If plugin cache parallelism causes a race condition, the error is # caught by match_validate_errors and retried automatically -# 4. If at least 1 check failed - change the exit code to non-zero # Arguments: # dir_path (string) PATH to dir relative to git repo root. # Can be used in error logging @@ -118,17 +118,6 @@ function per_dir_hook_unique_part { esac done - # When plugin cache is enabled and parallelism is active, run - # terraform init before validate to avoid race conditions with - # concurrent cache access. Only needed when .terraform doesn't exist. - # https://github.com/hashicorp/terraform/issues/31964 - if [[ ! -d $dir_path/.terraform && $TF_PLUGIN_CACHE_DIR && $parallelism_disabled != true ]]; then - common::terraform_init "$tf_path validate" "$dir_path" "$parallelism_disabled" "$tf_path" || { - exit_code=$? - return $exit_code - } - fi - # First try `terraform validate` with the hope that all deps are # pre-installed. That is needed for cases when `.terraform/modules` # or `.terraform/providers` missed AND that is expected. From 69694d0548d8b38381d04a45bf8689ce3f7bdbea Mon Sep 17 00:00:00 2001 From: Arunesh Dwivedi Date: Sat, 13 Jun 2026 18:30:41 +0000 Subject: [PATCH 4/6] fix(terraform_validate): force parallelism disabled on retry init When TF_PLUGIN_CACHE_DIR is set and the plugin cache race condition triggers a retry, the second terraform init call must force parallelism_disabled=true to avoid re-entering the retry loop in common::terraform_init. Previously the retry call passed the same $parallelism_disabled variable, making it identical to the first call. Also add plugin-cache-related error patterns to match_validate_errors so that cache corruption races are detected and retried. Signed-off-by: Arunesh Dwivedi --- hooks/terraform_validate.sh | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/hooks/terraform_validate.sh b/hooks/terraform_validate.sh index 6d3b45700..586a81a81 100755 --- a/hooks/terraform_validate.sh +++ b/hooks/terraform_validate.sh @@ -59,6 +59,8 @@ function match_validate_errors { *"there is no package for"*"cached in .terraform/providers") return 1 ;; *"could not retrieve the list of available versions for provider"*) return 1 ;; *"unexpected value returned by API"*) return 1 ;; + *"plugin is cached but contents have changed"*) return 1 ;; + *"the plugin cache directory is invalid"*) return 1 ;; esac done < <(jq -rc '.diagnostics[]' <<< "$validate_output") @@ -157,7 +159,7 @@ function per_dir_hook_unique_part { common::colorify "yellow" "Re-validating: $dir_path" - common::terraform_init "$tf_path validate" "$dir_path" "$parallelism_disabled" "$tf_path" || { + common::terraform_init "$tf_path validate" "$dir_path" "true" "$tf_path" || { exit_code=$? return $exit_code } From ae26d3daeb7030261f99b0a86a09ffa2c2330f7b Mon Sep 17 00:00:00 2001 From: Arunesh Dwivedi Date: Tue, 16 Jun 2026 06:16:10 +0000 Subject: [PATCH 5/6] fix: clarify retry is conditional on --retry-once-with-cleanup flag The comment now explicitly states that automatic retry for plugin-cache race conditions requires the --retry-once-with-cleanup flag to be set. Signed-off-by: Arunesh Dwivedi --- hooks/terraform_validate.sh | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/hooks/terraform_validate.sh b/hooks/terraform_validate.sh index 586a81a81..a14f04e73 100755 --- a/hooks/terraform_validate.sh +++ b/hooks/terraform_validate.sh @@ -72,8 +72,9 @@ function match_validate_errors { # on each provided dir path. Run wrapped tool with specified arguments # 1. Run `terraform validate` # 2. If validate fails, run `terraform init` and retry -# 3. If plugin cache parallelism causes a race condition, the error is -# caught by match_validate_errors and retried automatically +# 3. If --retry-once-with-cleanup is enabled and plugin cache parallelism +# causes a race condition, the error is caught by match_validate_errors +# and retried with cleanup # Arguments: # dir_path (string) PATH to dir relative to git repo root. # Can be used in error logging From 0585e26f261e4e074001fe87c2ad4dd840f71f57 Mon Sep 17 00:00:00 2001 From: Arunesh Dwivedi Date: Fri, 19 Jun 2026 18:30:37 +0530 Subject: [PATCH 6/6] Update hooks/terraform_validate.sh Co-authored-by: Maksym Vlasov --- hooks/terraform_validate.sh | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/hooks/terraform_validate.sh b/hooks/terraform_validate.sh index 086ffc896..e9a708acf 100755 --- a/hooks/terraform_validate.sh +++ b/hooks/terraform_validate.sh @@ -58,10 +58,7 @@ function match_validate_errors { "Could not load plugin") return 1 ;; "Missing required provider") return 1 ;; *"there is no package for"*"cached in .terraform/providers") return 1 ;; - *"could not retrieve the list of available versions for provider"*) return 1 ;; - *"unexpected value returned by API"*) return 1 ;; - *"plugin is cached but contents have changed"*) return 1 ;; - *"the plugin cache directory is invalid"*) return 1 ;; + *"Could not retrieve the list of available versions for provider"*) return 1 ;; esac done < <(jq -rc '.diagnostics[]' <<< "$validate_output")