Skip to content

chore: Moving envs and writers to venv#6092

Open
yhakbar wants to merge 2 commits into
mainfrom
chore/moving-envs-to-venv
Open

chore: Moving envs and writers to venv#6092
yhakbar wants to merge 2 commits into
mainfrom
chore/moving-envs-to-venv

Conversation

@yhakbar

@yhakbar yhakbar commented May 13, 2026

Copy link
Copy Markdown
Collaborator

Description

Moves environment variable access and writes to stdout/stderr to venv.

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.
  • Update the changelog in the docs.
  • Run the relevant tests successfully, including pre-commit checks.
  • This change is backwards compatible.
  • If this change is not forwards compatible (e.g. a new feature), it is gated behind a feature flag.

Summary by CodeRabbit

  • Bug Fixes

    • Improved consistency of where CLI output, diagnostics, and error prompts are written across commands (including help, discovery, rendering, listing, and validation).
    • Ensured logging controls—such as --log-show-abs-paths and disabling error summaries—are applied consistently during execution.
  • Refactor

    • Reworked how execution context, environment variables, and output writers are propagated through CLI flows to keep behavior aligned end-to-end.
  • Chores

    • Updated tests and benchmarks to match the new output/environment wiring.

@vercel

vercel Bot commented May 13, 2026

Copy link
Copy Markdown

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

Project Deployment Actions Updated (UTC)
terragrunt-docs Ready Ready Preview, Comment Jun 18, 2026 9:50pm

Request Review

@coderabbitai

coderabbitai Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 4620d8f1-0596-4f6b-a99e-3103bd3a7cab

📥 Commits

Reviewing files that changed from the base of the PR and between f89d0f7 and 9c91a26.

📒 Files selected for processing (2)
  • internal/configbridge/bridge.go
  • internal/remotestate/backend/backend.go
💤 Files with no reviewable changes (2)
  • internal/remotestate/backend/backend.go
  • internal/configbridge/bridge.go

📝 Walkthrough

Walkthrough

This PR moves runtime env and stdout/stderr handling from options and exec-specific paths into venv objects, then threads that runtime context through CLI commands, discovery, parsing, Terraform execution, remote state, stack operations, and related tests.

Changes

Runtime venv refactor

Layer / File(s) Summary
Runtime contracts and option shapes
internal/venv/*, internal/writer/*, pkg/options/*, pkg/config/parsing_context.go, internal/configbridge/bridge.go, internal/shell/run_cmd.go, internal/runner/run/options.go, internal/runner/run/venv.go, internal/tflint/venv.go, internal/remotestate/backend/backend.go, internal/engine/engine.go, internal/cli/flags/global/flags.go
venv now carries env and writers, options no longer do, logging flags move onto options/parsing contexts, and shell/backend/run contracts are updated to consume venv-backed runtime state.
Execution and runner plumbing
internal/tf/*, internal/prepare/*, internal/runner/run/*, internal/runner/common/*, internal/runner/graph/*, internal/runner/runall/*, internal/runner/runner*, internal/providercache/*, internal/remotestate/remote_state.go, internal/shell/git.go, internal/runner/run/creds/providers/*
Terraform, shell, hooks, credential lookup, init checks, provider cache, remote-state migration, and runner code now execute with venv env/writers and use explicit env arguments for data-dir and debug-file behavior.
Discovery, stacks, and config parsing
internal/discovery/*, internal/stacks/*, pkg/config/config_helpers.go, pkg/config/dependency.go, pkg/config/config.go, pkg/config/include.go, pkg/config/locals.go, pkg/config/stack.go
Discovery phases and stack generation/output accept venv, parsing contexts attach venv, and config/dependency helpers read env and emit output through pctx.Venv and parsing-context log flags.
CLI command wiring and outputs
internal/cli/app.go, internal/cli/commands/..., main.go, test/helpers/package.go, test/benchmarks/helpers/helpers.go
CLI app and many commands now pass venv through command construction and execution, use v.Writers for output, and use v.Env for discovery, help, formatting, rendering, backend, catalog, list, find, stack, and related flows.
Tests and benchmarks
internal/cli/..._test.go, internal/discovery/*test.go, internal/runner/*test.go, internal/shell/*test.go, internal/tf/*test.go, pkg/config/*test.go, pkg/options/options_test.go, internal/telemetry/*test.go, test/integration_*
Tests and benchmarks switch from configuring writers or env on options to constructing venv instances, passing venv.OSVenv() or in-memory venv wrappers into the updated APIs.

Estimated code review effort

🎯 5 (Critical) | ⏱️ ~120 minutes

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 54.12% 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
Title check ✅ Passed The title 'chore: Moving envs and writers to venv' directly describes the main architectural change throughout the changeset - refactoring environment and writer access patterns to route through a centralized venv abstraction.
Description check ✅ Passed The PR description clearly states the main objective ('Moves environment variable access and writes to stdout/stderr to venv') and indicates the required TODOs are complete, though some non-critical sections lack detail about implementation specifics.
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.

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

✨ 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 chore/moving-envs-to-venv

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

@yhakbar yhakbar force-pushed the chore/moving-envs-to-venv branch from 4896ef3 to 4881e82 Compare May 14, 2026 13:21
@yhakbar yhakbar force-pushed the chore/moving-envs-to-venv branch from 4881e82 to 9e3b692 Compare May 14, 2026 21:23
@yhakbar yhakbar force-pushed the chore/expand-pure-testing-through-venv branch from aa0c724 to 286d5b0 Compare May 19, 2026 16:35
@yhakbar yhakbar force-pushed the chore/expand-pure-testing-through-venv branch 3 times, most recently from e9b5ad2 to 17510c1 Compare June 11, 2026 18:47
@yhakbar yhakbar force-pushed the chore/expand-pure-testing-through-venv branch from 17510c1 to 124f427 Compare June 18, 2026 13:51
Base automatically changed from chore/expand-pure-testing-through-venv to main June 18, 2026 15:21
@yhakbar yhakbar force-pushed the chore/moving-envs-to-venv branch from 337a979 to 0ff1991 Compare June 18, 2026 15:54
@yhakbar yhakbar force-pushed the chore/moving-envs-to-venv branch from 2833f7e to 6a3ac53 Compare June 18, 2026 19:47
@yhakbar yhakbar force-pushed the chore/moving-envs-to-venv branch from 6a3ac53 to f89d0f7 Compare June 18, 2026 19:53
@yhakbar yhakbar marked this pull request as ready for review June 18, 2026 21:01
@yhakbar yhakbar requested a review from denis256 as a code owner June 18, 2026 21:01

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 4

Caution

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

⚠️ Outside diff range comments (3)
pkg/options/options.go (1)

79-300: ⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Keep a compatibility path for the exported options API.

pkg/options is importable API surface, and this change removes exported Env/Writers, removes NewTerragruntOptionsWithWriters, and changes TerraformDataDir/DataDir call signatures. That will break downstream builds despite the PR being marked backwards compatible; keep deprecated shims for one release or call this out as an intentional breaking change with the appropriate versioning/migration plan.

Also applies to: 320-348, 539-559

🤖 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 `@pkg/options/options.go` around lines 79 - 300, The TerragruntOptions struct
is removing exported API elements (Env, Writers fields,
NewTerragruntOptionsWithWriters function, and TerraformDataDir/DataDir method
signatures) that downstream users may depend on, which breaks backwards
compatibility. Restore these removed exported fields and functions as deprecated
shims that maintain the original signatures and delegate to the new internal
implementations, and add deprecation warnings or comments to guide users toward
the new API. This preserves the public API surface for at least one release
while allowing the transition to the new structure.
internal/runner/run/options.go (1)

231-239: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Forward the log flags into backend options.

Line 233 builds backend.Options but leaves LogShowAbsPaths and LogDisableErrorSummary at their zero values, unlike configbridge.BackendOptsFromOpts. Backend operations reached through run.Options.remoteStateOpts will ignore those CLI settings.

Proposed fix
 		Options: backend.Options{
 			Writers:                      v.Writers,
 			Env:                          v.Env,
 			IAMRoleOptions:               o.IAMRoleOptions,
 			NonInteractive:               o.NonInteractive,
 			FailIfBucketCreationRequired: o.FailIfBucketCreationRequired,
+			LogShowAbsPaths:              o.LogShowAbsPaths,
+			LogDisableErrorSummary:       o.LogDisableErrorSummary,
 		},
🤖 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 `@internal/runner/run/options.go` around lines 231 - 239, The remoteStateOpts
method is not forwarding the log configuration flags to the backend.Options
struct being created. Add LogShowAbsPaths and LogDisableErrorSummary fields to
the backend.Options initialization within the remoteStateOpts method, setting
these fields from the corresponding values in the Options receiver (o), similar
to how other options like IAMRoleOptions, NonInteractive, and
FailIfBucketCreationRequired are already being forwarded.
pkg/config/dependency.go (1)

1470-1550: ⚠️ Potential issue | 🟡 Minor

Remove redundant WithWriter call at line 1532.

Line 1480 sets pctx.Venv = pctx.Venv.WithWriter(stdoutBufferWriter) after cloning, and pctx.Venv is not modified between that assignment and line 1532. Since WithWriter returns a new Venv (not mutating the receiver), line 1532's runV := pctx.Venv.WithWriter(stdoutBufferWriter) applies WithWriter to a venv that already has stdoutBufferWriter set, creating a redundant duplicate writer assignment. Line 1532 should be runV := pctx.Venv.

🤖 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 `@pkg/config/dependency.go` around lines 1470 - 1550, The WithWriter method is
being applied redundantly to pctx.Venv. At the beginning of the
runTerragruntOutputJSON function, pctx.Venv is already updated with
stdoutBufferWriter via pctx.Venv = pctx.Venv.WithWriter(stdoutBufferWriter).
Since WithWriter returns a new Venv without mutating the original, and pctx.Venv
is not modified between that assignment and line 1532, the subsequent call runV
:= pctx.Venv.WithWriter(stdoutBufferWriter) applies the writer assignment twice.
Remove the redundant WithWriter call on line 1532 and simply assign runV :=
pctx.Venv instead, since pctx.Venv already has the correct writer configured.
🧹 Nitpick comments (7)
internal/cli/commands/commands.go (1)

327-342: ⚡ Quick win

Consider returning an error instead of panicking.

The function panics if v.Env is nil, but since this is called from RunAction (line 261) which already has error-handling infrastructure, returning an error would be more idiomatic Go and allow graceful degradation.

-// Panics if v.Env is nil, as it mutates it.
 func setupAutoProviderCacheDir(ctx context.Context, l log.Logger, opts *options.TerragruntOptions, v venv.Venv) error {
 	if v.Env == nil {
-		panic("setupAutoProviderCacheDir: venv environment map is nil")
+		return errors.New("setupAutoProviderCacheDir: venv environment map is nil")
 	}

Panics in Go are typically reserved for truly unrecoverable programming errors. A nil environment map, while unexpected, can be handled gracefully by returning an error that the caller can log and handle appropriately.

🤖 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 `@internal/cli/commands/commands.go` around lines 327 - 342, Replace the panic
statement in the setupAutoProviderCacheDir function with a proper error return.
Instead of panicking when v.Env is nil, return a formatted error (using
fmt.Errorf or similar) with a descriptive message about the nil environment map.
This allows the calling code in RunAction to handle the error gracefully through
the existing error-handling infrastructure rather than crashing the application.
internal/cli/commands/hcl/validate/validate.go (1)

335-403: 💤 Low value

Consider refactoring to reduce cognitive complexity.

The function has multiple validation paths with nested conditionals (missing/unused vars, strict mode logic, error assembly). While the current structure is acceptable for a validation function, breaking it into smaller helpers could improve readability.

🤖 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 `@internal/cli/commands/hcl/validate/validate.go` around lines 335 - 403, The
runValidateInputs function has multiple nested validation paths and conditional
blocks that can be simplified. Extract the unused variable detection loop into a
helper function, extract the missing variable detection loop into a separate
helper function, create a helper function for logging the validation results
(handling both missing and unused vars cases), and create a helper function for
the error determination logic that checks strict mode. Then refactor
runValidateInputs to call these helpers in sequence, reducing the overall
cognitive complexity and improving readability while maintaining the same
behavior.

Source: Linters/SAST tools

pkg/config/config_helpers_test.go (1)

639-642: ⚡ Quick win

Consider reordering venv initialization for clarity.

The current initialization sets pctx.Venv.Env on line 639, then reassigns the entire pctx.Venv struct on line 642 via the WithWriter(...).WithErrWriter(...) chain. While this works if those methods preserve fields via shallow copying, the ordering makes the code harder to reason about.

♻️ Suggested reordering
-	pctx.Venv.Env = map[string]string{}
 	pctx.SourceMap = map[string]string{}
 	pctx.TerraformCliArgs = iacargs.New()
 	pctx.Venv = pctx.Venv.WithWriter(os.Stdout).WithErrWriter(os.Stderr)
+	pctx.Venv.Env = map[string]string{}

Or initialize in a single struct literal:

-	pctx.Venv.Env = map[string]string{}
 	pctx.SourceMap = map[string]string{}
 	pctx.TerraformCliArgs = iacargs.New()
-	pctx.Venv = pctx.Venv.WithWriter(os.Stdout).WithErrWriter(os.Stderr)
+	pctx.Venv = venv.Venv{
+		Env:     map[string]string{},
+		Writers: writer.Writers{Writer: os.Stdout, ErrWriter: os.Stderr},
+	}
🤖 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 `@pkg/config/config_helpers_test.go` around lines 639 - 642, The pctx.Venv
initialization sets the Env field first on the initial struct, then immediately
reassigns the entire pctx.Venv via the WithWriter and WithErrWriter method
chain, which could overwrite the previously set Env field if those methods don't
preserve it. Reorder the initialization by first assigning pctx.Venv with the
complete WithWriter(os.Stdout).WithErrWriter(os.Stderr) chain, then set
pctx.Venv.Env on the resulting venv instance after the method chain completes.
This makes the initialization order clearer and ensures the Env field is not
overwritten.
internal/discovery/phase_worktree.go (1)

65-172: 🏗️ Heavy lift

Split Run into smaller phase steps.

Sonar flags WorktreePhase.Run at Line 65 for cognitive complexity 43 > 15. Extracting pair expansion/discovery and final classification into helpers would keep the venv threading intact while satisfying the quality gate.

🤖 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 `@internal/discovery/phase_worktree.go` around lines 65 - 172, The
WorktreePhase.Run method has excessive cognitive complexity (43 > 15) due to
nested logic for pair discovery, stack changes discovery, and classification.
Extract the discovery orchestration into a separate helper method that handles
setting up the error group, looping through worktree pairs with
discoverInWorktree calls, and invoking discoverChangesInWorktreeStacks,
returning the discoveredComponents collection. Then extract the classification
and result building logic into another helper method that iterates through the
discovered components, invokes input.Classifier.Classify for each component,
constructs the DiscoveryResult objects, and applies the switch statement to add
results. This separation will reduce the Run method's complexity while
preserving the venv threading behavior via the passed context and logger
parameters.

Source: Linters/SAST tools

internal/providercache/providercache.go (1)

505-580: ⚡ Quick win

Extract the retry attempt body to satisfy the complexity gate.

Sonar flags runTerraformCommand at Line 505 for cognitive complexity 18 > 15. Pulling the per-attempt command setup/error classification/flush handling into a small helper should preserve behavior and unblock analysis.

🤖 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 `@internal/providercache/providercache.go` around lines 505 - 580, The
runTerraformCommand function exceeds the cognitive complexity threshold (18 >
15) due to the complex error handling and retry logic within the closure passed
to util.DoWithRetry. Extract the entire closure body that handles error writer
setup, command execution, error classification (httpStatusCacheProviderReg
matching, timeout detection), and flushing operations into a separate private
helper function. This helper should take the necessary parameters (ctx, logger,
venv, TFOptions, CLI args) and return the command output and error, then call
this helper from within the util.DoWithRetry closure to maintain behavior while
reducing cognitive complexity.

Source: Linters/SAST tools

internal/discovery/phase_graph.go (1)

66-171: 🏗️ Heavy lift

Reduce GraphPhase.Run complexity to clear the SonarCloud gate.

SonarCloud reports cognitive complexity 29 vs the allowed 15 here. Extract candidate partitioning and graph-task scheduling/error aggregation so the new venv threading stays reviewable and the check turns green.

🤖 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 `@internal/discovery/phase_graph.go` around lines 66 - 171, The GraphPhase.Run
method has excessive cognitive complexity (29 vs the allowed 15). Extract the
candidate partitioning logic that separates input.Candidates into
graphTargetCandidates and otherCandidates (the switch statement on
candidate.Reason) into a separate helper method. Additionally, extract the
graph-task scheduling and error aggregation logic that handles the errgroup
setup, iterates over graphExprs and matchingCandidates, and collects errors
(from the g.Go call through the final error handling) into another separate
helper method. This will reduce the complexity of the main Run method while
keeping the new threading logic reviewable and maintainable.

Source: Linters/SAST tools

internal/discovery/phase_parse.go (1)

159-238: ⚡ Quick win

Split ParsePhase.Run before merging to satisfy the complexity check.

SonarCloud reports cognitive complexity 20 vs the allowed 15. Extracting parse-queue construction and result/error handling should be a localized fix while preserving the new venv propagation.

🤖 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 `@internal/discovery/phase_parse.go` around lines 159 - 238, The ParsePhase.Run
method has cognitive complexity exceeding the allowed threshold. Extract the
logic that builds the componentsToParse queue (the section filtering candidates
and checking discovery.readFiles, discovery.parseExclude,
discovery.parseIncludes conditions) into a separate private helper method that
returns the queue of components to parse. Additionally, extract the goroutine
processing logic that handles the switch statement for result.Status and error
accumulation into another separate private helper method that takes the parsed
result and appends to results. This will simplify the main Run method by
reducing nested conditionals and improving readability while preserving the venv
propagation.

Source: Linters/SAST tools

🤖 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 `@internal/runner/run/creds/providers/externalcmd/provider.go`:
- Around line 203-205: In the amazonsts.NewProvider call within the code block
around the provider initialization, replace the nil argument (third parameter)
with v.Env to ensure that the environment variables from the venv context are
properly passed to the STS provider for role assumption, allowing the external
auth command responses to use the correct runtime environment instead of
bypassing the venv-backed environment.

In `@internal/shell/run_cmd.go`:
- Around line 258-262: The code directly mutates the shared Env map in v (at
line 261 with v.Env[telemetry.TraceParentEnv] = traceParent) which causes race
conditions when parallel commands execute concurrently and also panics if Env is
nil. Create a per-command copy of the environment map before injecting the
TRACEPARENT value, ensuring to handle the nil case by initializing an empty map
when needed. Apply this same pattern to all three locations where TraceParent is
injected (around lines 258-262, 275-284, and 304-310), and use the copied env
map for both the engine and exec execution paths instead of modifying the
original v.Env directly.

In `@pkg/config/config_helpers.go`:
- Around line 448-456: The stderr replay logic in the replayOnce.Do block is
nested within the stdout writer condition, causing cached stderr to be lost when
stdout is discarded. Move the stderr replay logic (checking cachedEntry.Stderr
and pctx.Venv.Writers.ErrWriter) outside of the stdout writer condition so it
executes independently. Additionally, in the second location around lines
498-505, avoid writing stderr again to ErrWriter if it has already been streamed
by RunCommandWithOutput, as this causes duplicate output to the user.

In `@pkg/config/parsing_context.go`:
- Around line 39-45: The ParsingContext struct is exported from pkg/config and
the refactor removes the exported Env field while moving the source of truth to
Venv.Env, which breaks downstream code that constructs or reads
ParsingContext.Env. To maintain backward compatibility, retain the Env field on
the ParsingContext struct as a deprecated field and ensure it is synchronized
with Venv.Env by updating any methods that modify either field to keep them in
sync, or create getter and setter methods that bridge between ParsingContext.Env
and Venv.Env. Alternatively, if accepting this as a breaking API change,
document and communicate this clearly before merging.

---

Outside diff comments:
In `@internal/runner/run/options.go`:
- Around line 231-239: The remoteStateOpts method is not forwarding the log
configuration flags to the backend.Options struct being created. Add
LogShowAbsPaths and LogDisableErrorSummary fields to the backend.Options
initialization within the remoteStateOpts method, setting these fields from the
corresponding values in the Options receiver (o), similar to how other options
like IAMRoleOptions, NonInteractive, and FailIfBucketCreationRequired are
already being forwarded.

In `@pkg/config/dependency.go`:
- Around line 1470-1550: The WithWriter method is being applied redundantly to
pctx.Venv. At the beginning of the runTerragruntOutputJSON function, pctx.Venv
is already updated with stdoutBufferWriter via pctx.Venv =
pctx.Venv.WithWriter(stdoutBufferWriter). Since WithWriter returns a new Venv
without mutating the original, and pctx.Venv is not modified between that
assignment and line 1532, the subsequent call runV :=
pctx.Venv.WithWriter(stdoutBufferWriter) applies the writer assignment twice.
Remove the redundant WithWriter call on line 1532 and simply assign runV :=
pctx.Venv instead, since pctx.Venv already has the correct writer configured.

In `@pkg/options/options.go`:
- Around line 79-300: The TerragruntOptions struct is removing exported API
elements (Env, Writers fields, NewTerragruntOptionsWithWriters function, and
TerraformDataDir/DataDir method signatures) that downstream users may depend on,
which breaks backwards compatibility. Restore these removed exported fields and
functions as deprecated shims that maintain the original signatures and delegate
to the new internal implementations, and add deprecation warnings or comments to
guide users toward the new API. This preserves the public API surface for at
least one release while allowing the transition to the new structure.

---

Nitpick comments:
In `@internal/cli/commands/commands.go`:
- Around line 327-342: Replace the panic statement in the
setupAutoProviderCacheDir function with a proper error return. Instead of
panicking when v.Env is nil, return a formatted error (using fmt.Errorf or
similar) with a descriptive message about the nil environment map. This allows
the calling code in RunAction to handle the error gracefully through the
existing error-handling infrastructure rather than crashing the application.

In `@internal/cli/commands/hcl/validate/validate.go`:
- Around line 335-403: The runValidateInputs function has multiple nested
validation paths and conditional blocks that can be simplified. Extract the
unused variable detection loop into a helper function, extract the missing
variable detection loop into a separate helper function, create a helper
function for logging the validation results (handling both missing and unused
vars cases), and create a helper function for the error determination logic that
checks strict mode. Then refactor runValidateInputs to call these helpers in
sequence, reducing the overall cognitive complexity and improving readability
while maintaining the same behavior.

In `@internal/discovery/phase_graph.go`:
- Around line 66-171: The GraphPhase.Run method has excessive cognitive
complexity (29 vs the allowed 15). Extract the candidate partitioning logic that
separates input.Candidates into graphTargetCandidates and otherCandidates (the
switch statement on candidate.Reason) into a separate helper method.
Additionally, extract the graph-task scheduling and error aggregation logic that
handles the errgroup setup, iterates over graphExprs and matchingCandidates, and
collects errors (from the g.Go call through the final error handling) into
another separate helper method. This will reduce the complexity of the main Run
method while keeping the new threading logic reviewable and maintainable.

In `@internal/discovery/phase_parse.go`:
- Around line 159-238: The ParsePhase.Run method has cognitive complexity
exceeding the allowed threshold. Extract the logic that builds the
componentsToParse queue (the section filtering candidates and checking
discovery.readFiles, discovery.parseExclude, discovery.parseIncludes conditions)
into a separate private helper method that returns the queue of components to
parse. Additionally, extract the goroutine processing logic that handles the
switch statement for result.Status and error accumulation into another separate
private helper method that takes the parsed result and appends to results. This
will simplify the main Run method by reducing nested conditionals and improving
readability while preserving the venv propagation.

In `@internal/discovery/phase_worktree.go`:
- Around line 65-172: The WorktreePhase.Run method has excessive cognitive
complexity (43 > 15) due to nested logic for pair discovery, stack changes
discovery, and classification. Extract the discovery orchestration into a
separate helper method that handles setting up the error group, looping through
worktree pairs with discoverInWorktree calls, and invoking
discoverChangesInWorktreeStacks, returning the discoveredComponents collection.
Then extract the classification and result building logic into another helper
method that iterates through the discovered components, invokes
input.Classifier.Classify for each component, constructs the DiscoveryResult
objects, and applies the switch statement to add results. This separation will
reduce the Run method's complexity while preserving the venv threading behavior
via the passed context and logger parameters.

In `@internal/providercache/providercache.go`:
- Around line 505-580: The runTerraformCommand function exceeds the cognitive
complexity threshold (18 > 15) due to the complex error handling and retry logic
within the closure passed to util.DoWithRetry. Extract the entire closure body
that handles error writer setup, command execution, error classification
(httpStatusCacheProviderReg matching, timeout detection), and flushing
operations into a separate private helper function. This helper should take the
necessary parameters (ctx, logger, venv, TFOptions, CLI args) and return the
command output and error, then call this helper from within the util.DoWithRetry
closure to maintain behavior while reducing cognitive complexity.

In `@pkg/config/config_helpers_test.go`:
- Around line 639-642: The pctx.Venv initialization sets the Env field first on
the initial struct, then immediately reassigns the entire pctx.Venv via the
WithWriter and WithErrWriter method chain, which could overwrite the previously
set Env field if those methods don't preserve it. Reorder the initialization by
first assigning pctx.Venv with the complete
WithWriter(os.Stdout).WithErrWriter(os.Stderr) chain, then set pctx.Venv.Env on
the resulting venv instance after the method chain completes. This makes the
initialization order clearer and ensures the Env field is not overwritten.
🪄 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

Run ID: 99c263cb-2ece-49a6-bcf5-832ae18138bb

📥 Commits

Reviewing files that changed from the base of the PR and between ba429f0 and f89d0f7.

📒 Files selected for processing (139)
  • internal/cli/app.go
  • internal/cli/app_test.go
  • internal/cli/commands/aws-provider-patch/aws-provider-patch.go
  • internal/cli/commands/backend/bootstrap/bootstrap.go
  • internal/cli/commands/backend/bootstrap/cli.go
  • internal/cli/commands/backend/cli.go
  • internal/cli/commands/backend/delete/cli.go
  • internal/cli/commands/backend/delete/delete.go
  • internal/cli/commands/backend/migrate/migrate.go
  • internal/cli/commands/catalog/catalog.go
  • internal/cli/commands/catalog/tui/scaffold.go
  • internal/cli/commands/catalog/tui/welcome.go
  • internal/cli/commands/commands.go
  • internal/cli/commands/dag/cli.go
  • internal/cli/commands/dag/graph/cli.go
  • internal/cli/commands/dag/graph/cli_test.go
  • internal/cli/commands/exec/exec.go
  • internal/cli/commands/find/cli.go
  • internal/cli/commands/find/find.go
  • internal/cli/commands/find/find_test.go
  • internal/cli/commands/hcl/cli.go
  • internal/cli/commands/hcl/format/cli.go
  • internal/cli/commands/hcl/format/format.go
  • internal/cli/commands/hcl/format/format_bench_test.go
  • internal/cli/commands/hcl/format/format_test.go
  • internal/cli/commands/hcl/validate/validate.go
  • internal/cli/commands/info/print/print.go
  • internal/cli/commands/list/cli.go
  • internal/cli/commands/list/list.go
  • internal/cli/commands/list/list_test.go
  • internal/cli/commands/render/render.go
  • internal/cli/commands/render/render_test.go
  • internal/cli/commands/run/cli.go
  • internal/cli/commands/run/help.go
  • internal/cli/commands/run/run.go
  • internal/cli/commands/scaffold/scaffold.go
  • internal/cli/commands/stack/cli.go
  • internal/cli/commands/stack/stack.go
  • internal/cli/flags/global/flags.go
  • internal/configbridge/bridge.go
  • internal/discovery/benchmark_test.go
  • internal/discovery/constructor.go
  • internal/discovery/discovery.go
  • internal/discovery/discovery_integration_test.go
  • internal/discovery/discovery_test.go
  • internal/discovery/filter_test.go
  • internal/discovery/graph_target_test.go
  • internal/discovery/helpers.go
  • internal/discovery/options.go
  • internal/discovery/phase_filesystem.go
  • internal/discovery/phase_graph.go
  • internal/discovery/phase_parse.go
  • internal/discovery/phase_relationship.go
  • internal/discovery/phase_test.go
  • internal/discovery/phase_worktree.go
  • internal/discovery/phase_worktree_integration_test.go
  • internal/discovery/types.go
  • internal/engine/engine.go
  • internal/git/git.go
  • internal/prepare/prepare.go
  • internal/providercache/providercache.go
  • internal/remotestate/backend/backend.go
  • internal/remotestate/remote_state.go
  • internal/runner/common/unit_runner.go
  • internal/runner/graph/graph.go
  • internal/runner/run/creds/getter.go
  • internal/runner/run/creds/providers/amazonsts/provider.go
  • internal/runner/run/creds/providers/externalcmd/provider.go
  • internal/runner/run/creds/providers/externalcmd/provider_mem_test.go
  • internal/runner/run/creds/providers/externalcmd/provider_test.go
  • internal/runner/run/creds/providers/provider.go
  • internal/runner/run/debug.go
  • internal/runner/run/download_source.go
  • internal/runner/run/download_source_test.go
  • internal/runner/run/hook.go
  • internal/runner/run/hook_lifecycle_test.go
  • internal/runner/run/hook_test.go
  • internal/runner/run/options.go
  • internal/runner/run/prepare_internal_test.go
  • internal/runner/run/run.go
  • internal/runner/run/run_e2e_test.go
  • internal/runner/run/run_test.go
  • internal/runner/run/venv.go
  • internal/runner/run/version_check.go
  • internal/runner/run/version_check_mem_test.go
  • internal/runner/run/version_check_test.go
  • internal/runner/runall/runall.go
  • internal/runner/runner.go
  • internal/runner/runnerpool/builder.go
  • internal/runner/runnerpool/builder_helpers.go
  • internal/runner/runnerpool/runner.go
  • internal/runner/runnerpool/writer.go
  • internal/shell/git.go
  • internal/shell/git_mem_test.go
  • internal/shell/git_windows_test.go
  • internal/shell/run_cmd.go
  • internal/shell/run_cmd_mem_test.go
  • internal/shell/run_cmd_output_test.go
  • internal/shell/run_cmd_test.go
  • internal/shell/run_cmd_unix_test.go
  • internal/shell/run_cmd_windows_test.go
  • internal/stacks/generate/generate.go
  • internal/stacks/output/output.go
  • internal/strict/view/plaintext/render.go
  • internal/strict/view/plaintext/render_internal_test.go
  • internal/telemetry/meter_test.go
  • internal/telemetry/telemeter_test.go
  • internal/telemetry/tracer_test.go
  • internal/tf/context.go
  • internal/tf/run_cmd.go
  • internal/tf/run_cmd_test.go
  • internal/tflint/tflint.go
  • internal/tflint/tflint_test.go
  • internal/tflint/venv.go
  • internal/util/env.go
  • internal/venv/venv.go
  • internal/writer/writer.go
  • main.go
  • pkg/config/config.go
  • pkg/config/config_helpers.go
  • pkg/config/config_helpers_mem_test.go
  • pkg/config/config_helpers_test.go
  • pkg/config/dependency.go
  • pkg/config/dependency_test.go
  • pkg/config/early_stack_eval_test.go
  • pkg/config/fuzz_test.go
  • pkg/config/include.go
  • pkg/config/locals.go
  • pkg/config/parsing_context.go
  • pkg/config/sops_race_test.go
  • pkg/config/sops_test.go
  • pkg/config/stack.go
  • pkg/options/options.go
  • pkg/options/options_test.go
  • test/benchmarks/helpers/helpers.go
  • test/helpers/package.go
  • test/integration_aws_test.go
  • test/integration_debug_test.go
  • test/integration_gcp_test.go
💤 Files with no reviewable changes (2)
  • internal/util/env.go
  • internal/discovery/options.go

Comment on lines 203 to +205
provider := amazonsts.NewProvider(l, iamRoleOpts, nil)

creds, err := provider.GetCredentials(ctx, l, exec)
creds, err := provider.GetCredentials(ctx, l, v)

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pass v.Env into the nested STS provider.

amazonsts.Provider.GetCredentials ignores the venv.Venv argument and reads only the env map captured by amazonsts.NewProvider; constructing it with nil means awsRole responses from the external auth command can bypass the venv-backed environment. Use v.Env here so role assumption uses the same runtime env.

Proposed fix
-	provider := amazonsts.NewProvider(l, iamRoleOpts, nil)
+	provider := amazonsts.NewProvider(l, iamRoleOpts, v.Env)
 
 	creds, err := provider.GetCredentials(ctx, l, v)
🤖 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 `@internal/runner/run/creds/providers/externalcmd/provider.go` around lines 203
- 205, In the amazonsts.NewProvider call within the code block around the
provider initialization, replace the nil argument (third parameter) with v.Env
to ensure that the environment variables from the venv context are properly
passed to the STS provider for role assumption, allowing the external auth
command responses to use the correct runtime environment instead of bypassing
the venv-backed environment.

Comment thread internal/shell/run_cmd.go
Comment on lines 258 to 262
// Pass the traceparent to the child process if it is available in the context.
if traceParent := telemetry.TraceParentFromContext(ctx, runOpts.Telemetry); traceParent != "" {
l.Debugf("Setting trace parent=%q for command %s", traceParent, fmt.Sprintf("%s %v", cmdOpts.Command, cmdOpts.Args))
runOpts.Env[telemetry.TraceParentEnv] = traceParent
v.Env[telemetry.TraceParentEnv] = traceParent
}

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.

⚠️ Potential issue | 🔴 Critical | ⚡ Quick win

Copy the command env before injecting TRACEPARENT.

Line 261 mutates v.Env directly. Since Venv is copied by value but Env is a shared map, parallel commands can race/panic with concurrent map writes; a nil Env also panics when a trace parent is present. Build a per-command env map and pass that to both engine and exec paths.

🐛 Proposed fix
 	// Pass the traceparent to the child process if it is available in the context.
+	cmdEnv := v.Env
 	if traceParent := telemetry.TraceParentFromContext(ctx, runOpts.Telemetry); traceParent != "" {
 		l.Debugf("Setting trace parent=%q for command %s", traceParent, fmt.Sprintf("%s %v", cmdOpts.Command, cmdOpts.Args))
-		v.Env[telemetry.TraceParentEnv] = traceParent
+		cmdEnv = make(map[string]string, len(v.Env)+1)
+		for key, value := range v.Env {
+			cmdEnv[key] = value
+		}
+		cmdEnv[telemetry.TraceParentEnv] = traceParent
 	}
@@
-				Env:                    v.Env,
+				Env:                    cmdEnv,
@@
-		exec.WithEnv(v.Env),
+		exec.WithEnv(cmdEnv),

Also applies to: 275-284, 304-310

🤖 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 `@internal/shell/run_cmd.go` around lines 258 - 262, The code directly mutates
the shared Env map in v (at line 261 with v.Env[telemetry.TraceParentEnv] =
traceParent) which causes race conditions when parallel commands execute
concurrently and also panics if Env is nil. Create a per-command copy of the
environment map before injecting the TRACEPARENT value, ensuring to handle the
nil case by initializing an empty map when needed. Apply this same pattern to
all three locations where TraceParent is injected (around lines 258-262,
275-284, and 304-310), and use the copied env map for both the engine and exec
execution paths instead of modifying the original v.Env directly.

Comment on lines +448 to 456
if w := pctx.Venv.Writers.Writer; w != nil && w != io.Discard {
cachedEntry.replayOnce.Do(func() {
if !suppressOutput && cachedEntry.Stdout != "" {
_, _ = pctx.Writers.Writer.Write([]byte(cachedEntry.Stdout))
_, _ = w.Write([]byte(cachedEntry.Stdout))
}

if cachedEntry.Stderr != "" {
_, _ = pctx.Writers.ErrWriter.Write([]byte(cachedEntry.Stderr))
if cachedEntry.Stderr != "" && pctx.Venv.Writers.ErrWriter != nil {
_, _ = pctx.Venv.Writers.ErrWriter.Write([]byte(cachedEntry.Stderr))
}

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Handle stderr replay independently from stdout.

Lines 448-456 only replay cached stderr when stdout has a real writer, so stderr is lost when stdout is discarded. Lines 498-505 also write fresh stderr again even though RunCommandWithOutput already streamed stderr to ErrWriter; this can duplicate user output.

🐛 Proposed fix
-			if w := pctx.Venv.Writers.Writer; w != nil && w != io.Discard {
+			stdoutWriter := pctx.Venv.Writers.Writer
+			stderrWriter := pctx.Venv.Writers.ErrWriter
+			hasStdoutWriter := stdoutWriter != nil && stdoutWriter != io.Discard
+			hasStderrWriter := stderrWriter != nil && stderrWriter != io.Discard
+
+			if hasStdoutWriter || hasStderrWriter {
 				cachedEntry.replayOnce.Do(func() {
-					if !suppressOutput && cachedEntry.Stdout != "" {
-						_, _ = w.Write([]byte(cachedEntry.Stdout))
+					if hasStdoutWriter && !suppressOutput && cachedEntry.Stdout != "" {
+						_, _ = stdoutWriter.Write([]byte(cachedEntry.Stdout))
 					}
 
-					if cachedEntry.Stderr != "" && pctx.Venv.Writers.ErrWriter != nil {
-						_, _ = pctx.Venv.Writers.ErrWriter.Write([]byte(cachedEntry.Stderr))
+					if hasStderrWriter && cachedEntry.Stderr != "" {
+						_, _ = stderrWriter.Write([]byte(cachedEntry.Stderr))
 					}
 				})
 			}
@@
-	if w := pctx.Venv.Writers.Writer; w != nil && w != io.Discard {
+	stdoutWriter := pctx.Venv.Writers.Writer
+	stderrWriter := pctx.Venv.Writers.ErrWriter
+	hasStdoutWriter := stdoutWriter != nil && stdoutWriter != io.Discard
+	hasStderrWriter := stderrWriter != nil && stderrWriter != io.Discard
+
+	if hasStdoutWriter || hasStderrWriter {
 		entry.replayOnce.Do(func() {
-			if !suppressOutput && entry.Stdout != "" {
-				_, _ = w.Write([]byte(entry.Stdout))
-			}
-
-			if entry.Stderr != "" && pctx.Venv.Writers.ErrWriter != nil {
-				_, _ = pctx.Venv.Writers.ErrWriter.Write([]byte(entry.Stderr))
+			if hasStdoutWriter && !suppressOutput && entry.Stdout != "" {
+				_, _ = stdoutWriter.Write([]byte(entry.Stdout))
 			}
+			// Stderr was already streamed by RunCommandWithOutput on this fresh execution.
 		})
 	}

Also applies to: 498-505

🤖 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 `@pkg/config/config_helpers.go` around lines 448 - 456, The stderr replay logic
in the replayOnce.Do block is nested within the stdout writer condition, causing
cached stderr to be lost when stdout is discarded. Move the stderr replay logic
(checking cachedEntry.Stderr and pctx.Venv.Writers.ErrWriter) outside of the
stdout writer condition so it executes independently. Additionally, in the
second location around lines 498-505, avoid writing stderr again to ErrWriter if
it has already been streamed by RunCommandWithOutput, as this causes duplicate
output to the user.

Comment on lines 39 to 45
type ParsingContext struct {
Writers writer.Writers

// Venv is the virtualized environment used by HCL helper functions
// that shell out (e.g. get_repo_root) or evaluate dependency outputs.
// It also carries the shell environment and stdout/stderr writers.
// Defaults to the OS-backed environment when [NewParsingContext] is
// called; callers with a threaded root Venv set it before parsing.
Venv venv.Venv

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.

⚠️ Potential issue | 🟠 Major | 🏗️ Heavy lift

Keep a compatibility bridge for the removed ParsingContext.Env field.

ParsingContext is exported from pkg/config, and this refactor removes the exported Env map[string]string field while moving the source of truth to Venv.Env. That breaks downstream code constructing or reading ParsingContext.Env, despite the PR claiming backward compatibility. Please either retain a deprecated Env bridge that is normalized into Venv.Env, or mark this as a breaking API change before merge.

Also applies to: 134-141

🤖 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 `@pkg/config/parsing_context.go` around lines 39 - 45, The ParsingContext
struct is exported from pkg/config and the refactor removes the exported Env
field while moving the source of truth to Venv.Env, which breaks downstream code
that constructs or reads ParsingContext.Env. To maintain backward compatibility,
retain the Env field on the ParsingContext struct as a deprecated field and
ensure it is synchronized with Venv.Env by updating any methods that modify
either field to keep them in sync, or create getter and setter methods that
bridge between ParsingContext.Env and Venv.Env. Alternatively, if accepting this
as a breaking API change, document and communicate this clearly before merging.

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.

1 participant