Skip to content

feat: Add .env file support (DEV-2990)#1930

Open
osterman wants to merge 10 commits intomainfrom
feature/dev-2990-add-support-for-env-files
Open

feat: Add .env file support (DEV-2990)#1930
osterman wants to merge 10 commits intomainfrom
feature/dev-2990-add-support-for-env-files

Conversation

@osterman
Copy link
Copy Markdown
Member

@osterman osterman commented Jan 4, 2026

Summary

This PR implements comprehensive .env file support for Atmos, allowing users to load environment variables from .env files throughout the configuration and execution lifecycle.

Key Features

  • Structured and flat configuration forms: Support both env.vars + env.files (structured) and legacy env: {KEY: value} (flat) formats
  • Glob pattern support: Load multiple .env files using patterns like .env.*, .env.local, etc.
  • Parent directory walking: Optionally walk up directory tree toward repo root, with security enforcement (never beyond repo root)
  • Early .env loading: Load files before profile detection to support ATMOS_* environment variables (e.g., ATMOS_PROFILE, ATMOS_LOGS_LEVEL)
  • Case preservation: Maintains original case of environment variable names through Atmos's casemap infrastructure
  • Centralized integration: Single-point change in shell execution utilities provides .env support to all commands (terraform, helmfile, packer, workflows, custom commands)
  • UI feedback: Reports loaded files via ui.Success() for visibility

Configuration Example

New structured form (recommended):
```yaml
env:
vars:
AWS_PROFILE: dev
files:
enabled: true
paths:
- .env
- .env.local
- .env.*
parents: false
```

Legacy flat form (still supported):
```yaml
env:
AWS_PROFILE: dev
```

Implementation Details

  • New package: pkg/env/ with file.go and comprehensive test coverage
  • Schema updates: Added EnvFilesConfig and EnvConfig structs to pkg/schema/schema.go
  • Config loading: Added dual-form YAML parsing and early .env loading in pkg/config/load.go
  • Shell execution: Modified ExecuteShellCommand() and ExecuteShell() in internal/exec/shell_utils.go to support working directory .env files
  • Backward compatibility: Flat env: form continues to work without changes

Test Coverage

  • 21 unit tests in pkg/env/file_test.go covering glob patterns, case preservation, parent walking, and security boundaries
  • Updated existing tests in pkg/schema/schema_test.go to support new EnvConfig structure
  • All tests passing

Files Changed

  • pkg/schema/schema.go - New EnvFilesConfig and EnvConfig structs, GetCaseSensitiveEnvVars() helper
  • pkg/env/file.go - Core env file loading implementation with glob and parent walking
  • pkg/env/file_test.go - Comprehensive unit tests
  • pkg/config/load.go - Dual-form parsing, early .env loading, casemap integration
  • internal/exec/shell_utils.go - Working directory .env file loading
  • Various command files - Updated to use new env helpers

Related Issues

Closes DEV-2990

🤖 Generated with Claude Code

Co-Authored-By: Claude Haiku 4.5 noreply@anthropic.com

Summary by CodeRabbit

  • New Features

    • Load .env files from working and parent directories with deterministic merge/override behavior; preserve case sensitivity for environment variables; support structured env config (vars + files); early loading during config init with UI feedback.
  • Documentation

    • New docs, example config, README and blog post explaining .env usage, patterns, precedence, and examples.
  • Tests

    • Comprehensive tests for env loading, merging, directory traversal and schema handling.
  • Chores

    • Brought godotenv dependency to v1.5.1.

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

Add support for loading .env files into the environment with the following features:
- New structured env configuration (env.vars + env.files)
- Backward-compatible flat form (env: {KEY: value})
- Glob pattern support for .env files (e.g., .env.*)
- Optional parent directory walking with security bounds
- Early .env loading to support ATMOS_* vars influencing Atmos behavior
- Working directory .env loading for component-specific environments
- Case preservation for environment variable names
- UI feedback when files are loaded

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
@osterman osterman requested a review from a team as a code owner January 4, 2026 15:59
@github-actions github-actions bot added the size/l Large size PR label Jan 4, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Jan 4, 2026

Dependency Review

✅ No vulnerabilities or license issues found.

Scanned Files

None

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Jan 4, 2026

📝 Walkthrough

Walkthrough

Adds structured env configuration and .env file support: new pkg/env loader, early .env injection during config load, EnvConfig schema (vars + files), and propagates case-sensitive env merging and per-directory .env merging into shell/terraform execution paths.

Changes

Cohort / File(s) Summary
Env file utilities
pkg/env/file.go, pkg/env/file_test.go
New package to discover, load, and merge .env files (glob patterns, parent traversal, deterministic order) plus merge helpers and tests.
Configuration schema
pkg/schema/schema.go, pkg/schema/schema_test.go
Replaced AtmosConfiguration.Env with EnvConfig (Vars + Files), added EnvFilesConfig, and GetCaseSensitiveEnvVars() accessor.
Config loading
pkg/config/load.go
Added early .env file loading (loadEnvFilesEarly) and parseEnvConfig to support structured (vars+files) and legacy flat env forms; preserves case-sensitive paths.
Shell execution / exec helpers
internal/exec/shell_utils.go, internal/exec/shell_utils_test.go, internal/exec/stack_processor_process_stacks.go, internal/exec/workflow_utils.go, internal/exec/workflow_adapters.go
Threaded atmosConfig into ExecuteShell, switched env merges to GetCaseSensitiveEnvVars(), and integrated per-directory .env loading/merging into shell and terraform execution flows; tests updated to pass nil where appropriate.
CLI wiring
cmd/cmd_utils.go, cmd/auth_exec.go
Updated calls to new ExecuteShell signature to pass atmosConfig (or nil).
Dependency
go.mod
Promoted github.com/joho/godotenv v1.5.1 to a direct dependency.
Examples & docs
examples/dotenv/*, website/blog/2025-01-15-dotenv-file-support.mdx, website/docs/cli/configuration/env.mdx, website/plugins/file-browser/index.js, website/src/data/roadmap.js
Added dotenv example files, example atmos.yaml, example README, docs and blog post describing .env support, and UI/file-browser mappings.
Terraform env check
internal/exec/terraform.go
Replaced direct atmosConfig.Env access with GetCaseSensitiveEnvVars() for TF_PLUGIN_CACHE_DIR lookup.

Sequence Diagram(s)

sequenceDiagram
    autonumber
    actor User
    participant ConfigLoader as Config Loader
    participant EnvLoader as Env File Loader
    participant ShellExec as Shell Executor
    participant OS as OS Process

    User->>ConfigLoader: Load atmos.yaml
    ConfigLoader->>EnvLoader: loadEnvFilesEarly(env.files)
    EnvLoader->>OS: Inject global .env vars into process env
    ConfigLoader->>ConfigLoader: parseEnvConfig -> AtmosConfiguration.Env (EnvConfig)

    User->>ShellExec: Run command (component/workflow)
    ShellExec->>EnvLoader: LoadFromDirectory(component dir, patterns, parents)
    EnvLoader->>ShellExec: Return merged component .env vars + file list
    ShellExec->>ShellExec: Merge OS env + global env (GetCaseSensitiveEnvVars) + component env
    ShellExec->>OS: Execute command with final environment
    OS-->>User: Command output
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • aknysh
  • gberenice
🚥 Pre-merge checks | ✅ 4 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 73.33% 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
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed Title clearly identifies the main change: adding .env file support, which is the primary feature across all modified files.
Linked Issues check ✅ Passed PR implements all coding requirements from DEV-2990: .env loading via godotenv, configurable via atmos.yaml with env.files settings, supports glob patterns and parent walking, case-sensitive handling, and conditional loading.
Out of Scope Changes check ✅ Passed All changes directly support .env file loading: schema updates for EnvConfig/EnvFilesConfig, env file utilities, shell execution integration, configuration parsing, and documentation/examples. No unrelated modifications detected.

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

✨ 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 feature/dev-2990-add-support-for-env-files

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.

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 (7)
pkg/env/file.go (2)

104-108: Consider removing redundant perf tracking.

MapToSlice delegates to ConvertMapToSlice, which already has its own perf.Track. This creates double tracking overhead for the same operation.

🔎 Proposed fix
 func MapToSlice(m map[string]string) []string {
-	defer perf.Track(nil, "env.MapToSlice")()
-
 	return ConvertMapToSlice(m)
 }

120-126: Silent skip of invalid glob patterns may hide configuration issues.

When a pattern has invalid syntax (e.g., [invalid), it's silently skipped. Users won't know their pattern is malformed. Consider logging a warning.

🔎 Proposed enhancement
 for _, pattern := range patterns {
 	fullPattern := filepath.Join(dir, pattern)
 	matches, err := filepath.Glob(fullPattern)
 	if err != nil {
-		// Invalid pattern - skip it but don't fail.
+		// Invalid pattern syntax - log warning but don't fail.
+		log.Debug("Invalid glob pattern", "pattern", pattern, "error", err)
 		continue
 	}

This would require importing the log package.

pkg/schema/schema_test.go (1)

145-186: GetCaseSensitiveMap tests align with new EnvConfig semantics

The new subtests exercise EnvConfig.Vars both with and without CaseMaps and cover the nil Vars case; this matches the updated GetCaseSensitiveMap / GetCaseSensitiveEnvVars behavior and preserves previous expectations for the "env" path.

If you want to harden coverage further, consider adding a small subtest that calls GetCaseSensitiveMap("env.vars") explicitly to lock in the structured-path behavior, but the current suite is already functionally solid.

cmd/cmd_utils.go (1)

551-612: Custom‑command env is wired correctly, but we now double‑merge global env

Using atmosConfig.GetCaseSensitiveEnvVars() when seeding env and switching ExecuteShell to &atmosConfig keeps custom commands aligned with the new env handling (including case preservation and .env behavior).

Right now env already includes os.Environ() + global env, and ExecuteShell repeats that merge internally before applying envVars, so the same keys get processed twice. It’s harmless but a bit redundant. Longer‑term, it may be cleaner to treat env here as “overrides only” (commandConfig/env + auth) and let ExecuteShell own the system/global/.env merge.

pkg/schema/schema.go (1)

53-72: EnvConfig / EnvFilesConfig and case‑sensitive env access look coherent

The new EnvFilesConfig + EnvConfig structs, the switch to AtmosConfiguration.Env EnvConfig, and the updated GetCaseSensitiveMap/GetCaseSensitiveEnvVars all line up cleanly:

  • Dual form is supported (flat env vs structured env.vars/env.files) without fighting mapstructure.
  • Case preservation for env vars is centralized via GetCaseSensitiveEnvVars() and the extended caseSensitivePaths ("env", "env.vars"), which matches how workflows, shell exec, and auth shells are now consuming env.
  • Returning the raw Vars map when CaseMaps is nil preserves previous behavior.

Only minor thought: if you ever care about distinguishing “unset” vs “empty map” when CaseMaps is present, you might add an early if source == nil { return nil } before calling ApplyCase, but as it stands this is fully workable.

Also applies to: 113-114, 243-270, 1322-1323

pkg/config/load.go (1)

21-29: Early .env support and dual‑form env parsing are wired correctly

Nice job threading the new env behavior through config loading:

  • parseEnvConfig cleanly supports both legacy flat env and structured env.vars/env.files without reintroducing the old mapstructure collision with Command.Env.
  • loadEnvFilesEarly runs after setEnv(v) but before profile resolution, uses env.files.enabled/env.files.paths, and injects only missing vars into os.Environ, so ATMOS_* in .env can influence profile selection and other config without clobbering explicit env.
  • UI feedback via ui.Success("Loaded …") is consistent with the later working‑dir .env loading path.
  • Extending caseSensitivePaths with "env" and "env.vars" completes the link so GetCaseSensitiveEnvVars() can rely on casemap for both flat and structured forms.

One behavioral nuance to keep in mind (likely acceptable): early .env loading will honor env.files.* from the base config, not from profiles, since profiles are applied later. If you ever need profile‑specific .env behavior at bootstrap time, that would require a follow‑up design, but this implementation is internally consistent.

Also applies to: 218-237, 280-284, 1237-1317, 1321-1325

internal/exec/shell_utils.go (1)

18-26: Consider merging command env with MergeEnvSlices to avoid duplicate keys

The new layering in ExecuteShellCommand—system env → global env → working‑dir .env → command env—is the right shape, and using GetCaseSensitiveEnvVars() keeps it aligned with the dual‑form env config.

Right now the final step is:

cmdEnv = append(cmdEnv, env...)

Since env is already a full environment slice (from prepareStepEnvironment), this can introduce duplicate KEY=value entries and leaves which value “wins” up to the OS/env implementation. To make precedence explicit and avoid duplicates, you could merge the slices instead:

cmdEnv = envpkg.MergeEnvSlices(cmdEnv, env)

That keeps “command/env manager output” as the clear highest‑priority layer while still honoring global + .env values.

Also applies to: 51-76

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between def7a4b and 268f81f.

📒 Files selected for processing (13)
  • cmd/auth_exec.go
  • cmd/cmd_utils.go
  • go.mod
  • internal/exec/shell_utils.go
  • internal/exec/shell_utils_test.go
  • internal/exec/stack_processor_process_stacks.go
  • internal/exec/workflow_adapters.go
  • internal/exec/workflow_utils.go
  • pkg/config/load.go
  • pkg/env/file.go
  • pkg/env/file_test.go
  • pkg/schema/schema.go
  • pkg/schema/schema_test.go
🧰 Additional context used
📓 Path-based instructions (5)
cmd/**/*.go

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

cmd/**/*.go: Use Cobra's recommended command structure with a root command and subcommands, implementing each command in a separate file under cmd/ directory
Provide comprehensive help text for all commands and flags, include examples in command help, and follow Go's documentation conventions in Cobra command definitions
Provide meaningful feedback to users and include progress indicators for long-running operations in CLI commands

Files:

  • cmd/auth_exec.go
  • cmd/cmd_utils.go
**/*.go

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

**/*.go: Use Viper for managing configuration, environment variables, and flags in CLI commands
Use interfaces for external dependencies to facilitate mocking and consider using testify/mock for creating mock implementations
All code must pass golangci-lint checks
Follow Go's error handling idioms: use meaningful error messages, wrap errors with context using fmt.Errorf("context: %w", err), and consider using custom error types for domain-specific errors
Follow standard Go coding style: use gofmt and goimports to format code, prefer short descriptive variable names, use kebab-case for command-line flags, and snake_case for environment variables
Document all exported functions, types, and methods following Go's documentation conventions
Document complex logic with inline comments in Go code
Support configuration via files, environment variables, and flags following the precedence order: flags > environment variables > config file > defaults
Provide clear error messages to users, include troubleshooting hints when appropriate, and log detailed errors for debugging

**/*.go: All comments must end with periods (enforced by godot linter) in Go code
Organize imports into three groups separated by blank lines, sorted alphabetically: Go stdlib, 3rd-party (NOT cloudposse/atmos), then Atmos packages with maintained aliases (cfg, log, u, errUtils)
All errors MUST be wrapped using static errors defined in errors/errors.go - use errors.Join for combining errors, fmt.Errorf with %w for context, and errors.Is() for error checking
Never manually create mocks - use go.uber.org/mock/mockgen with //go:generate directives in Go code
Keep files small and focused - under 600 lines with one cmd/impl per file, co-locate tests, never use //revive:disable:file-length-limit
Use colors from pkg/ui/theme/colors.go for all UI theming in Go code
Code must be compatible with Linux, macOS, and Windows - use SDKs over binaries, use filepath.Join() instead of h...

Files:

  • cmd/auth_exec.go
  • internal/exec/stack_processor_process_stacks.go
  • pkg/schema/schema_test.go
  • pkg/env/file_test.go
  • pkg/schema/schema.go
  • internal/exec/workflow_adapters.go
  • internal/exec/shell_utils_test.go
  • cmd/cmd_utils.go
  • pkg/env/file.go
  • internal/exec/shell_utils.go
  • pkg/config/load.go
  • internal/exec/workflow_utils.go
**/{pkg,internal,cmd}/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Add defer perf.Track(atmosConfig, "pkg.FuncName")() plus blank line to all public functions, using nil if no atmosConfig param - exceptions: trivial getters/setters, command constructors, simple factories, functions delegating to tracked functions

Files:

  • cmd/auth_exec.go
  • internal/exec/stack_processor_process_stacks.go
  • pkg/schema/schema_test.go
  • pkg/env/file_test.go
  • pkg/schema/schema.go
  • internal/exec/workflow_adapters.go
  • internal/exec/shell_utils_test.go
  • cmd/cmd_utils.go
  • pkg/env/file.go
  • internal/exec/shell_utils.go
  • pkg/config/load.go
  • internal/exec/workflow_utils.go
**/*_test.go

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

**/*_test.go: Every new feature must include comprehensive unit tests targeting >80% code coverage for all packages
Use table-driven tests for testing multiple scenarios in Go
Include integration tests for command flows and test CLI end-to-end when possible with test fixtures

Prefer unit tests with mocks over integration tests - use interfaces and dependency injection for testability, generate mocks with go.uber.org/mock/mockgen, use table-driven tests, target >80% coverage

Files:

  • pkg/schema/schema_test.go
  • pkg/env/file_test.go
  • internal/exec/shell_utils_test.go
{go.mod,go.sum}

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

Manage dependencies with Go modules and keep dependencies up to date while minimizing external dependencies

Files:

  • go.mod
🧠 Learnings (64)
📓 Common learnings
Learnt from: osterman
Repo: cloudposse/atmos PR: 1697
File: internal/exec/oci_utils.go:0-0
Timestamp: 2025-11-08T19:56:18.660Z
Learning: In the Atmos codebase, when a function receives an `*schema.AtmosConfiguration` parameter, it should read configuration values from `atmosConfig.Settings` fields rather than using direct `os.Getenv()` or `viper.GetString()` calls. The Atmos pattern is: viper.BindEnv in cmd/root.go binds environment variables → Viper unmarshals into atmosConfig.Settings via mapstructure → business logic reads from the Settings struct. This provides centralized config management, respects precedence, and enables testability. Example: `atmosConfig.Settings.AtmosGithubToken` instead of `os.Getenv("ATMOS_GITHUB_TOKEN")` in functions like `getGHCRAuth` in internal/exec/oci_utils.go.
Learnt from: aknysh
Repo: cloudposse/atmos PR: 0
File: :0-0
Timestamp: 2025-01-19T22:30:27.600Z
Learning: The Atmos YAML function `!env` is used to retrieve environment variables and assign them to sections in stack manifests. It supports both simple types (string, number, boolean) and complex types (JSON-encoded lists, maps, objects).
Learnt from: Listener430
Repo: cloudposse/atmos PR: 934
File: tests/fixtures/scenarios/docs-generate/README.md.gotmpl:99-118
Timestamp: 2025-01-25T03:51:57.689Z
Learning: For the cloudposse/atmos repository, changes to template contents should be handled in dedicated PRs and are typically considered out of scope for PRs focused on other objectives.
📚 Learning: 2025-12-13T06:10:25.156Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: internal/exec/workflow_utils.go:0-0
Timestamp: 2025-12-13T06:10:25.156Z
Learning: Atmos workflows: In internal/exec/workflow_utils.go ExecuteWorkflow, non-identity steps intentionally use baseWorkflowEnv, which is constructed from the parent environment with PATH modifications for the toolchain. Avoid appending os.Environ() again; prefer documenting this behavior and testing that standard environment variables are preserved.

Applied to files:

  • cmd/auth_exec.go
  • internal/exec/stack_processor_process_stacks.go
  • pkg/env/file_test.go
  • internal/exec/workflow_adapters.go
  • internal/exec/shell_utils_test.go
  • cmd/cmd_utils.go
  • go.mod
  • pkg/env/file.go
  • internal/exec/shell_utils.go
  • internal/exec/workflow_utils.go
📚 Learning: 2025-08-29T20:57:35.423Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1433
File: cmd/theme_list.go:33-36
Timestamp: 2025-08-29T20:57:35.423Z
Learning: In the Atmos codebase, avoid using viper.SetEnvPrefix("ATMOS") with viper.AutomaticEnv() because canonical environment variable names are not exclusive to Atmos and could cause conflicts. Instead, use selective environment variable binding through the setEnv function in pkg/config/load.go with bindEnv(v, "config.key", "ENV_VAR_NAME") for specific environment variables.

Applied to files:

  • cmd/auth_exec.go
  • internal/exec/stack_processor_process_stacks.go
  • pkg/schema/schema_test.go
  • pkg/schema/schema.go
  • cmd/cmd_utils.go
  • pkg/env/file.go
  • internal/exec/shell_utils.go
  • pkg/config/load.go
  • internal/exec/workflow_utils.go
📚 Learning: 2025-11-08T19:56:18.660Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1697
File: internal/exec/oci_utils.go:0-0
Timestamp: 2025-11-08T19:56:18.660Z
Learning: In the Atmos codebase, when a function receives an `*schema.AtmosConfiguration` parameter, it should read configuration values from `atmosConfig.Settings` fields rather than using direct `os.Getenv()` or `viper.GetString()` calls. The Atmos pattern is: viper.BindEnv in cmd/root.go binds environment variables → Viper unmarshals into atmosConfig.Settings via mapstructure → business logic reads from the Settings struct. This provides centralized config management, respects precedence, and enables testability. Example: `atmosConfig.Settings.AtmosGithubToken` instead of `os.Getenv("ATMOS_GITHUB_TOKEN")` in functions like `getGHCRAuth` in internal/exec/oci_utils.go.

Applied to files:

  • cmd/auth_exec.go
  • internal/exec/stack_processor_process_stacks.go
  • pkg/schema/schema_test.go
  • pkg/schema/schema.go
  • internal/exec/workflow_adapters.go
  • internal/exec/shell_utils_test.go
  • cmd/cmd_utils.go
  • pkg/env/file.go
  • internal/exec/shell_utils.go
  • pkg/config/load.go
  • internal/exec/workflow_utils.go
📚 Learning: 2026-01-04T00:55:21.698Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.698Z
Learning: Applies to **/*.go : Use `viper.BindEnv("ATMOS_VAR", "ATMOS_VAR", "FALLBACK")` for environment variables - ATMOS_ prefix required in Go code

Applied to files:

  • cmd/auth_exec.go
  • internal/exec/stack_processor_process_stacks.go
  • pkg/schema/schema_test.go
  • pkg/schema/schema.go
  • internal/exec/shell_utils_test.go
  • cmd/cmd_utils.go
  • go.mod
  • pkg/env/file.go
  • internal/exec/shell_utils.go
  • pkg/config/load.go
📚 Learning: 2025-04-23T15:02:50.246Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1202
File: pkg/utils/yaml_func_exec.go:104-104
Timestamp: 2025-04-23T15:02:50.246Z
Learning: In the Atmos codebase, direct calls to `os.Getenv` should be avoided. Instead, use `viper.BindEnv` for environment variable access. This provides a consistent approach to configuration management across the codebase.

Applied to files:

  • cmd/auth_exec.go
  • internal/exec/stack_processor_process_stacks.go
  • pkg/schema/schema_test.go
  • pkg/schema/schema.go
  • cmd/cmd_utils.go
  • go.mod
  • pkg/env/file.go
  • internal/exec/shell_utils.go
  • pkg/config/load.go
📚 Learning: 2025-11-11T03:47:59.576Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/which_test.go:166-223
Timestamp: 2025-11-11T03:47:59.576Z
Learning: In the cloudposse/atmos repo, tests that manipulate environment variables should use testing.T.Setenv for automatic setup/teardown instead of os.Setenv/Unsetenv.

Applied to files:

  • cmd/auth_exec.go
  • pkg/schema/schema_test.go
  • pkg/env/file_test.go
  • internal/exec/shell_utils_test.go
  • cmd/cmd_utils.go
  • pkg/env/file.go
  • internal/exec/shell_utils.go
  • pkg/config/load.go
  • internal/exec/workflow_utils.go
📚 Learning: 2024-10-23T21:36:40.262Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 740
File: cmd/cmd_utils.go:340-359
Timestamp: 2024-10-23T21:36:40.262Z
Learning: In the Go codebase for Atmos, when reviewing functions like `checkAtmosConfig` in `cmd/cmd_utils.go`, avoid suggesting refactoring to return errors instead of calling `os.Exit` if such changes would significantly increase the scope due to the need to update multiple call sites.

Applied to files:

  • cmd/auth_exec.go
  • internal/exec/stack_processor_process_stacks.go
  • pkg/schema/schema_test.go
  • internal/exec/workflow_adapters.go
  • internal/exec/shell_utils_test.go
  • cmd/cmd_utils.go
  • internal/exec/shell_utils.go
  • pkg/config/load.go
  • internal/exec/workflow_utils.go
📚 Learning: 2025-11-11T03:47:45.878Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/add_test.go:67-77
Timestamp: 2025-11-11T03:47:45.878Z
Learning: In the cloudposse/atmos codebase, tests should prefer t.Setenv for environment variable setup/teardown instead of os.Setenv/Unsetenv to ensure test-scoped isolation.

Applied to files:

  • cmd/auth_exec.go
  • internal/exec/stack_processor_process_stacks.go
  • pkg/schema/schema_test.go
  • pkg/env/file_test.go
  • internal/exec/shell_utils_test.go
  • cmd/cmd_utils.go
  • pkg/env/file.go
  • internal/exec/shell_utils.go
📚 Learning: 2025-09-29T15:47:10.908Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1540
File: internal/exec/terraform_cli_args_utils.go:64-73
Timestamp: 2025-09-29T15:47:10.908Z
Learning: In the Atmos codebase, viper.BindEnv is required for CLI commands in the cmd/ package, but internal utilities can use os.Getenv directly when parsing environment variables for business logic purposes. The requirement to use viper is specific to the CLI interface layer, not all environment variable access throughout the codebase.

Applied to files:

  • cmd/auth_exec.go
  • pkg/config/load.go
📚 Learning: 2024-12-07T16:16:13.038Z
Learnt from: Listener430
Repo: cloudposse/atmos PR: 825
File: internal/exec/helmfile_generate_varfile.go:28-31
Timestamp: 2024-12-07T16:16:13.038Z
Learning: In `internal/exec/helmfile_generate_varfile.go`, the `--help` command (`./atmos helmfile generate varfile --help`) works correctly without requiring stack configurations, and the only change needed was to make `ProcessCommandLineArgs` exportable by capitalizing its name.

Applied to files:

  • cmd/auth_exec.go
  • internal/exec/stack_processor_process_stacks.go
  • internal/exec/workflow_adapters.go
  • internal/exec/shell_utils_test.go
  • cmd/cmd_utils.go
  • internal/exec/shell_utils.go
  • internal/exec/workflow_utils.go
📚 Learning: 2025-09-07T18:07:00.549Z
Learnt from: Benbentwo
Repo: cloudposse/atmos PR: 1452
File: cmd/auth_login.go:43-44
Timestamp: 2025-09-07T18:07:00.549Z
Learning: In the atmos project, the identity flag is defined as a persistent flag on the auth root command (cmd/auth.go), making it available to all auth subcommands without needing to be redefined in each individual subcommand.

Applied to files:

  • cmd/auth_exec.go
📚 Learning: 2024-12-11T18:40:12.808Z
Learnt from: Listener430
Repo: cloudposse/atmos PR: 844
File: cmd/helmfile.go:37-37
Timestamp: 2024-12-11T18:40:12.808Z
Learning: In the atmos project, `cliConfig` is initialized within the `cmd` package in `root.go` and can be used in other command files.

Applied to files:

  • cmd/auth_exec.go
  • pkg/schema/schema.go
  • cmd/cmd_utils.go
  • internal/exec/shell_utils.go
  • pkg/config/load.go
📚 Learning: 2025-12-13T04:37:25.223Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: cmd/root.go:0-0
Timestamp: 2025-12-13T04:37:25.223Z
Learning: In Atmos cmd/root.go Execute(), after cfg.InitCliConfig, we must call both toolchainCmd.SetAtmosConfig(&atmosConfig) and toolchain.SetAtmosConfig(&atmosConfig) so the CLI wrapper and the toolchain package receive configuration; missing either can cause nil-pointer panics in toolchain path resolution.

Applied to files:

  • cmd/auth_exec.go
  • internal/exec/stack_processor_process_stacks.go
  • internal/exec/workflow_adapters.go
  • internal/exec/shell_utils_test.go
  • cmd/cmd_utils.go
  • internal/exec/shell_utils.go
  • pkg/config/load.go
  • internal/exec/workflow_utils.go
📚 Learning: 2025-08-15T14:43:41.030Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1352
File: pkg/store/artifactory_store_test.go:108-113
Timestamp: 2025-08-15T14:43:41.030Z
Learning: In test files for the atmos project, it's acceptable to ignore errors from os.Setenv/Unsetenv operations during test environment setup and teardown, as these are controlled test scenarios.

Applied to files:

  • cmd/auth_exec.go
  • pkg/schema/schema_test.go
  • pkg/env/file_test.go
  • internal/exec/shell_utils_test.go
  • pkg/env/file.go
  • internal/exec/shell_utils.go
📚 Learning: 2025-12-13T04:37:40.435Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: cmd/toolchain/get.go:23-40
Timestamp: 2025-12-13T04:37:40.435Z
Learning: In Go CLI command files using Cobra, constrain the subcommand to accept at most one positional argument (MaximumNArgs(1)) so it supports both listing all items (zero args) and fetching a specific item (one arg). Define and parse flags with a standard parser (e.g., flags.NewStandardParser()) and avoid binding flags to Viper (no viper.BindEnv/BindPFlag). This promotes explicit argument handling and predictable flag behavior across command files.

Applied to files:

  • cmd/auth_exec.go
  • cmd/cmd_utils.go
📚 Learning: 2025-12-21T04:10:29.030Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1891
File: internal/exec/describe_affected.go:468-468
Timestamp: 2025-12-21T04:10:29.030Z
Learning: In Go, package-level declarations (constants, variables, types, and functions) are visible to all files in the same package without imports. During reviews in cloudposse/atmos (and similar Go codebases), before suggesting to declare a new identifier, first check if it already exists in another file of the same package. If it exists, you can avoid adding a new declaration; if not, proceed with a proper package-level declaration. 

Applied to files:

  • cmd/auth_exec.go
  • internal/exec/stack_processor_process_stacks.go
  • pkg/schema/schema_test.go
  • pkg/env/file_test.go
  • pkg/schema/schema.go
  • internal/exec/workflow_adapters.go
  • internal/exec/shell_utils_test.go
  • cmd/cmd_utils.go
  • pkg/env/file.go
  • internal/exec/shell_utils.go
  • pkg/config/load.go
  • internal/exec/workflow_utils.go
📚 Learning: 2024-11-13T21:37:07.852Z
Learnt from: Cerebrovinny
Repo: cloudposse/atmos PR: 764
File: internal/exec/describe_stacks.go:289-295
Timestamp: 2024-11-13T21:37:07.852Z
Learning: In the `internal/exec/describe_stacks.go` file of the `atmos` project written in Go, avoid extracting the stack name handling logic into a helper function within the `ExecuteDescribeStacks` method, even if the logic appears duplicated.

Applied to files:

  • internal/exec/stack_processor_process_stacks.go
📚 Learning: 2025-12-13T03:21:35.786Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1813
File: cmd/terraform/shell.go:28-73
Timestamp: 2025-12-13T03:21:35.786Z
Learning: In Atmos, when calling cfg.InitCliConfig, you must first populate the schema.ConfigAndStacksInfo struct with global flag values using flags.ParseGlobalFlags(cmd, v) rather than passing an empty struct. The LoadConfig function (pkg/config/load.go) reads config selection fields (AtmosConfigFilesFromArg, AtmosConfigDirsFromArg, BasePath, ProfilesFromArg) directly from the ConfigAndStacksInfo struct, NOT from Viper. Passing an empty struct causes config selection flags (--base-path, --config, --config-path, --profile) to be silently ignored. Correct pattern: parse flags → populate struct → call InitCliConfig. See cmd/terraform/plan_diff.go for reference implementation.

Applied to files:

  • internal/exec/stack_processor_process_stacks.go
  • pkg/schema/schema_test.go
  • pkg/schema/schema.go
  • cmd/cmd_utils.go
  • internal/exec/shell_utils.go
  • pkg/config/load.go
📚 Learning: 2025-10-13T18:13:54.020Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1622
File: pkg/perf/perf.go:140-184
Timestamp: 2025-10-13T18:13:54.020Z
Learning: In pkg/perf/perf.go, the `trackWithSimpleStack` function intentionally skips ownership checks at call stack depth > 1 to avoid expensive `getGoroutineID()` calls on every nested function. This is a performance optimization for the common single-goroutine execution case (most Atmos commands), accepting the rare edge case of potential metric corruption if multi-goroutine execution occurs at depth > 1. The ~19× performance improvement justifies this trade-off.

Applied to files:

  • internal/exec/stack_processor_process_stacks.go
📚 Learning: 2025-07-05T20:59:02.914Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1363
File: internal/exec/template_utils.go:18-18
Timestamp: 2025-07-05T20:59:02.914Z
Learning: In the Atmos project, gomplate v4 is imported with a blank import (`_ "github.com/hairyhenderson/gomplate/v4"`) alongside v3 imports to resolve AWS SDK version conflicts. V3 uses older AWS SDK versions that conflict with newer AWS modules used by Atmos. A full migration to v4 requires extensive refactoring due to API changes and should be handled in a separate PR.

Applied to files:

  • internal/exec/stack_processor_process_stacks.go
  • go.mod
  • internal/exec/shell_utils.go
  • pkg/config/load.go
📚 Learning: 2025-08-16T23:32:40.412Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1405
File: internal/exec/describe_dependents_test.go:455-456
Timestamp: 2025-08-16T23:32:40.412Z
Learning: In the cloudposse/atmos Go codebase, `InitCliConfig` returns a `schema.AtmosConfiguration` value (not a pointer), while `ExecuteDescribeDependents` expects a `*schema.AtmosConfiguration` pointer parameter. Therefore, when passing the result of `InitCliConfig` to `ExecuteDescribeDependents`, use `&atmosConfig` to pass the address of the value.

Applied to files:

  • pkg/schema/schema_test.go
  • pkg/schema/schema.go
  • internal/exec/workflow_adapters.go
  • internal/exec/shell_utils_test.go
  • cmd/cmd_utils.go
  • pkg/config/load.go
  • internal/exec/workflow_utils.go
📚 Learning: 2024-12-12T15:17:45.245Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 808
File: examples/demo-atmos.d/atmos.d/tools/helmfile.yml:10-10
Timestamp: 2024-12-12T15:17:45.245Z
Learning: In `examples/demo-atmos.d/atmos.d/tools/helmfile.yml`, when suggesting changes to `kubeconfig_path`, ensure that the values use valid Go template syntax.

Applied to files:

  • pkg/schema/schema_test.go
📚 Learning: 2025-11-10T23:23:39.771Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/registry/aqua/aqua_test.go:417-442
Timestamp: 2025-11-10T23:23:39.771Z
Learning: In Atmos toolchain AquaRegistry, tests should not hit real GitHub. Use the options pattern via WithGitHubBaseURL to inject an httptest server URL and make GetLatestVersion/GetAvailableVersions deterministic.

Applied to files:

  • pkg/schema/schema_test.go
📚 Learning: 2024-10-20T13:12:46.499Z
Learnt from: haitham911
Repo: cloudposse/atmos PR: 736
File: pkg/config/const.go:6-6
Timestamp: 2024-10-20T13:12:46.499Z
Learning: In `cmd/cmd_utils.go`, it's acceptable to have hardcoded references to `atmos.yaml` in logs, and it's not necessary to update them to use the `CliConfigFileName` constant.

Applied to files:

  • pkg/schema/schema_test.go
  • pkg/schema/schema.go
  • internal/exec/workflow_adapters.go
  • internal/exec/shell_utils.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*_test.go : Use table-driven tests for testing multiple scenarios in Go

Applied to files:

  • pkg/env/file_test.go
  • internal/exec/shell_utils_test.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*_test.go : Every new feature must include comprehensive unit tests targeting >80% code coverage for all packages

Applied to files:

  • pkg/env/file_test.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*_test.go : Include integration tests for command flows and test CLI end-to-end when possible with test fixtures

Applied to files:

  • pkg/env/file_test.go
  • internal/exec/shell_utils_test.go
📚 Learning: 2025-05-23T19:51:47.091Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1255
File: cmd/describe_affected_test.go:15-15
Timestamp: 2025-05-23T19:51:47.091Z
Learning: The atmos codebase has a custom extension to *testing.T that provides a Chdir method, allowing test functions to call t.Chdir() to change working directories during tests. This is used consistently across test files in the codebase.

Applied to files:

  • pkg/env/file_test.go
  • internal/exec/shell_utils_test.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*.go : Support configuration via files, environment variables, and flags following the precedence order: flags > environment variables > config file > defaults

Applied to files:

  • pkg/env/file_test.go
  • pkg/schema/schema.go
  • pkg/env/file.go
  • pkg/config/load.go
📚 Learning: 2026-01-04T00:55:21.698Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.698Z
Learning: Applies to **/*_test.go : Prefer unit tests with mocks over integration tests - use interfaces and dependency injection for testability, generate mocks with `go.uber.org/mock/mockgen`, use table-driven tests, target >80% coverage

Applied to files:

  • pkg/env/file_test.go
📚 Learning: 2025-05-23T19:51:47.091Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1255
File: cmd/describe_affected_test.go:15-15
Timestamp: 2025-05-23T19:51:47.091Z
Learning: In the atmos codebase, t.Chdir() is a valid method call on *testing.T objects and works correctly for changing directories in tests. This is implemented through custom testing framework extensions and is used consistently throughout the test suite.

Applied to files:

  • pkg/env/file_test.go
  • internal/exec/shell_utils_test.go
📚 Learning: 2025-05-23T19:51:47.091Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1255
File: cmd/describe_affected_test.go:15-15
Timestamp: 2025-05-23T19:51:47.091Z
Learning: In the atmos codebase, t.Chdir() is a valid method call on *testing.T objects and works correctly for changing directories in tests.

Applied to files:

  • pkg/env/file_test.go
  • internal/exec/shell_utils_test.go
📚 Learning: 2025-05-23T19:51:47.091Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1255
File: cmd/describe_affected_test.go:15-15
Timestamp: 2025-05-23T19:51:47.091Z
Learning: In the atmos codebase, t.Chdir() is a valid method that can be called on *testing.T objects. This functionality is implemented through custom testing framework extensions and is used consistently throughout the test suite for changing working directories during tests.

Applied to files:

  • pkg/env/file_test.go
  • internal/exec/shell_utils_test.go
📚 Learning: 2024-12-25T20:28:47.526Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 887
File: internal/exec/stack_processor_utils.go:380-380
Timestamp: 2024-12-25T20:28:47.526Z
Learning: Windows path handling often requires `filepath.Join` to ensure correct separators and comparisons. Insufficient tests can break cross-platform compatibility, so migrating from `path.Join` to `filepath.Join` needs thorough testing on Windows before merging.

Applied to files:

  • pkg/env/file_test.go
📚 Learning: 2024-10-27T04:28:40.966Z
Learnt from: haitham911
Repo: cloudposse/atmos PR: 727
File: internal/exec/terraform_clean.go:155-175
Timestamp: 2024-10-27T04:28:40.966Z
Learning: In the `CollectDirectoryObjects` function in `internal/exec/terraform_clean.go`, recursive search through all subdirectories is not needed.

Applied to files:

  • pkg/env/file_test.go
📚 Learning: 2026-01-04T00:55:21.698Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.698Z
Learning: Applies to **/{pkg,internal,cmd}/**/*.go : Add `defer perf.Track(atmosConfig, "pkg.FuncName")()` plus blank line to all public functions, using `nil` if no atmosConfig param - exceptions: trivial getters/setters, command constructors, simple factories, functions delegating to tracked functions

Applied to files:

  • pkg/schema/schema.go
  • pkg/env/file.go
  • internal/exec/shell_utils.go
  • pkg/config/load.go
📚 Learning: 2024-12-02T21:26:32.337Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 808
File: pkg/config/config.go:478-483
Timestamp: 2024-12-02T21:26:32.337Z
Learning: In the 'atmos' project, when reviewing Go code like `pkg/config/config.go`, avoid suggesting file size checks after downloading remote configs if such checks aren't implemented elsewhere in the codebase.

Applied to files:

  • pkg/schema/schema.go
  • pkg/config/load.go
📚 Learning: 2025-09-08T01:25:44.958Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: The atmos toolchain has been updated to follow XDG Base Directory Specification with helper functions GetXDGCacheDir() and GetXDGTempCacheDir() in toolchain/xdg_cache.go, using XDG_CACHE_HOME when set and falling back to ~/.cache/atmos-toolchain, making it consistent with atmos core's XDG compliance.

Applied to files:

  • pkg/schema/schema.go
  • cmd/cmd_utils.go
  • internal/exec/workflow_utils.go
📚 Learning: 2025-01-25T15:21:40.413Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 808
File: examples/demo-atmos-cli-imports/atmos.yaml:8-8
Timestamp: 2025-01-25T15:21:40.413Z
Learning: In Atmos, when a directory is specified for configuration loading (e.g., in the `import` section of atmos.yaml), all files within that directory should be treated as Atmos configurations. Do not suggest restricting file extensions in directory-based glob patterns.

Applied to files:

  • pkg/schema/schema.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*.go : Use Viper for managing configuration, environment variables, and flags in CLI commands

Applied to files:

  • pkg/schema/schema.go
  • go.mod
  • pkg/config/load.go
📚 Learning: 2024-11-16T17:30:52.893Z
Learnt from: pkbhowmick
Repo: cloudposse/atmos PR: 786
File: internal/exec/shell_utils.go:159-162
Timestamp: 2024-11-16T17:30:52.893Z
Learning: For the `atmos terraform shell` command in `internal/exec/shell_utils.go`, input validation for the custom shell prompt is not required, as users will use this as a CLI tool and any issues will impact themselves.

Applied to files:

  • internal/exec/workflow_adapters.go
  • internal/exec/shell_utils_test.go
  • internal/exec/shell_utils.go
📚 Learning: 2025-08-16T23:33:07.477Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1405
File: internal/exec/describe_dependents_test.go:651-652
Timestamp: 2025-08-16T23:33:07.477Z
Learning: In the cloudposse/atmos Go codebase, ExecuteDescribeDependents expects a pointer to AtmosConfiguration (*schema.AtmosConfiguration), so when calling it with a value returned by cfg.InitCliConfig (which returns schema.AtmosConfiguration), the address-of operator (&) is necessary: ExecuteDescribeDependents(&atmosConfig, ...).

Applied to files:

  • internal/exec/workflow_adapters.go
  • cmd/cmd_utils.go
  • internal/exec/workflow_utils.go
📚 Learning: 2026-01-04T00:55:21.698Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.698Z
Learning: Applies to cmd/**/*_test.go : Always use `cmd.NewTestKit(t)` for cmd tests to auto-clean RootCmd state

Applied to files:

  • internal/exec/shell_utils_test.go
📚 Learning: 2025-12-10T18:32:51.237Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1808
File: cmd/terraform/backend/backend_delete_test.go:9-23
Timestamp: 2025-12-10T18:32:51.237Z
Learning: In cmd subpackages (e.g., cmd/terraform/backend/), tests cannot use cmd.NewTestKit(t) due to Go's test visibility rules (NewTestKit is in a parent package test file). These tests only need TestKit if they execute commands through RootCmd or modify RootCmd state. Structural tests that only verify command structure/flags without touching RootCmd don't require TestKit cleanup.

Applied to files:

  • internal/exec/shell_utils_test.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to {go.mod,go.sum} : Manage dependencies with Go modules and keep dependencies up to date while minimizing external dependencies

Applied to files:

  • go.mod
  • pkg/env/file.go
📚 Learning: 2025-01-17T00:21:32.987Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 944
File: go.mod:3-3
Timestamp: 2025-01-17T00:21:32.987Z
Learning: Go version 1.23.0 was deliberately introduced by the maintainer (aknysh) in January 2025. While this might be a pre-release or development version of Go, it has been approved for use in this project.

Applied to files:

  • go.mod
📚 Learning: 2025-01-17T00:21:32.987Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 944
File: go.mod:3-3
Timestamp: 2025-01-17T00:21:32.987Z
Learning: The project uses Go version 1.23.0 which has been confirmed by the maintainer to be working in production for months. Do not flag this as an invalid Go version.

Applied to files:

  • go.mod
📚 Learning: 2024-10-31T19:25:41.298Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 727
File: internal/exec/terraform_clean.go:233-235
Timestamp: 2024-10-31T19:25:41.298Z
Learning: When specifying color values in functions like `confirmDeleteTerraformLocal` in `internal/exec/terraform_clean.go`, avoid hardcoding color values. Instead, use predefined color constants or allow customization through configuration settings to improve accessibility and user experience across different terminals and themes.

Applied to files:

  • go.mod
📚 Learning: 2025-04-26T15:54:10.506Z
Learnt from: haitham911
Repo: cloudposse/atmos PR: 1195
File: internal/exec/terraform_clean.go:99-99
Timestamp: 2025-04-26T15:54:10.506Z
Learning: The error variable `ErrRelPath` is defined in `internal/exec/terraform_clean_util.go` and is used across files in the `exec` package, including in `terraform_clean.go`. This is part of an approach to standardize error handling in the codebase.

Applied to files:

  • go.mod
📚 Learning: 2025-03-12T21:38:42.699Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1139
File: pkg/config/go-homedir/homedir.go:183-196
Timestamp: 2025-03-12T21:38:42.699Z
Learning: The code in pkg/config/go-homedir is a direct fork of the mitchellh/go-homedir package and was intentionally imported as-is without modifications to maintain consistency with the original source. Security concerns or other improvements may be addressed in future PRs.

Applied to files:

  • go.mod
📚 Learning: 2025-04-10T20:48:22.687Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1147
File: pkg/config/load.go:0-0
Timestamp: 2025-04-10T20:48:22.687Z
Learning: In the `bindEnv` function in `pkg/config/load.go`, panic is used deliberately instead of returning errors because errors from `BindEnv` would only occur due to developer mistakes. Using panic helps with early detection of these developer errors during initialization.

Applied to files:

  • pkg/env/file.go
  • pkg/config/load.go
📚 Learning: 2025-10-10T23:51:36.597Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1599
File: internal/exec/terraform.go:394-402
Timestamp: 2025-10-10T23:51:36.597Z
Learning: In Atmos (internal/exec/terraform.go), when adding OpenTofu-specific flags like `--var-file` for `init`, do not gate them based on command name (e.g., checking if `info.Command == "tofu"` or `info.Command == "opentofu"`) because command names don't reliably indicate the actual binary being executed (symlinks, aliases). Instead, document the OpenTofu requirement in code comments and documentation, trusting users who enable the feature (e.g., `PassVars`) to ensure their terraform command points to an OpenTofu binary.

Applied to files:

  • internal/exec/shell_utils.go
📚 Learning: 2025-09-13T18:06:07.674Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1466
File: toolchain/list.go:39-42
Timestamp: 2025-09-13T18:06:07.674Z
Learning: In the cloudposse/atmos repository, for UI messages in the toolchain package, use utils.PrintfMessageToTUI instead of log.Error or fmt.Fprintln(os.Stderr, ...). Import pkg/utils with alias "u" to follow the established pattern.

Applied to files:

  • internal/exec/shell_utils.go
  • pkg/config/load.go
📚 Learning: 2026-01-04T00:55:21.698Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.698Z
Learning: Applies to **/*.go : Use colors from `pkg/ui/theme/colors.go` for all UI theming in Go code

Applied to files:

  • internal/exec/shell_utils.go
📚 Learning: 2026-01-04T00:55:21.698Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.698Z
Learning: Applies to **/*.go : Organize imports into three groups separated by blank lines, sorted alphabetically: Go stdlib, 3rd-party (NOT cloudposse/atmos), then Atmos packages with maintained aliases (`cfg`, `log`, `u`, `errUtils`)

Applied to files:

  • internal/exec/shell_utils.go
  • pkg/config/load.go
📚 Learning: 2025-11-09T19:06:58.470Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1752
File: pkg/profile/list/formatter_table.go:27-29
Timestamp: 2025-11-09T19:06:58.470Z
Learning: In the cloudposse/atmos repository, performance tracking with `defer perf.Track()` is enforced on all functions via linting, including high-frequency utility functions, formatters, and renderers. This is a repository-wide policy to maintain consistency and avoid making case-by-case judgment calls about which functions should have profiling.

Applied to files:

  • internal/exec/shell_utils.go
📚 Learning: 2024-10-28T01:51:30.811Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 727
File: internal/exec/terraform_clean.go:329-332
Timestamp: 2024-10-28T01:51:30.811Z
Learning: In the Atmos Go code, when deleting directories or handling file paths (e.g., in `terraform_clean.go`), always resolve the absolute path using `filepath.Abs` and use the logger `u.LogWarning` for logging messages instead of using `fmt.Printf`.

Applied to files:

  • internal/exec/shell_utils.go
📚 Learning: 2025-02-06T13:38:07.216Z
Learnt from: Listener430
Repo: cloudposse/atmos PR: 984
File: internal/exec/copy_glob.go:0-0
Timestamp: 2025-02-06T13:38:07.216Z
Learning: The `u.LogTrace` function in the `cloudposse/atmos` repository accepts `atmosConfig` as its first parameter, followed by the message string.

Applied to files:

  • internal/exec/shell_utils.go
📚 Learning: 2025-09-27T20:50:20.564Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1533
File: pkg/config/load.go:585-637
Timestamp: 2025-09-27T20:50:20.564Z
Learning: In the cloudposse/atmos repository, command merging prioritizes precedence over display ordering. Help commands are displayed lexicographically regardless of internal array order, so the mergeCommandArrays function focuses on ensuring the correct precedence chain (top-level file wins) rather than maintaining specific display order.

Applied to files:

  • internal/exec/shell_utils.go
📚 Learning: 2025-05-30T03:21:37.197Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1274
File: go.mod:63-63
Timestamp: 2025-05-30T03:21:37.197Z
Learning: The redis dependency (github.com/redis/go-redis/v9) in the atmos project is only used in tests, not in production code.

Applied to files:

  • pkg/config/load.go
📚 Learning: 2026-01-04T00:55:21.698Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.698Z
Learning: New config templates support Go templating with `FuncMap()` from `internal/exec/template_funcs.go`

Applied to files:

  • pkg/config/load.go
📚 Learning: 2025-09-23T04:43:31.857Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1430
File: pkg/config/config.go:98-107
Timestamp: 2025-09-23T04:43:31.857Z
Learning: In the Atmos codebase, NO_PAGER environment variable handling is intentionally kept as direct os.Getenv() access in pkg/config/config.go rather than using Viper binding, because adding no_pager to the config file would be confusing for users. This is an acknowledged exception to the normal Viper binding pattern for environment variables.

Applied to files:

  • pkg/config/load.go
📚 Learning: 2025-06-02T14:12:02.710Z
Learnt from: milldr
Repo: cloudposse/atmos PR: 1229
File: internal/exec/workflow_test.go:0-0
Timestamp: 2025-06-02T14:12:02.710Z
Learning: In the atmos codebase, workflow error handling was refactored to use `PrintErrorMarkdown` followed by returning specific error variables (like `ErrWorkflowNoSteps`, `ErrInvalidFromStep`, `ErrInvalidWorkflowStepType`, `ErrWorkflowStepFailed`) instead of `PrintErrorMarkdownAndExit`. This pattern allows proper error testing without the function terminating the process with `os.Exit`, enabling unit tests to assert on error conditions while maintaining excellent user-facing error formatting.

Applied to files:

  • internal/exec/workflow_utils.go
🧬 Code graph analysis (9)
cmd/auth_exec.go (1)
pkg/env/global.go (1)
  • MergeGlobalEnv (16-35)
pkg/schema/schema_test.go (1)
pkg/schema/schema.go (2)
  • AtmosConfiguration (75-120)
  • EnvConfig (67-72)
pkg/env/file_test.go (1)
pkg/env/file.go (4)
  • LoadEnvFiles (19-27)
  • LoadFromDirectory (33-69)
  • MergeEnvMaps (72-82)
  • MergeEnvSlices (85-100)
pkg/schema/schema.go (1)
pkg/config/casemap/casemap.go (1)
  • CaseMaps (20-22)
internal/exec/workflow_adapters.go (2)
pkg/perf/perf.go (1)
  • Track (121-138)
internal/exec/shell_utils.go (1)
  • ExecuteShell (133-191)
cmd/cmd_utils.go (2)
pkg/env/global.go (1)
  • MergeGlobalEnv (16-35)
internal/exec/shell_utils.go (1)
  • ExecuteShell (133-191)
pkg/env/file.go (2)
pkg/perf/perf.go (1)
  • Track (121-138)
pkg/env/global.go (1)
  • ConvertMapToSlice (39-51)
internal/exec/shell_utils.go (2)
pkg/env/global.go (3)
  • MergeGlobalEnv (16-35)
  • MergeSystemEnvWithGlobal (87-91)
  • MergeSystemEnvSimpleWithGlobal (105-109)
pkg/env/file.go (3)
  • LoadFromDirectory (33-69)
  • MergeEnvSlices (85-100)
  • MapToSlice (104-108)
internal/exec/workflow_utils.go (1)
internal/exec/shell_utils.go (1)
  • ExecuteShell (133-191)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (21)
pkg/env/file.go (6)

19-27: LGTM.

Clean delegation with proper empty pattern handling.


33-69: LGTM — parent directory walking logic is correct.

The precedence model (repo root lowest, working dir highest) aligns with the expected behavior. Error handling propagates correctly.


72-82: LGTM.

Simple and correct. Nil maps are handled gracefully.


85-100: LGTM.

Properly handles values containing = by splitting only at the first occurrence. Entries without = or with empty keys are silently skipped, which is reasonable.


164-213: LGTM — security boundary enforced correctly.

The isWithinOrEqual check at Line 187 prevents walking beyond repo root. Path normalization with filepath.Abs and filepath.Clean ensures cross-platform compatibility.


216-231: LGTM — path containment check is robust.

Adding the separator before the prefix check (Line 229) correctly prevents false positives like /a/b/c-other matching /a/b/c.

pkg/env/file_test.go (4)

353-377: LGTM — comprehensive table-driven tests for path containment.

Good coverage of edge cases including trailing slashes and similar prefixes. The hardcoded Unix paths work because isWithinOrEqual uses filepath.Clean which normalizes separators.


12-125: LGTM — thorough test coverage for LoadEnvFiles.

Tests cover key scenarios: single file, glob patterns, missing files, case preservation, empty patterns, values with equals, quoted values, and directory filtering.


127-230: LGTM — LoadFromDirectory tests validate parent traversal and security boundaries.

The test at Line 172-200 correctly verifies that .env files outside repo root are not loaded.


279-281: No issue here. The splitStringAtFirstOccurrence helper is defined in pkg/env/env.go at line 55 and is accessible throughout the package.

cmd/auth_exec.go (1)

111-111: LGTM — updated to use case-sensitive env vars.

Consistent with the broader refactor to GetCaseSensitiveEnvVars() across the codebase.

internal/exec/shell_utils_test.go (2)

600-609: LGTM — nil atmosConfig is appropriate for basic shell tests.

These tests verify shell execution mechanics, not Atmos-specific env var handling.


611-669: LGTM — remaining ExecuteShell test updates are consistent.

All test cases correctly pass nil as the first argument with appropriate comments.

internal/exec/workflow_adapters.go (1)

34-40: LGTM — nil atmosConfig is intentional for workflow shell commands.

Per the learning, workflow non-identity steps use baseWorkflowEnv. The comment clearly documents the rationale: shell commands don't need atmosConfig for basic execution.

go.mod (1)

71-71: LGTM — godotenv promoted to direct dependency.

This correctly reflects the new direct import in pkg/env/file.go. v1.5.1 is the latest stable version.

internal/exec/stack_processor_process_stacks.go (1)

211-215: LGTM — atmos.yaml env correctly positioned as lowest priority in merge chain.

GetCaseSensitiveEnvVars() provides case-preserved env vars from atmos.yaml. The merge order ensures component-level env overrides global env, which overrides atmos.yaml env.

internal/exec/workflow_utils.go (1)

324-334: Workflow env handling now correctly uses case‑preserving env access

Wiring prepareStepEnvironment to atmosConfig.GetCaseSensitiveEnvVars() and passing &atmosConfig into ExecuteShell keeps workflow steps in line with the new EnvConfig / .env behavior and perf tracking. The change is consistent with how other call sites are being updated.

internal/exec/shell_utils.go (3)

133-191: ExecuteShell updates correctly integrate global env and per‑dir .env support

Passing *schema.AtmosConfiguration into ExecuteShell and merging:

  • os.Environ()
  • global env via atmosConfig.GetCaseSensitiveEnvVars()
  • per‑directory .env via LoadFromDirectory

before applying envVars gives shells the same enriched environment as workflows and other exec paths, while still allowing the caller‑supplied entries to override. The atmosConfig == nil guard keeps tests and non‑Atmos usage safe.


210-305: Terraform shell now respects global env via GetCaseSensitiveEnvVars

Switching execTerraformShellCommand to build mergedEnv with:

mergedEnv := envpkg.MergeSystemEnvWithGlobal(componentEnvList, atmosConfig.GetCaseSensitiveEnvVars())

ensures both legacy flat env and structured env.vars feed into the Terraform shell’s environment with proper case restoration, while still letting the per‑component entries in componentEnvList take precedence. This keeps the Terraform shell consistent with the rest of the env pipeline.

Also applies to: 297-304


361-419: Auth shell correctly layers global env under auth‑specific vars

In ExecAuthShellCommand, using MergeSystemEnvSimpleWithGlobal(authEnvList, atmosConfig.GetCaseSensitiveEnvVars()) makes the auth shell’s environment:

  • Start from the host env.
  • Add global env from Atmos config (case‑preserved).
  • Finally overlay the auth/env list (including ATMOS_IDENTITY and provider‑specific credentials).

That matches the documented precedence and keeps auth shells aligned with the new env model.

Also applies to: 402-403

pkg/schema/schema_test.go (1)

3-10: Import path resolves correctlygo.yaml.in/yaml/v3 is declared as a direct dependency in go.mod, so no resolution issues here.

The inconsistency you noticed is real (codebase mixes go.yaml.in/yaml/v3 and gopkg.in/yaml.v3), but both import paths are intentionally maintained as separate dependencies. If consistency across the codebase matters for maintenance, that's a separate refactoring conversation.

Likely an incorrect or invalid review comment.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 4, 2026
@osterman osterman added the minor New features that do not break anything label Jan 5, 2026
@github-actions
Copy link
Copy Markdown

github-actions bot commented Jan 8, 2026

Warning

Release Documentation Required

This PR is labeled minor or major and requires documentation updates:

  • Changelog entry - Add a blog post in website/blog/YYYY-MM-DD-feature-name.mdx
  • Roadmap update - Update website/src/data/roadmap.js with the new milestone

Alternatively: If this change doesn't require release documentation, remove the minor or major label.

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 (2)
pkg/config/load.go (2)

1243-1275: Consider more robust structured form detection.

The detection at lines 1259-1260 checks only for key existence, not value type. If a flat env map contains a key literally named vars or files, it would be misinterpreted as structured form.

♻️ More robust detection
-	// Detect structured form by presence of "vars" or "files" keys.
-	_, hasVars := envMap["vars"]
-	_, hasFiles := envMap["files"]
+	// Detect structured form by presence of "vars" or "files" keys with map values.
+	varsVal, hasVars := envMap["vars"]
+	filesVal, hasFiles := envMap["files"]
+	
+	// Only treat as structured if the values are maps/objects, not simple strings.
+	if hasVars {
+		if _, ok := varsVal.(map[string]any); !ok {
+			hasVars = false
+		}
+	}
+	if hasFiles {
+		if _, ok := filesVal.(map[string]any); !ok {
+			hasFiles = false
+		}
+	}

1318-1320: Consider moving UI reporting to the caller.

The ui.Success() calls create side effects in the config loading function, which is called from many places. This could be noisy in test environments or when config is reloaded.

Since the function already returns loadedFiles, consider letting the caller handle UI reporting:

// In LoadConfig (line 284):
loadedFiles := loadEnvFilesEarly(v, v.GetString("base_path"))
for _, file := range loadedFiles {
    ui.Success(fmt.Sprintf("Loaded %s", filepath.Base(file)))
}

Then remove the UI calls from inside loadEnvFilesEarly. This separates concerns and makes the config loading function more testable.

📜 Review details

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Disabled knowledge base sources:

  • Linear integration is disabled by default for public repositories

You can enable these sources in your CodeRabbit configuration.

📥 Commits

Reviewing files that changed from the base of the PR and between 268f81f and addb84d.

📒 Files selected for processing (5)
  • cmd/auth_exec.go
  • cmd/cmd_utils.go
  • go.mod
  • internal/exec/stack_processor_process_stacks.go
  • pkg/config/load.go
🚧 Files skipped from review as they are similar to previous changes (2)
  • internal/exec/stack_processor_process_stacks.go
  • cmd/cmd_utils.go
🧰 Additional context used
📓 Path-based instructions (4)
cmd/**/*.go

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

cmd/**/*.go: Use Cobra's recommended command structure with a root command and subcommands, implementing each command in a separate file under cmd/ directory
Provide comprehensive help text for all commands and flags, include examples in command help, and follow Go's documentation conventions in Cobra command definitions
Provide meaningful feedback to users and include progress indicators for long-running operations in CLI commands

Files:

  • cmd/auth_exec.go
**/*.go

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

**/*.go: Use Viper for managing configuration, environment variables, and flags in CLI commands
Use interfaces for external dependencies to facilitate mocking and consider using testify/mock for creating mock implementations
All code must pass golangci-lint checks
Follow Go's error handling idioms: use meaningful error messages, wrap errors with context using fmt.Errorf("context: %w", err), and consider using custom error types for domain-specific errors
Follow standard Go coding style: use gofmt and goimports to format code, prefer short descriptive variable names, use kebab-case for command-line flags, and snake_case for environment variables
Document all exported functions, types, and methods following Go's documentation conventions
Document complex logic with inline comments in Go code
Support configuration via files, environment variables, and flags following the precedence order: flags > environment variables > config file > defaults
Provide clear error messages to users, include troubleshooting hints when appropriate, and log detailed errors for debugging

**/*.go: All comments must end with periods (enforced by godot linter) in Go code
Organize imports into three groups separated by blank lines, sorted alphabetically: Go stdlib, 3rd-party (NOT cloudposse/atmos), then Atmos packages with maintained aliases (cfg, log, u, errUtils)
All errors MUST be wrapped using static errors defined in errors/errors.go - use errors.Join for combining errors, fmt.Errorf with %w for context, and errors.Is() for error checking
Never manually create mocks - use go.uber.org/mock/mockgen with //go:generate directives in Go code
Keep files small and focused - under 600 lines with one cmd/impl per file, co-locate tests, never use //revive:disable:file-length-limit
Use colors from pkg/ui/theme/colors.go for all UI theming in Go code
Code must be compatible with Linux, macOS, and Windows - use SDKs over binaries, use filepath.Join() instead of h...

Files:

  • cmd/auth_exec.go
  • pkg/config/load.go
**/{pkg,internal,cmd}/**/*.go

📄 CodeRabbit inference engine (CLAUDE.md)

Add defer perf.Track(atmosConfig, "pkg.FuncName")() plus blank line to all public functions, using nil if no atmosConfig param - exceptions: trivial getters/setters, command constructors, simple factories, functions delegating to tracked functions

Files:

  • cmd/auth_exec.go
  • pkg/config/load.go
{go.mod,go.sum}

📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)

Manage dependencies with Go modules and keep dependencies up to date while minimizing external dependencies

Files:

  • go.mod
🧠 Learnings (37)
📓 Common learnings
Learnt from: osterman
Repo: cloudposse/atmos PR: 1697
File: internal/exec/oci_utils.go:0-0
Timestamp: 2025-11-08T19:56:18.660Z
Learning: In the Atmos codebase, when a function receives an `*schema.AtmosConfiguration` parameter, it should read configuration values from `atmosConfig.Settings` fields rather than using direct `os.Getenv()` or `viper.GetString()` calls. The Atmos pattern is: viper.BindEnv in cmd/root.go binds environment variables → Viper unmarshals into atmosConfig.Settings via mapstructure → business logic reads from the Settings struct. This provides centralized config management, respects precedence, and enables testability. Example: `atmosConfig.Settings.AtmosGithubToken` instead of `os.Getenv("ATMOS_GITHUB_TOKEN")` in functions like `getGHCRAuth` in internal/exec/oci_utils.go.
Learnt from: aknysh
Repo: cloudposse/atmos PR: 0
File: :0-0
Timestamp: 2025-01-19T22:30:27.600Z
Learning: The Atmos YAML function `!env` is used to retrieve environment variables and assign them to sections in stack manifests. It supports both simple types (string, number, boolean) and complex types (JSON-encoded lists, maps, objects).
Learnt from: Listener430
Repo: cloudposse/atmos PR: 934
File: tests/fixtures/scenarios/docs-generate/README.md.gotmpl:99-118
Timestamp: 2025-01-25T03:51:57.689Z
Learning: For the cloudposse/atmos repository, changes to template contents should be handled in dedicated PRs and are typically considered out of scope for PRs focused on other objectives.
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/add_test.go:67-77
Timestamp: 2025-11-11T03:47:45.878Z
Learning: In the cloudposse/atmos codebase, tests should prefer t.Setenv for environment variable setup/teardown instead of os.Setenv/Unsetenv to ensure test-scoped isolation.
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/which_test.go:166-223
Timestamp: 2025-11-11T03:47:59.576Z
Learning: In the cloudposse/atmos repo, tests that manipulate environment variables should use testing.T.Setenv for automatic setup/teardown instead of os.Setenv/Unsetenv.
Learnt from: RoseSecurity
Repo: cloudposse/atmos PR: 725
File: internal/exec/vendor_component_utils.go:55-62
Timestamp: 2024-10-15T17:36:08.040Z
Learning: In the 'atmos' project, when adding support for multiple file extensions, the preferred approach is to directly check for the existence of the files rather than modifying constants or adding helper functions.
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: The atmos toolchain has been updated to follow XDG Base Directory Specification with helper functions GetXDGCacheDir() and GetXDGTempCacheDir() in toolchain/xdg_cache.go, using XDG_CACHE_HOME when set and falling back to ~/.cache/atmos-toolchain, making it consistent with atmos core's XDG compliance.
Learnt from: osterman
Repo: cloudposse/atmos PR: 1433
File: cmd/theme_list.go:33-36
Timestamp: 2025-08-29T20:57:35.423Z
Learning: In the Atmos codebase, avoid using viper.SetEnvPrefix("ATMOS") with viper.AutomaticEnv() because canonical environment variable names are not exclusive to Atmos and could cause conflicts. Instead, use selective environment variable binding through the setEnv function in pkg/config/load.go with bindEnv(v, "config.key", "ENV_VAR_NAME") for specific environment variables.
Learnt from: osterman
Repo: cloudposse/atmos PR: 808
File: examples/demo-atmos-cli-imports/atmos.yaml:8-8
Timestamp: 2025-01-25T15:21:40.413Z
Learning: In Atmos, when a directory is specified for configuration loading (e.g., in the `import` section of atmos.yaml), all files within that directory should be treated as Atmos configurations. Do not suggest restricting file extensions in directory-based glob patterns.
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1352
File: pkg/store/artifactory_store_test.go:108-113
Timestamp: 2025-08-15T14:43:41.030Z
Learning: In test files for the atmos project, it's acceptable to ignore errors from os.Setenv/Unsetenv operations during test environment setup and teardown, as these are controlled test scenarios.
Learnt from: Benbentwo
Repo: cloudposse/atmos PR: 1475
File: pkg/auth/providers/github/oidc.go:96-100
Timestamp: 2025-09-10T17:34:52.568Z
Learning: The ATMOS_ environment variable binding guideline applies to Atmos configuration variables, not external service-required environment variables like GitHub Actions OIDC variables (GITHUB_ACTIONS, ACTIONS_ID_TOKEN_*) which must use their standard names.
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.720Z
Learning: Applies to **/*.go : Use `viper.BindEnv("ATMOS_VAR", "ATMOS_VAR", "FALLBACK")` for environment variables - ATMOS_ prefix required in Go code
📚 Learning: 2025-12-13T06:10:25.156Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: internal/exec/workflow_utils.go:0-0
Timestamp: 2025-12-13T06:10:25.156Z
Learning: Atmos workflows: In internal/exec/workflow_utils.go ExecuteWorkflow, non-identity steps intentionally use baseWorkflowEnv, which is constructed from the parent environment with PATH modifications for the toolchain. Avoid appending os.Environ() again; prefer documenting this behavior and testing that standard environment variables are preserved.

Applied to files:

  • cmd/auth_exec.go
  • go.mod
📚 Learning: 2025-08-29T20:57:35.423Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1433
File: cmd/theme_list.go:33-36
Timestamp: 2025-08-29T20:57:35.423Z
Learning: In the Atmos codebase, avoid using viper.SetEnvPrefix("ATMOS") with viper.AutomaticEnv() because canonical environment variable names are not exclusive to Atmos and could cause conflicts. Instead, use selective environment variable binding through the setEnv function in pkg/config/load.go with bindEnv(v, "config.key", "ENV_VAR_NAME") for specific environment variables.

Applied to files:

  • cmd/auth_exec.go
  • pkg/config/load.go
📚 Learning: 2025-11-08T19:56:18.660Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1697
File: internal/exec/oci_utils.go:0-0
Timestamp: 2025-11-08T19:56:18.660Z
Learning: In the Atmos codebase, when a function receives an `*schema.AtmosConfiguration` parameter, it should read configuration values from `atmosConfig.Settings` fields rather than using direct `os.Getenv()` or `viper.GetString()` calls. The Atmos pattern is: viper.BindEnv in cmd/root.go binds environment variables → Viper unmarshals into atmosConfig.Settings via mapstructure → business logic reads from the Settings struct. This provides centralized config management, respects precedence, and enables testability. Example: `atmosConfig.Settings.AtmosGithubToken` instead of `os.Getenv("ATMOS_GITHUB_TOKEN")` in functions like `getGHCRAuth` in internal/exec/oci_utils.go.

Applied to files:

  • cmd/auth_exec.go
  • pkg/config/load.go
📚 Learning: 2026-01-04T00:55:21.720Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.720Z
Learning: Applies to **/*.go : Use `viper.BindEnv("ATMOS_VAR", "ATMOS_VAR", "FALLBACK")` for environment variables - ATMOS_ prefix required in Go code

Applied to files:

  • cmd/auth_exec.go
  • go.mod
  • pkg/config/load.go
📚 Learning: 2025-11-11T03:47:59.576Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/which_test.go:166-223
Timestamp: 2025-11-11T03:47:59.576Z
Learning: In the cloudposse/atmos repo, tests that manipulate environment variables should use testing.T.Setenv for automatic setup/teardown instead of os.Setenv/Unsetenv.

Applied to files:

  • cmd/auth_exec.go
  • pkg/config/load.go
📚 Learning: 2025-04-23T15:02:50.246Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1202
File: pkg/utils/yaml_func_exec.go:104-104
Timestamp: 2025-04-23T15:02:50.246Z
Learning: In the Atmos codebase, direct calls to `os.Getenv` should be avoided. Instead, use `viper.BindEnv` for environment variable access. This provides a consistent approach to configuration management across the codebase.

Applied to files:

  • cmd/auth_exec.go
  • go.mod
  • pkg/config/load.go
📚 Learning: 2024-10-23T21:36:40.262Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 740
File: cmd/cmd_utils.go:340-359
Timestamp: 2024-10-23T21:36:40.262Z
Learning: In the Go codebase for Atmos, when reviewing functions like `checkAtmosConfig` in `cmd/cmd_utils.go`, avoid suggesting refactoring to return errors instead of calling `os.Exit` if such changes would significantly increase the scope due to the need to update multiple call sites.

Applied to files:

  • cmd/auth_exec.go
  • pkg/config/load.go
📚 Learning: 2025-11-11T03:47:45.878Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: toolchain/add_test.go:67-77
Timestamp: 2025-11-11T03:47:45.878Z
Learning: In the cloudposse/atmos codebase, tests should prefer t.Setenv for environment variable setup/teardown instead of os.Setenv/Unsetenv to ensure test-scoped isolation.

Applied to files:

  • cmd/auth_exec.go
📚 Learning: 2025-09-08T01:25:44.958Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1466
File: website/docs/cli/commands/toolchain/usage.mdx:117-121
Timestamp: 2025-09-08T01:25:44.958Z
Learning: The atmos toolchain has been updated to follow XDG Base Directory Specification with helper functions GetXDGCacheDir() and GetXDGTempCacheDir() in toolchain/xdg_cache.go, using XDG_CACHE_HOME when set and falling back to ~/.cache/atmos-toolchain, making it consistent with atmos core's XDG compliance.

Applied to files:

  • cmd/auth_exec.go
📚 Learning: 2024-12-07T16:16:13.038Z
Learnt from: Listener430
Repo: cloudposse/atmos PR: 825
File: internal/exec/helmfile_generate_varfile.go:28-31
Timestamp: 2024-12-07T16:16:13.038Z
Learning: In `internal/exec/helmfile_generate_varfile.go`, the `--help` command (`./atmos helmfile generate varfile --help`) works correctly without requiring stack configurations, and the only change needed was to make `ProcessCommandLineArgs` exportable by capitalizing its name.

Applied to files:

  • cmd/auth_exec.go
📚 Learning: 2025-09-07T18:07:00.549Z
Learnt from: Benbentwo
Repo: cloudposse/atmos PR: 1452
File: cmd/auth_login.go:43-44
Timestamp: 2025-09-07T18:07:00.549Z
Learning: In the atmos project, the identity flag is defined as a persistent flag on the auth root command (cmd/auth.go), making it available to all auth subcommands without needing to be redefined in each individual subcommand.

Applied to files:

  • cmd/auth_exec.go
📚 Learning: 2024-12-11T18:40:12.808Z
Learnt from: Listener430
Repo: cloudposse/atmos PR: 844
File: cmd/helmfile.go:37-37
Timestamp: 2024-12-11T18:40:12.808Z
Learning: In the atmos project, `cliConfig` is initialized within the `cmd` package in `root.go` and can be used in other command files.

Applied to files:

  • cmd/auth_exec.go
  • pkg/config/load.go
📚 Learning: 2025-12-13T04:37:25.223Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: cmd/root.go:0-0
Timestamp: 2025-12-13T04:37:25.223Z
Learning: In Atmos cmd/root.go Execute(), after cfg.InitCliConfig, we must call both toolchainCmd.SetAtmosConfig(&atmosConfig) and toolchain.SetAtmosConfig(&atmosConfig) so the CLI wrapper and the toolchain package receive configuration; missing either can cause nil-pointer panics in toolchain path resolution.

Applied to files:

  • cmd/auth_exec.go
  • pkg/config/load.go
📚 Learning: 2025-08-15T14:43:41.030Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1352
File: pkg/store/artifactory_store_test.go:108-113
Timestamp: 2025-08-15T14:43:41.030Z
Learning: In test files for the atmos project, it's acceptable to ignore errors from os.Setenv/Unsetenv operations during test environment setup and teardown, as these are controlled test scenarios.

Applied to files:

  • cmd/auth_exec.go
📚 Learning: 2025-12-13T04:37:40.435Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1686
File: cmd/toolchain/get.go:23-40
Timestamp: 2025-12-13T04:37:40.435Z
Learning: In Go CLI command files using Cobra, constrain the subcommand to accept at most one positional argument (MaximumNArgs(1)) so it supports both listing all items (zero args) and fetching a specific item (one arg). Define and parse flags with a standard parser (e.g., flags.NewStandardParser()) and avoid binding flags to Viper (no viper.BindEnv/BindPFlag). This promotes explicit argument handling and predictable flag behavior across command files.

Applied to files:

  • cmd/auth_exec.go
📚 Learning: 2025-12-21T04:10:29.030Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1891
File: internal/exec/describe_affected.go:468-468
Timestamp: 2025-12-21T04:10:29.030Z
Learning: In Go, package-level declarations (constants, variables, types, and functions) are visible to all files in the same package without imports. During reviews in cloudposse/atmos (and similar Go codebases), before suggesting to declare a new identifier, first check if it already exists in another file of the same package. If it exists, you can avoid adding a new declaration; if not, proceed with a proper package-level declaration. 

Applied to files:

  • cmd/auth_exec.go
  • pkg/config/load.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to {go.mod,go.sum} : Manage dependencies with Go modules and keep dependencies up to date while minimizing external dependencies

Applied to files:

  • go.mod
📚 Learning: 2025-01-17T00:21:32.987Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 944
File: go.mod:3-3
Timestamp: 2025-01-17T00:21:32.987Z
Learning: Go version 1.23.0 was deliberately introduced by the maintainer (aknysh) in January 2025. While this might be a pre-release or development version of Go, it has been approved for use in this project.

Applied to files:

  • go.mod
📚 Learning: 2025-01-17T00:21:32.987Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 944
File: go.mod:3-3
Timestamp: 2025-01-17T00:21:32.987Z
Learning: The project uses Go version 1.23.0 which has been confirmed by the maintainer to be working in production for months. Do not flag this as an invalid Go version.

Applied to files:

  • go.mod
📚 Learning: 2024-10-31T19:25:41.298Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 727
File: internal/exec/terraform_clean.go:233-235
Timestamp: 2024-10-31T19:25:41.298Z
Learning: When specifying color values in functions like `confirmDeleteTerraformLocal` in `internal/exec/terraform_clean.go`, avoid hardcoding color values. Instead, use predefined color constants or allow customization through configuration settings to improve accessibility and user experience across different terminals and themes.

Applied to files:

  • go.mod
📚 Learning: 2025-04-26T15:54:10.506Z
Learnt from: haitham911
Repo: cloudposse/atmos PR: 1195
File: internal/exec/terraform_clean.go:99-99
Timestamp: 2025-04-26T15:54:10.506Z
Learning: The error variable `ErrRelPath` is defined in `internal/exec/terraform_clean_util.go` and is used across files in the `exec` package, including in `terraform_clean.go`. This is part of an approach to standardize error handling in the codebase.

Applied to files:

  • go.mod
📚 Learning: 2025-03-12T21:38:42.699Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1139
File: pkg/config/go-homedir/homedir.go:183-196
Timestamp: 2025-03-12T21:38:42.699Z
Learning: The code in pkg/config/go-homedir is a direct fork of the mitchellh/go-homedir package and was intentionally imported as-is without modifications to maintain consistency with the original source. Security concerns or other improvements may be addressed in future PRs.

Applied to files:

  • go.mod
📚 Learning: 2025-07-05T20:59:02.914Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1363
File: internal/exec/template_utils.go:18-18
Timestamp: 2025-07-05T20:59:02.914Z
Learning: In the Atmos project, gomplate v4 is imported with a blank import (`_ "github.com/hairyhenderson/gomplate/v4"`) alongside v3 imports to resolve AWS SDK version conflicts. V3 uses older AWS SDK versions that conflict with newer AWS modules used by Atmos. A full migration to v4 requires extensive refactoring due to API changes and should be handled in a separate PR.

Applied to files:

  • go.mod
  • pkg/config/load.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*.go : Use Viper for managing configuration, environment variables, and flags in CLI commands

Applied to files:

  • go.mod
  • pkg/config/load.go
📚 Learning: 2025-11-24T17:35:37.209Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-11-24T17:35:37.209Z
Learning: Applies to **/*.go : Support configuration via files, environment variables, and flags following the precedence order: flags > environment variables > config file > defaults

Applied to files:

  • pkg/config/load.go
📚 Learning: 2025-04-10T20:48:22.687Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1147
File: pkg/config/load.go:0-0
Timestamp: 2025-04-10T20:48:22.687Z
Learning: In the `bindEnv` function in `pkg/config/load.go`, panic is used deliberately instead of returning errors because errors from `BindEnv` would only occur due to developer mistakes. Using panic helps with early detection of these developer errors during initialization.

Applied to files:

  • pkg/config/load.go
📚 Learning: 2025-12-13T03:21:35.786Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1813
File: cmd/terraform/shell.go:28-73
Timestamp: 2025-12-13T03:21:35.786Z
Learning: In Atmos, when calling cfg.InitCliConfig, you must first populate the schema.ConfigAndStacksInfo struct with global flag values using flags.ParseGlobalFlags(cmd, v) rather than passing an empty struct. The LoadConfig function (pkg/config/load.go) reads config selection fields (AtmosConfigFilesFromArg, AtmosConfigDirsFromArg, BasePath, ProfilesFromArg) directly from the ConfigAndStacksInfo struct, NOT from Viper. Passing an empty struct causes config selection flags (--base-path, --config, --config-path, --profile) to be silently ignored. Correct pattern: parse flags → populate struct → call InitCliConfig. See cmd/terraform/plan_diff.go for reference implementation.

Applied to files:

  • pkg/config/load.go
📚 Learning: 2026-01-04T00:55:21.720Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.720Z
Learning: Applies to **/{pkg,internal,cmd}/**/*.go : Add `defer perf.Track(atmosConfig, "pkg.FuncName")()` plus blank line to all public functions, using `nil` if no atmosConfig param - exceptions: trivial getters/setters, command constructors, simple factories, functions delegating to tracked functions

Applied to files:

  • pkg/config/load.go
📚 Learning: 2024-12-02T21:26:32.337Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 808
File: pkg/config/config.go:478-483
Timestamp: 2024-12-02T21:26:32.337Z
Learning: In the 'atmos' project, when reviewing Go code like `pkg/config/config.go`, avoid suggesting file size checks after downloading remote configs if such checks aren't implemented elsewhere in the codebase.

Applied to files:

  • pkg/config/load.go
📚 Learning: 2025-09-13T18:06:07.674Z
Learnt from: samtholiya
Repo: cloudposse/atmos PR: 1466
File: toolchain/list.go:39-42
Timestamp: 2025-09-13T18:06:07.674Z
Learning: In the cloudposse/atmos repository, for UI messages in the toolchain package, use utils.PrintfMessageToTUI instead of log.Error or fmt.Fprintln(os.Stderr, ...). Import pkg/utils with alias "u" to follow the established pattern.

Applied to files:

  • pkg/config/load.go
📚 Learning: 2025-05-30T03:21:37.197Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1274
File: go.mod:63-63
Timestamp: 2025-05-30T03:21:37.197Z
Learning: The redis dependency (github.com/redis/go-redis/v9) in the atmos project is only used in tests, not in production code.

Applied to files:

  • pkg/config/load.go
📚 Learning: 2026-01-04T00:55:21.720Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.720Z
Learning: Applies to **/*.go : Organize imports into three groups separated by blank lines, sorted alphabetically: Go stdlib, 3rd-party (NOT cloudposse/atmos), then Atmos packages with maintained aliases (`cfg`, `log`, `u`, `errUtils`)

Applied to files:

  • pkg/config/load.go
📚 Learning: 2026-01-04T00:55:21.720Z
Learnt from: CR
Repo: cloudposse/atmos PR: 0
File: CLAUDE.md:0-0
Timestamp: 2026-01-04T00:55:21.720Z
Learning: New config templates support Go templating with `FuncMap()` from `internal/exec/template_funcs.go`

Applied to files:

  • pkg/config/load.go
📚 Learning: 2025-08-16T23:32:40.412Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1405
File: internal/exec/describe_dependents_test.go:455-456
Timestamp: 2025-08-16T23:32:40.412Z
Learning: In the cloudposse/atmos Go codebase, `InitCliConfig` returns a `schema.AtmosConfiguration` value (not a pointer), while `ExecuteDescribeDependents` expects a `*schema.AtmosConfiguration` pointer parameter. Therefore, when passing the result of `InitCliConfig` to `ExecuteDescribeDependents`, use `&atmosConfig` to pass the address of the value.

Applied to files:

  • pkg/config/load.go
📚 Learning: 2025-09-23T04:43:31.857Z
Learnt from: osterman
Repo: cloudposse/atmos PR: 1430
File: pkg/config/config.go:98-107
Timestamp: 2025-09-23T04:43:31.857Z
Learning: In the Atmos codebase, NO_PAGER environment variable handling is intentionally kept as direct os.Getenv() access in pkg/config/config.go rather than using Viper binding, because adding no_pager to the config file would be confusing for users. This is an acknowledged exception to the normal Viper binding pattern for environment variables.

Applied to files:

  • pkg/config/load.go
📚 Learning: 2025-09-29T15:47:10.908Z
Learnt from: aknysh
Repo: cloudposse/atmos PR: 1540
File: internal/exec/terraform_cli_args_utils.go:64-73
Timestamp: 2025-09-29T15:47:10.908Z
Learning: In the Atmos codebase, viper.BindEnv is required for CLI commands in the cmd/ package, but internal utilities can use os.Getenv directly when parsing environment variables for business logic purposes. The requirement to use viper is specific to the CLI interface layer, not all environment variable access throughout the codebase.

Applied to files:

  • pkg/config/load.go
🧬 Code graph analysis (2)
cmd/auth_exec.go (1)
pkg/env/global.go (1)
  • MergeGlobalEnv (16-35)
pkg/config/load.go (3)
pkg/schema/schema.go (1)
  • AtmosConfiguration (75-121)
pkg/env/file.go (1)
  • LoadEnvFiles (19-27)
pkg/ui/formatter.go (1)
  • Success (204-211)
⏰ Context from checks skipped due to timeout of 900000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (1)
  • GitHub Check: Summary
🔇 Additional comments (5)
go.mod (1)

74-74: Solid dependency addition. Moving joho/godotenv to direct dependency is the right call since it's now a first-class feature. Version v1.5.1 is the latest stable release, so you're good to go.

cmd/auth_exec.go (1)

113-113: LGTM! Proper use of case-sensitive env access.

The switch to GetCaseSensitiveEnvVars() correctly aligns with the new case-sensitive environment handling introduced in this PR.

pkg/config/load.go (3)

22-22: LGTM! Imports properly organized.

The new imports for env and ui packages are correctly placed and follow the three-group organization pattern.

Also applies to: 26-26


281-284: Correct placement for early .env loading.

The placement before profile detection is essential and well-documented. This enables ATMOS_PROFILE and other ATMOS_* variables from .env files to influence Atmos behavior during initialization.


1328-1331: Good catch on dual-form case preservation.

Adding both env (flat form) and env.vars (structured form) ensures environment variable names maintain correct casing regardless of which configuration format is used.

@github-actions
Copy link
Copy Markdown

Warning

Release Documentation Required

This PR is labeled minor or major and requires documentation updates:

  • Changelog entry - Add a blog post in website/blog/YYYY-MM-DD-feature-name.mdx
  • Roadmap update - Update website/src/data/roadmap.js with the new milestone

Alternatively: If this change doesn't require release documentation, remove the minor or major label.

@github-actions
Copy link
Copy Markdown

Warning

Release Documentation Required

This PR is labeled minor or major and requires documentation updates:

  • Changelog entry - Add a blog post in website/blog/YYYY-MM-DD-feature-name.mdx
  • Roadmap update - Update website/src/data/roadmap.js with the new milestone

Alternatively: If this change doesn't require release documentation, remove the minor or major label.

coderabbitai[bot]
coderabbitai bot previously approved these changes Jan 14, 2026
@github-actions
Copy link
Copy Markdown

Warning

Release Documentation Required

This PR is labeled minor or major and requires documentation updates:

  • Changelog entry - Add a blog post in website/blog/YYYY-MM-DD-feature-name.mdx
  • Roadmap update - Update website/src/data/roadmap.js with the new milestone

Alternatively: If this change doesn't require release documentation, remove the minor or major label.

- Add examples/dotenv/ demonstrating .env file support
- Custom commands show-env and check-var to demonstrate functionality
- Example .env, .env.local, and .env.dev files
- Fix terraform.go to use GetCaseSensitiveEnvVars() after merge

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@osterman osterman requested a review from a team as a code owner January 16, 2026 02:14
@github-actions github-actions bot added size/xl Extra large size PR and removed size/l Large size PR labels Jan 16, 2026
@github-actions
Copy link
Copy Markdown

Warning

Release Documentation Required

This PR is labeled minor or major and requires documentation updates:

  • Changelog entry - Add a blog post in website/blog/YYYY-MM-DD-feature-name.mdx
  • Roadmap update - Update website/src/data/roadmap.js with the new milestone

Alternatively: If this change doesn't require release documentation, remove the minor or major label.

@mergify
Copy link
Copy Markdown

mergify bot commented Jan 16, 2026

Warning

This PR exceeds the recommended limit of 1,000 lines.

Large PRs are difficult to review and may be rejected due to their size.

Please verify that this PR does not address multiple issues.
Consider refactoring it into smaller, more focused PRs to facilitate a smoother review process.

- Add blog post announcing .env file support feature
- Add milestone to DX initiative in roadmap
- Link to PR #1930

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add structured form configuration with env.vars and env.files
- Document all env.files options (enabled, paths, parents)
- Add sections for glob patterns, parent walking, early loading
- Embed the dotenv example using EmbedExample component

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
- Add dotenv to TAGS_MAP under DX category
- Add dotenv to DOCS_MAP linking to env configuration docs

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@mergify
Copy link
Copy Markdown

mergify bot commented Jan 17, 2026

💥 This pull request now has conflicts. Could you fix it @osterman? 🙏

@mergify mergify bot added the conflict This PR has conflicts label Jan 17, 2026
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: 2

🤖 Fix all issues with AI agents
In `@pkg/config/load.go`:
- Around line 1286-1307: The early .env loader (loadEnvFilesEarly) currently
calls env.LoadEnvFiles(basePath, paths) which ignores the env.files.parents
flag; change it to call env.LoadFromDirectory(basePath, paths,
v.GetBool("env.files.parents"), v.GetString("repo_root")) so parent traversal is
honored during early loading and profile detection, ensuring it uses the same
repoRoot and parents flag as the main loader; preserve returned envVars,
loadedFiles and existing error handling (log.Debug and behavior) after the
switch.
- Around line 1251-1278: The code currently treats presence of "vars" or "files"
keys (via viper-lowercased envMap["vars"/"files"]) as indicating structured form
even when those keys are flat string values; update the detection logic to
verify the actual types before assuming structured form: check envMap["vars"] is
a map[string]any (or can be decoded by GetStringMapString) and envMap["files"]
is a map[string]any or a slice/struct expected by atmosConfig.Env.Files, only
then set hasVars/hasFiles true and use
GetStringMapString("env.vars")/GetBool/GetStringSlice; otherwise fall back to
the flat form and populate atmosConfig.Env.Vars from
v.GetStringMapString("env"). Ensure you reference and update the existing
symbols envRaw, envMap, envMap["vars"], envMap["files"], hasVars/hasFiles,
GetStringMapString("env.vars"), and atmosConfig.Env.Files handling so legacy
string values are not lost.
🧹 Nitpick comments (3)
pkg/schema/schema.go (1)

264-273: Fallback logic may be redundant.

Both GetCaseSensitiveMap("env.vars") and GetCaseSensitiveMap("env") return a.Env.Vars per lines 252-253. The fallback on line 272 would only differ if the casemap has different keys registered for each path. If that's the intent, it works. Otherwise, the fallback is effectively a no-op when vars is empty.

Consider adding a brief comment explaining when the fallback would return different results, or simplify if both paths always return identical data.

website/blog/2025-01-15-dotenv-file-support.mdx (1)

37-56: Consider a brief note about .env secrets and gitignore.
A short reminder to avoid committing .env / .env.local would help readers.

✏️ Optional doc tweak
 Environment variables are now available to all Atmos commands including Terraform, Helmfile, workflows, and custom commands.
+
+Note: `.env` files often contain secrets. Add `.env.local` to `.gitignore` and avoid committing sensitive values.
website/docs/cli/configuration/env.mdx (1)

126-209: Optional: add a brief note about missing files being skipped.
This matches the “load only when they exist” behavior and prevents confusion.

✏️ Optional doc tweak
 Enable loading in `atmos.yaml`:
 
 <File title="atmos.yaml">
 ```yaml
 env:
   files:
     enabled: true
     paths:
       - .env
+ +Missing files are skipped silently; only existing matches are loaded. ```

Comment on lines +1251 to +1278
// Check if env section uses structured or flat form.
envRaw := v.Get("env")
if envRaw == nil {
return
}

envMap, ok := envRaw.(map[string]any)
if !ok {
return
}

// Detect structured form by presence of "vars" or "files" keys.
_, hasVars := envMap["vars"]
_, hasFiles := envMap["files"]

if hasVars || hasFiles {
// Structured form: env.vars + env.files.
if hasVars {
atmosConfig.Env.Vars = v.GetStringMapString("env.vars")
}
// Files config is parsed via mapstructure into atmosConfig.Env.Files.
atmosConfig.Env.Files.Enabled = v.GetBool("env.files.enabled")
atmosConfig.Env.Files.Paths = v.GetStringSlice("env.files.paths")
atmosConfig.Env.Files.Parents = v.GetBool("env.files.parents")
} else {
// Flat form: all keys are vars (backward compatibility).
atmosConfig.Env.Vars = v.GetStringMapString("env")
}
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

🧩 Analysis chain

🏁 Script executed:

# First, let's examine the parseEnvConfig function and surrounding context in load.go
cd cloudposse/atmos && head -1300 pkg/config/load.go | tail -100

Repository: cloudposse/atmos

Length of output: 126


🏁 Script executed:

# Check how Viper is configured in load.go - look for case sensitivity settings
rg -n "viper\.|SetEnvKeyReplacer|SetEnvPrefix" pkg/config/load.go -A 2 -B 2

Repository: cloudposse/atmos

Length of output: 10707


🏁 Script executed:

# Search for tests of parseEnvConfig
fd -t f "load.*test.go" pkg/config/ && rg -n "parseEnvConfig\|Test.*Env" pkg/config/ | head -30

Repository: cloudposse/atmos

Length of output: 401


🏁 Script executed:

# Check Viper's default behavior - search for any documentation or config about case handling
rg -n "caseSensitive|CaseSensitive" pkg/config/load.go -B 3 -A 3

Repository: cloudposse/atmos

Length of output: 2177


🏁 Script executed:

# Look for legacy env config examples or tests
rg -n "env:\s*\n\s+VARS|env:\s*\n\s+FILES" . -i --type yaml

Repository: cloudposse/atmos

Length of output: 251


🏁 Script executed:

# Look at the parseEnvConfig function in detail
sed -n '1250,1280p' pkg/config/load.go

Repository: cloudposse/atmos

Length of output: 988


🏁 Script executed:

# Check the preserveCaseSensitiveMaps and casemap extraction to understand case preservation
sed -n '1329,1375p' pkg/config/load.go

Repository: cloudposse/atmos

Length of output: 1923


🏁 Script executed:

# Search for tests that exercise parseEnvConfig with legacy flat env configs
rg -n "parseEnvConfig\|env:" pkg/config/load_test.go -A 5 -B 5 | head -80

Repository: cloudposse/atmos

Length of output: 42


🏁 Script executed:

# Check the env package for parent-walking logic mentioned in the scratchpad
fd -t f "env.go" pkg/ internal/ && rg -n "Parent\|parents" pkg/env/ internal/env/ 2>/dev/null | head -30

Repository: cloudposse/atmos

Length of output: 228


🏁 Script executed:

# Check Viper's behavior with case sensitivity and Get() method
web_search
Viper library case sensitivity behavior for map keys Get method

Repository: cloudposse/atmos

Length of output: 154


🏁 Script executed:

# Check env.go for parent-walking logic
sed -n '1,100p' pkg/env/env.go

Repository: cloudposse/atmos

Length of output: 3224


🏁 Script executed:

# Look for tests that cover env config parsing with legacy formats
rg -n "env:" pkg/config/load_test.go -A 10 | head -60

Repository: cloudposse/atmos

Length of output: 1829


🏁 Script executed:

# Check if there are YAML test fixtures with env configs
fd -t f ".*\.yaml" pkg/config | xargs rg "^env:" 2>/dev/null | head -20

Repository: cloudposse/atmos

Length of output: 42


🏁 Script executed:

# Understand how v.Get() works with Viper - look for usage patterns
rg -n "v\.Get\(" pkg/config/load.go -B 2 -A 5 | head -50

Repository: cloudposse/atmos

Length of output: 634


🏁 Script executed:

# Check how case preservation affects parseEnvConfig - does it restore case before parsing?
rg -n "parseEnvConfig\|CaseMaps\|Case.*Map" pkg/config/load.go | head -30

Repository: cloudposse/atmos

Length of output: 42


🏁 Script executed:

# Look at the order of operations: when is parseEnvConfig called vs when are case maps applied?
sed -n '200,400p' pkg/config/load.go | grep -n "parseEnvConfig\|preserveCaseSensitiveMaps\|Unmarshal"

Repository: cloudposse/atmos

Length of output: 275


🏁 Script executed:

# Check the schema.AtmosConfiguration to understand Env field structure
rg -n "type.*Env\s|Env.*struct" pkg/schema/ -A 15 | head -50

Repository: cloudposse/atmos

Length of output: 3762


🏁 Script executed:

# Search for any legacy env configs in tests or examples with VARS or FILES as flat keys
rg -n "VARS:|FILES:" . --type yaml -i -B 2 -A 2

Repository: cloudposse/atmos

Length of output: 50373


🏁 Script executed:

# Check if there are any integration tests showing backward compatibility for env
rg -n "backward|legacy|compat" pkg/config/load_test.go -i -B 2 -A 5

Repository: cloudposse/atmos

Length of output: 771


🏁 Script executed:

# Let's understand the exact behavior - check what v.Get returns for structured vs flat env
sed -n '1250,1280p' pkg/config/load.go

Repository: cloudposse/atmos

Length of output: 988


🏁 Script executed:

# Check loadEnvFilesEarly to see if it ignores parents
sed -n '1285,1330p' pkg/config/load.go

Repository: cloudposse/atmos

Length of output: 1389


🏁 Script executed:

# Look for how env vars with case are preserved - check the CaseMap usage
rg -n "CaseMap\|CaseMaps" pkg/config/load.go -A 3 -B 3 | head -60

Repository: cloudposse/atmos

Length of output: 42


🏁 Script executed:

# Check if there are any existing tests that show env parsing with edge cases
rg -n "env.*vars\|env.*files" pkg/config/load_test.go -B 5 -A 5 | head -80

Repository: cloudposse/atmos

Length of output: 42


🏁 Script executed:

# Check the order of operations - when is parseEnvConfig called vs preserveCaseSensitiveMaps
sed -n '120,160p' pkg/config/load.go

Repository: cloudposse/atmos

Length of output: 1366


🏁 Script executed:

# Search for any env config in test fixtures with VARS or FILES as actual flat keys
find tests/ -name "*.yaml" -type f -exec grep -l "^env:" {} \; | head -5 | xargs grep -A 10 "^env:"

Repository: cloudposse/atmos

Length of output: 3733


🏁 Script executed:

# Check the schema to understand what Env field structure expects
rg -n "type Env struct|type EnvFilesConfig" pkg/schema/ -A 10

Repository: cloudposse/atmos

Length of output: 904


🏁 Script executed:

# Check the actual order of operations for parseEnvConfig and preserveCaseSensitiveMaps
sed -n '100,160p' pkg/config/load.go | grep -n "parseEnvConfig\|preserveCaseSensitiveMaps\|Unmarshal"

Repository: cloudposse/atmos

Length of output: 42


🏁 Script executed:

# Look for any actual usage or documentation of env config structure with VARS/FILES
rg -n "env:" tests/ --type yaml -A 15 | grep -i "vars:\|files:" | head -30

Repository: cloudposse/atmos

Length of output: 2024


🏁 Script executed:

# Check if there are integration tests that verify env config parsing behavior
rg -n "TestParseEnvConfig\|env.*config" pkg/config/load_test.go -i

Repository: cloudposse/atmos

Length of output: 42


🏁 Script executed:

# Look at the schema to understand legacy vs new form
sed -n '54,75p' pkg/schema/schema.go

Repository: cloudposse/atmos

Length of output: 1169


🏁 Script executed:

# Check if case maps are actually applied back to Viper or just tracked
rg -n "CaseMap\|RestoreCase\|applyCase" pkg/config/load.go pkg/casemap/ -i

Repository: cloudposse/atmos

Length of output: 112


🏁 Script executed:

# Check loadEnvFilesEarly - does it use parents option?
sed -n '1285,1328p' pkg/config/load.go

Repository: cloudposse/atmos

Length of output: 1244


🏁 Script executed:

# Check env.LoadEnvFiles to see if it respects the parents flag
rg -n "LoadEnvFiles\|func.*parents" pkg/env/env.go -A 5 -B 2 | head -40

Repository: cloudposse/atmos

Length of output: 42


🏁 Script executed:

# Check if case maps are used/restored before parseEnvConfig
sed -n '100,150p' pkg/config/load.go

Repository: cloudposse/atmos

Length of output: 1710


🏁 Script executed:

# Check if env.LoadEnvFiles signature includes parents parameter
rg -n "func LoadEnvFiles" pkg/env/env.go -A 10

Repository: cloudposse/atmos

Length of output: 42


🏁 Script executed:

# Let's verify the actual issue - create a test to see what happens with flat env with VARS key
rg -n "TestParseEnv\|parseEnv" pkg/config/load_test.go

Repository: cloudposse/atmos

Length of output: 42


🏁 Script executed:

# Check exact order: is parseEnvConfig called before or after preserveCaseSensitiveMaps?
sed -n '100,160p' pkg/config/load.go

Repository: cloudposse/atmos

Length of output: 2140


🏁 Script executed:

# Look at what GetStringMapString does when value is not a map
rg -n "GetStringMapString" pkg/config/load.go -B 3 -A 3

Repository: cloudposse/atmos

Length of output: 919


🏁 Script executed:

# Get the full InitCliConfig function to see order of operations
sed -n '100,160p' pkg/config/load.go

Repository: cloudposse/atmos

Length of output: 2140


🏁 Script executed:

# Search for preserveCaseSensitiveMaps call timing
rg -n "preserveCaseSensitiveMaps" pkg/config/load.go -B 5 -A 5

Repository: cloudposse/atmos

Length of output: 1346


🏁 Script executed:

# Verify: does GetStringMapString return empty map for non-existent paths?
python3 << 'EOF'
# Test Viper behavior conceptually
# When you call GetStringMapString("env.vars") on a config without env.vars but with env.vars as a flat key,
# does it return the flat key or empty?

# Viper documentation says GetStringMapString converts the value at that path to map[string]string
# If the path doesn't exist, it returns an empty map

print("Viper GetStringMapString behavior:")
print("- If path 'env.vars' doesn't exist as a nested map, returns empty map {}")
print("- If path 'env' exists with flat key 'vars' (string value), that's not a nested 'env.vars' path")
print("- So GetStringMapString('env.vars') returns empty {}")
print("\nThis means the review comment's concern is valid:")
print("1. Config has: env: { VARS: value }")
print("2. Viper lowercases to: env: { vars: value }")
print("3. Code checks hasVars := envMap['vars'] → true (key exists)")
print("4. Code calls GetStringMapString('env.vars') → empty (no nested map at that path)")
print("5. Result: atmosConfig.Env.Vars = {} (LOST)")
EOF

Repository: cloudposse/atmos

Length of output: 635


🏁 Script executed:

# Check the exact sequence in InitCliConfig
sed -n '95,150p' pkg/config/load.go

Repository: cloudposse/atmos

Length of output: 1814


🏁 Script executed:

# Verify one more detail: does the case preservation actually help if called before parseEnvConfig?
# Check if CaseMaps are used to restore values
rg -n "CaseMap\|ApplyCase\|Restore" pkg/config/ --type go

Repository: cloudposse/atmos

Length of output: 42


🏁 Script executed:

# Final verification: test scenario with legacy flat env VARS key
python3 << 'EOF'
# Scenario: atmos.yaml contains:
# env:
#   GITHUB_TOKEN: "secret"
#   AWS_KEY: "key123"

# Viper loads and lowercases:
# env:
#   github_token: "secret"
#   aws_key: "key123"

# Current code does:
# envMap = v.Get("env") → {github_token: "secret", aws_key: "key123"}
# _, hasVars = envMap["vars"] → false (no "vars" key)
# _, hasFiles = envMap["files"] → false (no "files" key)
# if hasVars || hasFiles → false
# else → v.GetStringMapString("env") ✓ WORKS

# But with legacy config containing VARS/FILES keys:
# env:
#   VARS:
#     GITHUB_TOKEN: "secret"
#   FILES:
#     enabled: true

# Viper lowercases:
# env:
#   vars:  ← THIS IS A STRING VALUE, not a map!
#     github_token: "secret"
#   files:  ← THIS IS A BOOL or struct, not a map!
#     enabled: true

# Current code does:
# envMap = v.Get("env") → {vars: {...}, files: {...}}
# _, hasVars = envMap["vars"] → true (KEY EXISTS)
# _, hasFiles = envMap["files"] → true (KEY EXISTS)
# if hasVars || hasFiles → true ✓ assumes structured
# v.GetStringMapString("env.vars") → empty {} ✗ LOST DATA

print("CONFIRMED: The review comment correctly identifies a real backward compatibility bug.")
print("\nLegacy config with VARS/FILES as flat keys will lose data:")
print("- hasVars/hasFiles check succeeds (keys exist)")
print("- GetStringMapString fails (values aren't nested maps)")
print("- Result: silent data loss")
EOF

Repository: cloudposse/atmos

Length of output: 334


Check value types before treating env config as structured form.

Legacy env configs with VARS or FILES as flat keys will be misclassified as structured. Because Viper lowercases keys to vars and files, the presence check succeeds even when those keys contain string values, not nested maps. Then GetStringMapString("env.vars") returns empty, silently losing the data.

✅ Proposed fix
-	_, hasVars := envMap["vars"]
-	_, hasFiles := envMap["files"]
-
-	if hasVars || hasFiles {
+	varsRaw, hasVars := envMap["vars"]
+	filesRaw, hasFiles := envMap["files"]
+	_, varsIsMap := varsRaw.(map[string]any)
+	_, filesIsMap := filesRaw.(map[string]any)
+
+	// If VARS/FILES are legacy flat keys (non-map), keep flat parsing.
+	if (hasVars && !varsIsMap) || (hasFiles && !filesIsMap) {
+		atmosConfig.Env.Vars = v.GetStringMapString("env")
+		return
+	}
+
+	if varsIsMap || filesIsMap {
 		// Structured form: env.vars + env.files.
 		if hasVars {
 			atmosConfig.Env.Vars = v.GetStringMapString("env.vars")
 		}
🤖 Prompt for AI Agents
In `@pkg/config/load.go` around lines 1251 - 1278, The code currently treats
presence of "vars" or "files" keys (via viper-lowercased envMap["vars"/"files"])
as indicating structured form even when those keys are flat string values;
update the detection logic to verify the actual types before assuming structured
form: check envMap["vars"] is a map[string]any (or can be decoded by
GetStringMapString) and envMap["files"] is a map[string]any or a slice/struct
expected by atmosConfig.Env.Files, only then set hasVars/hasFiles true and use
GetStringMapString("env.vars")/GetBool/GetStringSlice; otherwise fall back to
the flat form and populate atmosConfig.Env.Vars from
v.GetStringMapString("env"). Ensure you reference and update the existing
symbols envRaw, envMap, envMap["vars"], envMap["files"], hasVars/hasFiles,
GetStringMapString("env.vars"), and atmosConfig.Env.Files handling so legacy
string values are not lost.

Comment on lines +1286 to +1307
// Check if env.files is enabled (quick bootstrap read).
if !v.GetBool("env.files.enabled") {
return nil
}

paths := v.GetStringSlice("env.files.paths")
if len(paths) == 0 {
paths = []string{".env"} // Default pattern.
}

// Determine base path for loading.
if basePath == "" {
basePath = v.GetString("base_path")
}
if basePath == "" {
basePath = "."
}

// Load .env files from base path.
envVars, loadedFiles, err := env.LoadEnvFiles(basePath, paths)
if err != nil {
log.Debug("Failed to load .env files", "error", err)
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

🧩 Analysis chain

🏁 Script executed:

# First, let's understand the context around lines 1286-1307 in pkg/config/load.go
cd /tmp && head -1320 /dev/stdin | tail -100 <<'EOF'
File will be read next
EOF

# Actually, let me read the file directly
head -1320 pkg/config/load.go | tail -100

Repository: cloudposse/atmos

Length of output: 247


🏁 Script executed:

# Find the function definition around line 1286
sed -n '1250,1320p' pkg/config/load.go | cat -n

Repository: cloudposse/atmos

Length of output: 2763


🏁 Script executed:

# Look for env.files.parents usage in the codebase
rg "env\.files\.parents" -A 3 -B 3

Repository: cloudposse/atmos

Length of output: 1157


🏁 Script executed:

# Find the env.LoadEnvFiles function signature
rg "func.*LoadEnvFiles" -A 5

Repository: cloudposse/atmos

Length of output: 633


🏁 Script executed:

# Search for the main loader that handles .env files
rg "LoadEnvFiles" -B 5 -A 10

Repository: cloudposse/atmos

Length of output: 8528


🏁 Script executed:

# Check the entire function that contains lines 1286-1307
sed -n '1200,1350p' pkg/config/load.go

Repository: cloudposse/atmos

Length of output: 4722


🏁 Script executed:

# Find LoadFromDirectory implementation
rg "LoadFromDirectory" -A 20

Repository: cloudposse/atmos

Length of output: 10461


🏁 Script executed:

# Search for where the parents flag is actually used in env loading
rg "\.Parents" pkg/env/ -B 3 -A 3

Repository: cloudposse/atmos

Length of output: 42


🏁 Script executed:

# Check if there's a broader function that respects parents
rg "parents" pkg/env/file.go -B 2 -A 5

Repository: cloudposse/atmos

Length of output: 834


🏁 Script executed:

# Look for where the main loader (after profile detection) loads env files
rg "loadEnvFiles" -i -B 3 -A 3

Repository: cloudposse/atmos

Length of output: 7519


Early .env loader doesn't respect env.files.parents flag.

Currently, loadEnvFilesEarly uses env.LoadEnvFiles(basePath, paths), which only loads from a single directory and has no parents support. The main loader later uses env.LoadFromDirectory(dir, patterns, parents, repoRoot) which correctly walks up to the repository root when env.files.parents: true. Since early loading happens before profile detection, variables like ATMOS_PROFILE in parent directories won't be seen, causing the behavior to diverge from the main loader's handling of the same flag.

Update the early loader to use LoadFromDirectory with the parents flag (and repoRoot from config) so parent-walked .env files are honored during profile detection.

🤖 Prompt for AI Agents
In `@pkg/config/load.go` around lines 1286 - 1307, The early .env loader
(loadEnvFilesEarly) currently calls env.LoadEnvFiles(basePath, paths) which
ignores the env.files.parents flag; change it to call
env.LoadFromDirectory(basePath, paths, v.GetBool("env.files.parents"),
v.GetString("repo_root")) so parent traversal is honored during early loading
and profile detection, ensuring it uses the same repoRoot and parents flag as
the main loader; preserve returned envVars, loadedFiles and existing error
handling (log.Debug and behavior) after the switch.

@mergify
Copy link
Copy Markdown

mergify bot commented Jan 20, 2026

💥 This pull request now has conflicts. Could you fix it @osterman? 🙏

@mergify mergify bot added the conflict This PR has conflicts label Jan 20, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conflict This PR has conflicts minor New features that do not break anything size/xl Extra large size PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant