Skip to content

chore: improved log message for failing hooks#5603

Merged
denis256 merged 13 commits intomainfrom
5596-hook-eror-message
Mar 3, 2026
Merged

chore: improved log message for failing hooks#5603
denis256 merged 13 commits intomainfrom
5596-hook-eror-message

Conversation

@denis256
Copy link
Member

@denis256 denis256 commented Feb 23, 2026

Description

  • improved log message for failing hooks

When a hook (like tflint or a custom linter) exits with a non-zero code, the error message now shows the command, exit code, and actual output instead of the generic "Failed to execute ...
exit status 2".

  Before:
  ERROR  Error running hook lint_check with message: Failed to execute "sh -c ..." in .
  exit status 2

  After:
  ERROR  Hook "lint_check" (command: sh -c ...) failed with exit code 2:
  ERROR: resource "aws_s3_bucket" missing required tags:...

Fixes #5596.

TODOs

Read the Gruntwork contribution guidelines.

  • I authored this code entirely myself
  • I am submitting code based on open source software (e.g. MIT, MPL-2.0, Apache)]
  • I am adding or upgrading a dependency or adapted code and confirm it has a compatible open source license
  • Update the docs.
  • Run the relevant tests successfully, including pre-commit checks.
  • Include release notes. If this PR is backward incompatible, include a migration guide.

Release Notes (draft)

Added / Removed / Updated [X].

Migration Guide

Summary by CodeRabbit

  • New Features

    • Improved GCP credential handling and impersonation option composition.
  • Bug Fixes

    • Hook failure messages now include command, exit code, and output for clearer diagnostics.
    • TFLint error reporting now wraps and exposes underlying errors for better context.
  • Tests

    • Added tests and fixtures covering hook exit-code failures and message formatting.

@vercel
Copy link

vercel bot commented Feb 23, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
terragrunt-docs Ready Ready Preview, Comment Mar 3, 2026 3:10pm

Request Review

@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 23, 2026

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

Refactors GCP credential handling, changes impersonation option composition, introduces formatted hook execution error messages with unit tests, wraps tflint execution errors to preserve underlying errors, and adds a hook-exit-code fixture plus an integration test asserting non-zero hook reporting.

Changes

Cohort / File(s) Summary
Hook error formatting
internal/runner/run/hook.go, internal/runner/run/hook_internal_test.go
Adds hookErrorMessage(hookName, err) to extract command, args, exit code, and preferred output (stderr/stdout) from process execution errors; replaces inline error message construction and adds unit tests covering stderr, stdout fallback, no output, wrapped errors, and exit-code cases.
TFLint error wrapping
internal/tflint/tflint.go
Changes ErrorRunningTflint to include Err error and Args []string, updates Error() to include the wrapped error and arguments, and adds Unwrap() to expose the underlying error.
GCP credentials handling
internal/gcphelper/config.go
Splits env credential creation assignment into a separate if block and adjusts impersonation flow so the impersonation token source is created with existing client options and replaces the final clientOpts slice instead of being appended.
Hook exit-code fixture & integration test
test/fixtures/hooks/exit-code-error/main.tf, test/fixtures/hooks/exit-code-error/terragrunt.hcl, test/integration_hooks_test.go
Adds a fixture where a hook writes a lint warning to stderr and exits with code 2, and an integration test TestTerragruntHookExitCodeError asserting the reported non-zero exit code and that the hook output appears in error output.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Out of Scope Changes check ⚠️ Warning Changes in internal/gcphelper/config.go related to GCP credentials and client options are out of scope relative to the stated objective of improving hook error messages. Remove unrelated changes to internal/gcphelper/config.go that modify GCP credentials and client options handling, as these are not part of the hook error messaging improvement objective.
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (3 passed)
Check name Status Explanation
Title check ✅ Passed The title 'chore: improved log message for failing hooks' is directly related to the main change in the PR, which improves error messages for hook failures.
Description check ✅ Passed The PR description includes a clear problem statement, before/after examples, the issue reference (#5596), and completion of most required checklist items.
Linked Issues check ✅ Passed The code changes successfully address issue #5596 by adding a hookErrorMessage helper function that extracts command, exit code, and output from errors, and integrating it into hook error logging.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch 5596-hook-eror-message

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 and usage tips.

func (err ErrorRunningTflint) Error() string {
return fmt.Sprintf("Error while running tflint with args: %v", err.args)
if err.Err != nil {
return fmt.Sprintf("Error while running tflint with args: %v: %s", err.Args, err.Err)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
return fmt.Sprintf("Error while running tflint with args: %v: %s", err.Args, err.Err)
return fmt.Sprintf("Error encountered while running tflint with args: '%v': %s", err.Args, err.Err)

return fmt.Sprintf("Error while running tflint with args: %v: %s", err.Args, err.Err)
}

return fmt.Sprintf("Error while running tflint with args: %v", err.Args)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
return fmt.Sprintf("Error while running tflint with args: %v", err.Args)
return fmt.Sprintf("Error encountered while running tflint with args: '%v'", err.Args)

}

if exitCodeErr == nil && output != "" {
return fmt.Sprintf("Hook %q (command: %s) failed with exit code %d:\n%s", hookName, cmd, exitCode, output)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
return fmt.Sprintf("Hook %q (command: %s) failed with exit code %d:\n%s", hookName, cmd, exitCode, output)
return fmt.Sprintf("Hook %q (command: %s) exited with non-zero exit code %d:\n%s", hookName, cmd, exitCode, output)

return fmt.Sprintf("Hook %q (command: %s) failed with exit code %d:\n%s", hookName, cmd, exitCode, output)
}

if exitCodeErr == nil {
Copy link
Collaborator

Choose a reason for hiding this comment

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

Kinda confused by this.

}

if exitCodeErr == nil {
return fmt.Sprintf("Hook %q (command: %s) failed with exit code %d", hookName, cmd, exitCode)
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
return fmt.Sprintf("Hook %q (command: %s) failed with exit code %d", hookName, cmd, exitCode)
return fmt.Sprintf("Hook %q (command: %s) exited with non-zero exit code %d", hookName, cmd, exitCode)

}
}

return fmt.Sprintf("Hook %q failed: %s", hookName, err.Error())
Copy link
Collaborator

Choose a reason for hiding this comment

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

Suggested change
return fmt.Sprintf("Hook %q failed: %s", hookName, err.Error())
return fmt.Sprintf("Hook %q failed to execute: %s", hookName, err.Error())

Assuming this is correct. I'm not sure about that.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (2)
test/integration_hooks_test.go (1)

480-483: Strengthen this test to assert hook name and command context too.

The new assertions cover exit code + output, but the PR goal also includes clearer hook identity/command context. Add assertions for those substrings so regressions in message context are caught.

Suggested assertion extension
 	output := stderr.String()
 	// Error message should show exit code and the actual hook output
+	assert.Contains(t, output, `Hook "hook_exit_nonzero"`)
+	assert.Contains(t, output, "command:")
 	assert.Contains(t, output, `exited with non-zero exit code 2`)
 	assert.Contains(t, output, "lint warning: something is wrong")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@test/integration_hooks_test.go` around lines 480 - 483, The test currently
asserts the exit code and hook output but misses checking hook identity and
command context; update the assertions around the local variable output (in
test/integration_hooks_test.go) to also assert that output contains the hook
name (e.g., the hook identifier string used in the test) and the executed
command context (e.g., the command invocation or arguments shown in logs), by
adding two assert.Contains calls referencing the same output variable so the
failure message includes both the hook name and the command string.
internal/runner/run/hook.go (1)

41-41: Render command arguments with quoting to avoid ambiguous diagnostics.

Joining raw args with spaces can misrepresent commands when args contain spaces/quotes. Quoting each token makes the logged command deterministic and copy/paste-safe.

Suggested update
 import (
 	"context"
 	"fmt"
+	"strconv"
 	"slices"
 	"strings"
 	"sync"
@@
-	cmd := strings.Join(append([]string{processErr.Command}, processErr.Args...), " ")
+	parts := append([]string{processErr.Command}, processErr.Args...)
+	for i := range parts {
+		parts[i] = strconv.Quote(parts[i])
+	}
+	cmd := strings.Join(parts, " ")
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/runner/run/hook.go` at line 41, The logged command is built by
joining processErr.Command and processErr.Args raw which misrepresents tokens
containing spaces or quotes; update the construction of cmd (where
processErr.Command and processErr.Args are used) to quote each token
deterministically (e.g., map strconv.Quote over the command and each arg or
otherwise shell-escape each token) and then join the quoted tokens with spaces
so the diagnostic is copy/paste-safe and unambiguous.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/runner/run/hook.go`:
- Line 41: The logged command is built by joining processErr.Command and
processErr.Args raw which misrepresents tokens containing spaces or quotes;
update the construction of cmd (where processErr.Command and processErr.Args are
used) to quote each token deterministically (e.g., map strconv.Quote over the
command and each arg or otherwise shell-escape each token) and then join the
quoted tokens with spaces so the diagnostic is copy/paste-safe and unambiguous.

In `@test/integration_hooks_test.go`:
- Around line 480-483: The test currently asserts the exit code and hook output
but misses checking hook identity and command context; update the assertions
around the local variable output (in test/integration_hooks_test.go) to also
assert that output contains the hook name (e.g., the hook identifier string used
in the test) and the executed command context (e.g., the command invocation or
arguments shown in logs), by adding two assert.Contains calls referencing the
same output variable so the failure message includes both the hook name and the
command string.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 57ce08a and 566edab.

📒 Files selected for processing (5)
  • internal/gcphelper/config.go
  • internal/runner/run/hook.go
  • internal/runner/run/hook_internal_test.go
  • internal/tflint/tflint.go
  • test/integration_hooks_test.go

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (1)
internal/gcphelper/config.go (1)

104-118: Good fix for impersonation, but the comment is now stale.

Passing clientOpts... to CredentialsTokenSource correctly allows impersonation to use explicitly provided credentials as the source identity, rather than always relying on ADC. This is the right behavior.

However, the comment on lines 105-107 now contradicts the implementation — it states the impersonate library "uses Application Default Credentials internally as the source identity," but with this change, it uses the gathered clientOpts first (falling back to ADC only if none were provided).

📝 Suggested comment update
 	// Handle service account impersonation.
-	// When impersonation is configured, the impersonation token source replaces
-	// any base credentials. The impersonate library uses Application Default
-	// Credentials internally as the source identity.
+	// When impersonation is configured, the impersonation token source replaces
+	// any base credentials. The impersonate library uses the provided credentials
+	// (or Application Default Credentials if none were provided) as the source identity.
 	if gcpCfg != nil && gcpCfg.ImpersonateServiceAccount != "" {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/gcphelper/config.go` around lines 104 - 118, Update the stale
comment above the impersonation block to reflect the actual behavior: note that
when gcpCfg.ImpersonateServiceAccount is set we call
impersonate.CredentialsTokenSource with the current clientOpts so the
impersonation token source will use the explicitly provided credentials as the
source identity (falling back to ADC only if no clientOpts are supplied),
instead of claiming the impersonate library always uses Application Default
Credentials.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@internal/gcphelper/config.go`:
- Around line 104-118: Update the stale comment above the impersonation block to
reflect the actual behavior: note that when gcpCfg.ImpersonateServiceAccount is
set we call impersonate.CredentialsTokenSource with the current clientOpts so
the impersonation token source will use the explicitly provided credentials as
the source identity (falling back to ADC only if no clientOpts are supplied),
instead of claiming the impersonate library always uses Application Default
Credentials.

ℹ️ Review info

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 7b260b9 and 5d27aa0.

📒 Files selected for processing (1)
  • internal/gcphelper/config.go

Resolve conflict in internal/gcphelper/config.go: use `env` variable
with two-line pattern to keep envCreds in outer scope.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@denis256 denis256 merged commit 10d9c52 into main Mar 3, 2026
52 of 53 checks passed
@denis256 denis256 deleted the 5596-hook-eror-message branch March 3, 2026 16:21
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.

Hook execution error message is confusing

2 participants