Skip to content

fix: Fixing report run duplication error#5252

Merged
yhakbar merged 1 commit intomainfrom
fix/fixing-report-duplication
Dec 18, 2025
Merged

fix: Fixing report run duplication error#5252
yhakbar merged 1 commit intomainfrom
fix/fixing-report-duplication

Conversation

@yhakbar
Copy link
Copy Markdown
Collaborator

@yhakbar yhakbar commented Dec 17, 2025

Description

Fixes issue with duplicate reports causing failures in runs.

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

  • Improvements

    • Enhanced execution logging for clearer tracing of run lifecycle and exclusions.
    • More explicit and consistent error handling with structured ignore/retry flows.
    • Improved reporting of run outcomes with richer contextual information.
  • Tests

    • Test suite updated to reflect logging and reporting behavior changes.

✏️ Tip: You can customize this high-level summary in your review settings.

@vercel
Copy link
Copy Markdown

vercel bot commented Dec 17, 2025

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

Project Deployment Review Updated (UTC)
terragrunt-docs Ready Ready Preview, Comment Dec 18, 2025 2:49pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Dec 17, 2025

📝 Walkthrough

Walkthrough

Adds a logger parameter to Report API methods (AddRun, EnsureRun, EndRun), propagates the logger to all call sites, and replaces previously-silent error paths with explicit error-checked logging in dependency discovery and run/error handling flows.

Changes

Cohort / File(s) Summary
Report API updates
internal/report/report.go
Changed signatures for AddRun, EnsureRun, and EndRun to accept a log.Logger first; added debug logging in run lifecycle methods and propagated logger to internal calls.
Report tests
internal/report/report_test.go
Updated tests to create/pass a logger to AddRun, EnsureRun, and EndRun; adjusted test setup signatures and imports for logging.
Runner call sites
internal/runner/common/unit_runner.go, internal/runner/runnerpool/runner.go
Updated callers to pass per-unit logger into EnsureRun and EndRun; switched to using EnsureRun for run creation and propagated error context (e.g., WithCauseRunError) into end reporting.
Dependency discovery
internal/discovery/dependencydiscovery.go
Replaced silent handling for excluded external dependencies with explicit calls to EnsureRun/EndRun that accept a logger; errors from these calls are now logged.
Error handling / options
options/options.go
Refactored RunWithErrorHandling to use Errors.ProcessError, compute a cleaned absolute reportDir, call EnsureRun with logger/reportDir, and pass logger into EndRun with ignore/retry reason reporting and signal handling.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25–35 minutes

  • Verify all call sites consistently pass the correct logger instance to EnsureRun/EndRun.
  • Review EndRun error-logging paths added in dependency discovery and runner flows.
  • Inspect options.RunWithErrorHandling changes for correct reportDir handling and error-action reporting (ignore/retry) and signal recording.

Possibly related PRs

Suggested reviewers

  • ThisGuyCodes
  • denis256

Pre-merge checks and finishing touches

❌ Failed checks (1 warning, 1 inconclusive)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 8.33% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Description check ❓ Inconclusive The description addresses the core issue but lacks specifics; required template sections like issue reference, detailed change explanation, and complete release notes are missing or incomplete. Add issue reference (e.g., 'Fixes #XXXX'), provide detailed explanation of the duplication problem and solution, complete the release notes with specific changes, and clarify backward compatibility implications.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: Fixing report run duplication error' accurately describes the main change—resolving duplicate reports causing run failures—aligning with the code modifications across multiple files.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch fix/fixing-report-duplication

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

Comment thread internal/discovery/dependencydiscovery.go Outdated
Copy link
Copy Markdown
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.

Actionable comments posted: 0

🧹 Nitpick comments (1)
internal/runner/common/unit_runner.go (1)

83-106: Minor optimization opportunity: path is cleaned twice.

The unitPath is cleaned on line 60 and again on line 84. Since the path doesn't change between these calls, you could store the cleaned path in a variable to avoid redundant cleaning.

🔎 View suggested refactor:
 	// Only create report entries if report is not nil
 	if r != nil {
 		unitPath := runner.Unit.AbsolutePath()
 		unitPath = util.CleanPath(unitPath)
 
 		if _, err := r.EnsureRun(runner.Unit.Execution.Logger, unitPath); err != nil {
 			return err
 		}
+
+		// Store cleaned path to avoid recleaning later
+		defer func(cleanedPath string) {
+			if runErr != nil {
+				if endErr := r.EndRun(
+					runner.Unit.Execution.Logger,
+					cleanedPath,
+					report.WithResult(report.ResultFailed),
+					report.WithReason(report.ReasonRunError),
+					report.WithCauseRunError(runErr.Error()),
+				); endErr != nil {
+					runner.Unit.Execution.Logger.Errorf("Error ending run for unit %s: %v", cleanedPath, endErr)
+				}
+			} else {
+				if endErr := r.EndRun(
+					runner.Unit.Execution.Logger,
+					cleanedPath,
+					report.WithResult(report.ResultSucceeded),
+				); endErr != nil {
+					runner.Unit.Execution.Logger.Errorf("Error ending run for unit %s: %v", cleanedPath, endErr)
+				}
+			}
+		}(unitPath)
 	}
 
 	// Use a unit-scoped detailed exit code so retries in this unit don't clobber global state
 	globalExitCode := tf.DetailedExitCodeFromContext(ctx)
 
 	var unitExitCode tf.DetailedExitCode
 
 	ctx = tf.ContextWithDetailedExitCode(ctx, &unitExitCode)
 
 	runErr := opts.RunTerragrunt(ctx, runner.Unit.Execution.Logger, opts, r)
 
 	// Only merge the final unit exit code when the unit run completed without error
 	// and the exit code isn't stuck at 1 from a prior retry attempt.
 	if runErr == nil && globalExitCode != nil && unitExitCode.Get() != tf.DetailedExitCodeError {
 		globalExitCode.Set(unitExitCode.Get())
 	}
-
-	// End the run with appropriate result (only if report is not nil)
-	if r != nil {
-		unitPath := runner.Unit.AbsolutePath()
-		unitPath = util.CleanPath(unitPath)
-
-		if runErr != nil {
-			if endErr := r.EndRun(
-				runner.Unit.Execution.Logger,
-				unitPath,
-				report.WithResult(report.ResultFailed),
-				report.WithReason(report.ReasonRunError),
-				report.WithCauseRunError(runErr.Error()),
-			); endErr != nil {
-				runner.Unit.Execution.Logger.Errorf("Error ending run for unit %s: %v", unitPath, endErr)
-			}
-		} else {
-			if endErr := r.EndRun(
-				runner.Unit.Execution.Logger,
-				unitPath,
-				report.WithResult(report.ResultSucceeded),
-			); endErr != nil {
-				runner.Unit.Execution.Logger.Errorf("Error ending run for unit %s: %v", unitPath, endErr)
-			}
-		}
-	}
 
 	return runErr

Alternatively, if you prefer to keep the current structure, simply reuse the cleaned path:

 	// Only create report entries if report is not nil
+	var cleanedUnitPath string
 	if r != nil {
 		unitPath := runner.Unit.AbsolutePath()
-		unitPath = util.CleanPath(unitPath)
+		cleanedUnitPath = util.CleanPath(unitPath)
 
-		if _, err := r.EnsureRun(runner.Unit.Execution.Logger, unitPath); err != nil {
+		if _, err := r.EnsureRun(runner.Unit.Execution.Logger, cleanedUnitPath); err != nil {
 			return err
 		}
 	}
 
 	// Use a unit-scoped detailed exit code so retries in this unit don't clobber global state
 	globalExitCode := tf.DetailedExitCodeFromContext(ctx)
 
 	var unitExitCode tf.DetailedExitCode
 
 	ctx = tf.ContextWithDetailedExitCode(ctx, &unitExitCode)
 
 	runErr := opts.RunTerragrunt(ctx, runner.Unit.Execution.Logger, opts, r)
 
 	// Only merge the final unit exit code when the unit run completed without error
 	// and the exit code isn't stuck at 1 from a prior retry attempt.
 	if runErr == nil && globalExitCode != nil && unitExitCode.Get() != tf.DetailedExitCodeError {
 		globalExitCode.Set(unitExitCode.Get())
 	}
 
 	// End the run with appropriate result (only if report is not nil)
 	if r != nil {
-		unitPath := runner.Unit.AbsolutePath()
-		unitPath = util.CleanPath(unitPath)
-
 		if runErr != nil {
 			if endErr := r.EndRun(
 				runner.Unit.Execution.Logger,
-				unitPath,
+				cleanedUnitPath,
 				report.WithResult(report.ResultFailed),
 				report.WithReason(report.ReasonRunError),
 				report.WithCauseRunError(runErr.Error()),
 			); endErr != nil {
-				runner.Unit.Execution.Logger.Errorf("Error ending run for unit %s: %v", unitPath, endErr)
+				runner.Unit.Execution.Logger.Errorf("Error ending run for unit %s: %v", cleanedUnitPath, endErr)
 			}
 		} else {
 			if endErr := r.EndRun(
 				runner.Unit.Execution.Logger,
-				unitPath,
+				cleanedUnitPath,
 				report.WithResult(report.ResultSucceeded),
 			); endErr != nil {
-				runner.Unit.Execution.Logger.Errorf("Error ending run for unit %s: %v", unitPath, endErr)
+				runner.Unit.Execution.Logger.Errorf("Error ending run for unit %s: %v", cleanedUnitPath, endErr)
 			}
 		}
 	}
 
 	return runErr
📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0969ec6 and 4f421ed.

📒 Files selected for processing (6)
  • internal/discovery/dependencydiscovery.go (1 hunks)
  • internal/report/report.go (6 hunks)
  • internal/report/report_test.go (33 hunks)
  • internal/runner/common/unit_runner.go (3 hunks)
  • internal/runner/runnerpool/runner.go (5 hunks)
  • options/options.go (3 hunks)
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/runner/runnerpool/runner.go
  • internal/report/report.go
🧰 Additional context used
📓 Path-based instructions (1)
**/*.go

⚙️ CodeRabbit configuration file

Review the Go code for quality and correctness. Make sure that the Go code follows best practices, is performant, and is easy to understand and maintain.

Files:

  • internal/runner/common/unit_runner.go
  • options/options.go
  • internal/report/report_test.go
  • internal/discovery/dependencydiscovery.go
🧠 Learnings (2)
📚 Learning: 2025-02-10T13:36:19.542Z
Learnt from: levkohimins
Repo: gruntwork-io/terragrunt PR: 3723
File: cli/commands/stack/action.go:160-160
Timestamp: 2025-02-10T13:36:19.542Z
Learning: The project uses a custom error package `github.com/gruntwork-io/terragrunt/internal/errors` which provides similar functionality to `fmt.Errorf` but includes stack traces. Prefer using this package's error functions (e.g., `errors.Errorf`, `errors.New`) over the standard library's error handling.

Applied to files:

  • options/options.go
  • internal/report/report_test.go
📚 Learning: 2025-08-19T16:05:54.723Z
Learnt from: Resonance1584
Repo: gruntwork-io/terragrunt PR: 4683
File: go.mod:86-90
Timestamp: 2025-08-19T16:05:54.723Z
Learning: When analyzing Go module dependencies for removal, always check for both direct imports and API usage across all Go files in the repository, not just a quick search. The github.com/mattn/go-zglob library is used for filesystem walking and glob expansion in multiple Terragrunt files including util/file.go, format commands, and AWS provider patch functionality.

Applied to files:

  • internal/report/report_test.go
🧬 Code graph analysis (4)
internal/runner/common/unit_runner.go (2)
internal/component/unit.go (1)
  • Unit (22-33)
internal/report/report.go (2)
  • WithResult (257-261)
  • ResultSucceeded (54-54)
options/options.go (2)
internal/report/report.go (1)
  • Report (16-23)
util/file.go (1)
  • CleanPath (637-639)
internal/report/report_test.go (2)
test/helpers/logger/logger.go (1)
  • CreateLogger (9-14)
internal/report/report.go (11)
  • WithResult (257-261)
  • Report (16-23)
  • WithReason (264-268)
  • ReasonRunError (63-63)
  • ResultExcluded (57-57)
  • WithCauseRetryBlock (274-276)
  • ResultSucceeded (54-54)
  • ReasonRetrySucceeded (61-61)
  • ResultEarlyExit (56-56)
  • WithCauseExcludeBlock (290-292)
  • ReasonAncestorError (67-67)
internal/discovery/dependencydiscovery.go (1)
internal/report/report.go (4)
  • WithResult (257-261)
  • ResultExcluded (57-57)
  • WithReason (264-268)
  • ReasonExcludeExternal (66-66)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
  • GitHub Check: lint / lint
  • GitHub Check: license_check / License Check
  • GitHub Check: Pull Request has non-contributor approval
🔇 Additional comments (7)
internal/discovery/dependencydiscovery.go (1)

293-307: LGTM! Error handling for excluded dependencies is well implemented.

The changes properly handle errors when creating and ending runs for excluded external dependencies by logging them without stopping execution, which is appropriate for reporting purposes. The use of cleaned absolute paths ensures consistency across the reporting flow.

internal/report/report_test.go (3)

54-67: LGTM! Logger integration is correct.

The test properly creates a logger and passes it to all AddRun calls, consistent with the new API signature.


116-196: LGTM! Logger properly propagated through test setup.

The test correctly creates a logger and passes it through both the setup function and all report method calls, ensuring consistent logging context.


457-526: LGTM! Test setup functions properly updated.

The setup function signatures correctly accept a logger parameter, and the logger is properly propagated to all report method calls within the test scenarios.

options/options.go (2)

692-697: LGTM! Path normalization is correctly implemented.

The two-step process of converting to an absolute path followed by cleaning ensures consistent path formatting for reporting. Error handling is appropriate.


725-740: LGTM! Logger and path handling are consistent across error flows.

Both the ignore and retry error handling paths correctly propagate the logger to EnsureRun and EndRun, and consistently use the cleaned reportDir for all reporting operations. The error handling ensures any reporting failures are properly caught and returned.

Also applies to: 758-771

internal/runner/common/unit_runner.go (1)

58-64: LGTM! Run creation is correctly implemented.

The code properly checks for a nil report, cleans the unit path, and calls EnsureRun with the unit's logger. The return value is appropriately discarded since it's not needed, and error handling is correct.

@yhakbar yhakbar requested a review from denis256 December 18, 2025 14:52
@yhakbar yhakbar merged commit 9486574 into main Dec 18, 2025
126 of 130 checks passed
@yhakbar yhakbar deleted the fix/fixing-report-duplication branch December 18, 2025 20:18
@coderabbitai coderabbitai bot mentioned this pull request Jan 7, 2026
6 tasks
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.

2 participants