Fix: Preserve exit codes from shell commands and workflows#1660
Fix: Preserve exit codes from shell commands and workflows#1660
Conversation
📝 WalkthroughWalkthroughPreserves and propagates subprocess exit codes across the CLI: introduces typed exit-code propagation and an OsExit hook, updates shell/terraform/workflow error handling to return and honor real exit codes, centralizes exit plumbing through the errors package, and adds tests/fixtures validating exit-code behavior and messages. Changes
Sequence Diagram(s)sequenceDiagram
participant User as CLI user
participant CLI as atmos CLI
participant Shell as Shell runner
participant ErrPkg as errors package (OsExit)
participant OS as OS process
User->>CLI: invoke command (workflow/terraform/custom)
CLI->>Shell: execute subprocess
Shell->>OS: spawn process
OS-->>Shell: exits with code N
alt N == 0
Shell-->>CLI: success (nil)
CLI->>ErrPkg: normal flow (no exit)
else N != 0
Shell->>Shell: extract code, wrap as ExitCodeError{Code:N}
Shell-->>CLI: return ExitCodeError
CLI->>ErrPkg: CheckErrorPrintAndExit / IsKnownWorkflowError
ErrPkg->>ErrPkg: errors.As -> extract Code N
ErrPkg-->>OS: OsExit(N)
end
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Suggested labels
Suggested reviewers
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
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. Comment |
|
@coderabbitai full review of all changed files in this PR |
🧩 Analysis chain🏁 Script executed: Length of output: 430 🏁 Script executed: Length of output: 65282 🏁 Script executed: Length of output: 2281 🏁 Script executed: Length of output: 40196 🏁 Script executed: Length of output: 89 🏁 Script executed: Length of output: 3446 🏁 Script executed: Length of output: 183 🏁 Script executed: Length of output: 24401 🏁 Script executed: Length of output: 874 🏁 Script executed: Length of output: 1587 @aknysh, I'll provide a comprehensive review of all changes in this PR. 📋 Overall AssessmentThis is a well-structured PR that addresses a critical issue: preserving exit codes from shell commands and workflows. The implementation is thorough, properly tested, and includes necessary documentation updates. 🔍 Detailed File Review1. pkg/utils/shell_utils.go ⭐ Core FixChanges:
Review: err = runner.Run(context.TODO(), parser)
if err != nil {
// Check if the error is an interp.ExitStatus and preserve the exit code.
var exitErr interp.ExitStatus
if errors.As(err, &exitErr) {
return errUtils.ExitCodeError{Code: int(exitErr)}
}
return err
}✅ Strengths:
2. errors/error_funcs.go ⭐ Critical IntegrationChanges:
Review: // Check for ExitCodeError (from ShellRunner preserving interp.ExitStatus)
var exitCodeErr ExitCodeError
if errors.As(err, &exitCodeErr) {
Exit(exitCodeErr.Code)
}✅ Strengths:
3. cmd/workflow.go ⭐ Workflow IntegrationChanges:
Review: if e.IsKnownWorkflowError(err) {
var exitCodeErr errUtils.ExitCodeError
if errors.As(err, &exitCodeErr) {
errUtils.Exit(exitCodeErr.Code)
}
errUtils.Exit(1)
}✅ Strengths:
4. internal/exec/workflow_utils.go ⭐ Error Chain PreservationChanges:
Review: return errors.Join(ErrWorkflowStepFailed, err)✅ Strengths:
5. internal/exec/terraform.go ⭐ Terraform Exit Code FixChanges:
Review: if exitCode == 0 {
return nil
}
if exitCode == 2 {
// Exit code 2 is success for terraform plan but we must preserve it
return errUtils.ExitCodeError{Code: 2}
}✅ Strengths:
Important Context:
6. tests/test-cases/exit-codes.yaml ⭐ Comprehensive Test CoverageNew file with 6 test cases:
✅ Strengths:
7. tests/test-cases/workflows.yaml ⭐ Updated TestChanges:
Review: # Exit code 127 is the standard Unix/POSIX exit code for "command not found".
# This differs from exit code 1 (generic error) and provides more specific information.
# The workflow correctly preserves the shell's actual exit code rather than converting it to 1.
exit_code: 127✅ Strengths:
8. tests/test-cases/complete.yamlChanges:
✅ Consistent with the new 9. tests/snapshots/ - Updated Golden FilesChanges:
✅ Necessary for test consistency 10. .goreleaser.yml - Build ImprovementsChanges:
✅ Good housekeeping:
11. go.mod/go.sum - Dependency UpdatesChanges:
✅ Standard maintenance - minor version updates are safe 🎯 Key Strengths of This PR
🔧 Potential Considerations
✅ RecommendationsMinor Suggestions:
// ExitCodeError preserves a specific exit code from a shell command.
// This allows proper exit code propagation through error chains.
type ExitCodeError struct {
Code int
}
Test Coverage:✅ Excellent - Tests cover:
🎉 ConclusionThis is a well-engineered solution to a critical issue. The implementation:
The PR is ready for merge with high confidence. The fix addresses the stated problem comprehensively and the test coverage ensures no regressions. Excellent work! 🚀 ✅ Actions performedFull review triggered. |
There was a problem hiding this comment.
Actionable comments posted: 1
Caution
Some comments are outside the diff and can’t be posted inline due to platform limitations.
⚠️ Outside diff range comments (1)
errors/error_funcs.go (1)
54-67: Fix doc comment and comment punctuation to satisfy linters and reflect behavior.Function may exit with preserved non‑zero codes now; also end comments with periods (godot).
-// CheckErrorPrintAndExit prints an error message and exits with exit code 1. +// CheckErrorPrintAndExit prints an error message and exits with the best available exit code. - // Check for ExitCodeError (from ShellRunner preserving interp.ExitStatus) + // Check for ExitCodeError (from ShellRunner preserving interp.ExitStatus).
🧹 Nitpick comments (1)
.goreleaser.yml (1)
19-25: Add go mod tidy to pre-build hooks to avoid surprise diffs and ensure sums are current.Cleanup alone is fine, but releases often expect tidy state.
sh -c 'if [ "$GITHUB_ACTIONS" = "true" ]; then echo "Freeing disk space..." df -h / sudo rm -rf /usr/local/lib/android /usr/share/dotnet /opt/ghc 2>/dev/null || true df -h / fi' + - go mod tidyConfirm other CI steps don’t already tidy before goreleaser; if they do, feel free to skip this.
📜 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.
⛔ Files ignored due to path filters (1)
go.sumis excluded by!**/*.sum
📒 Files selected for processing (13)
.goreleaser.yml(2 hunks)cmd/workflow.go(2 hunks)errors/error_funcs.go(1 hunks)go.mod(2 hunks)internal/exec/terraform.go(1 hunks)internal/exec/workflow_utils.go(2 hunks)pkg/utils/shell_utils.go(2 hunks)tests/fixtures/scenarios/exit-codes/atmos.yaml(1 hunks)tests/fixtures/scenarios/exit-codes/stacks/workflows/workflows.yaml(1 hunks)tests/snapshots/TestCLICommands_atmos_circuit-breaker.stderr.golden(1 hunks)tests/test-cases/complete.yaml(1 hunks)tests/test-cases/exit-codes.yaml(1 hunks)tests/test-cases/workflows.yaml(1 hunks)
🧰 Additional context used
📓 Path-based instructions (7)
**/*.go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
**/*.go: All code must pass golangci-lint checks
Follow Go error handling idioms and use meaningful error messages
Wrap errors with context using fmt.Errorf("context: %w", err)
Consider custom error types for domain-specific errors
Follow standard Go coding style; run gofmt and goimports
Use snake_case for environment variables
Document complex logic with inline comments
**/*.go: All Go comments must end with periods; applies to single-line, multi-line, inline, and documentation comments (golangci-lint godot).
Group imports into three sections (stdlib, 3rd-party, Atmos), separated by blank lines; sort alphabetically within each group; preserve existing aliases.
Configuration loading must use Viper with precedence CLI → ENV → files → defaults; bind config name atmos and add path, AutomaticEnv, and ATMOS prefix.
All errors must be wrapped using static errors (defined in errors/errors.go); use errors.Join for multiple errors; fmt.Errorf with %w for context; use errors.Is for checks; never compare error strings.
Distinguish structured logging from UI output: UI prompts/status/errors to stderr; data/results to stdout; never use logging for UI.
Most text UI must go to stderr; only data/results to stdout; prefer utils.PrintfMessageToTUI for UI messages.
All new configurations must support Go templating using existing utilities and available template functions.
Prefer SDKs over external binaries for cross-platform support; use filepath/os/runtime for portability.
For non-standard execution paths, capture telemetry via telemetry.CaptureCmd or telemetry.CaptureCmdString without user data.
80% minimum coverage on new/changed lines and include unit tests for new features; add integration tests for CLI using tests/ fixtures.
Always bind environment variables with viper.BindEnv and provide ATMOS_ alternatives for every env var.
Use structured logging with levels (Fatal>Error>Warn>Debug>Trace); avoid string interpolation and ensure logging does not affect execution.
Prefer re...
Files:
internal/exec/terraform.gointernal/exec/workflow_utils.goerrors/error_funcs.gopkg/utils/shell_utils.gocmd/workflow.go
**/!(*_test).go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
Document all exported functions, types, and methods with Go doc comments
Add
defer perf.Track()to all public functions and critical private functions; include a blank line after the call; use package-prefixed names; pass atmosConfig when present, else nil.
Files:
internal/exec/terraform.gointernal/exec/workflow_utils.goerrors/error_funcs.gopkg/utils/shell_utils.gocmd/workflow.go
internal/exec/**/*.go
📄 CodeRabbit inference engine (CLAUDE.md)
Place command business logic in internal/exec/ (separate from cmd/ wiring).
Files:
internal/exec/terraform.gointernal/exec/workflow_utils.go
tests/{test-cases,testdata}/**
📄 CodeRabbit inference engine (CLAUDE.md)
Never modify files under tests/test-cases/ or tests/testdata/ without explicit instruction; these are golden snapshots.
Files:
tests/test-cases/complete.yamltests/test-cases/exit-codes.yamltests/test-cases/workflows.yaml
pkg/**/*.go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
Place business logic in pkg rather than in cmd
Files:
pkg/utils/shell_utils.go
cmd/**/*.go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
cmd/**/*.go: Use Cobra's recommended command structure with a root command and subcommands
Implement each CLI command in a separate file under cmd/
Use Viper for managing configuration, environment variables, and flags in the CLI
Keep separation of concerns between CLI interface (cmd) and business logic
Use kebab-case for command-line flags
Provide comprehensive help text for all commands and flags
Include examples in Cobra command help
Use Viper for configuration management; support files, env vars, and flags with precedence flags > env > config > defaults
Follow single responsibility; separate command interface from business logic
Provide meaningful user feedback and include progress indicators for long-running operations
Provide clear error messages to users and troubleshooting hints where appropriate
cmd/**/*.go: Use utils.PrintfMarkdown() to render embedded markdown content for CLI command help/examples.
One Cobra command per file in cmd/; keep files focused and small.
Files:
cmd/workflow.go
go.{mod,sum}
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
go.{mod,sum}: Manage dependencies with Go modules
Keep dependencies up to date
Files:
go.mod
🧠 Learnings (7)
📚 Learning: 2025-06-02T14:12:02.710Z
Learnt from: milldr
PR: cloudposse/atmos#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:
tests/test-cases/exit-codes.yamlinternal/exec/workflow_utils.goerrors/error_funcs.gocmd/workflow.go
📚 Learning: 2025-02-03T06:00:11.419Z
Learnt from: samtholiya
PR: cloudposse/atmos#959
File: cmd/describe_config.go:20-20
Timestamp: 2025-02-03T06:00:11.419Z
Learning: Commands should use `PrintErrorMarkdownAndExit` with empty title and suggestion (`"", err, ""`) for general error handling. Specific titles like "Invalid Usage" or "File Not Found" should only be used for validation or specific error scenarios.
Applied to files:
errors/error_funcs.go
📚 Learning: 2025-09-13T18:06:07.674Z
Learnt from: samtholiya
PR: cloudposse/atmos#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/utils/shell_utils.go
📚 Learning: 2025-06-02T14:12:02.710Z
Learnt from: milldr
PR: cloudposse/atmos#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 the error 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.
Applied to files:
cmd/workflow.go
📚 Learning: 2025-09-23T02:30:42.362Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-09-23T02:30:42.362Z
Learning: Applies to cmd/**/*.go : Use Cobra's recommended command structure with a root command and subcommands
Applied to files:
cmd/workflow.go
📚 Learning: 2025-09-23T02:30:42.362Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-09-23T02:30:42.362Z
Learning: Applies to cmd/**/*.go : Provide clear error messages to users and troubleshooting hints where appropriate
Applied to files:
cmd/workflow.go
📚 Learning: 2025-10-16T15:18:00.319Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-16T15:18:00.319Z
Learning: Applies to **/*.go : Ensure Atmos remains portable across Linux, macOS, and Windows; use filepath.Join, os.PathSeparator, and build constraints when necessary.
Applied to files:
.goreleaser.yml
🧬 Code graph analysis (4)
internal/exec/terraform.go (1)
errors/errors.go (1)
ExitCodeError(379-381)
errors/error_funcs.go (1)
errors/errors.go (1)
ExitCodeError(379-381)
pkg/utils/shell_utils.go (1)
errors/errors.go (1)
ExitCodeError(379-381)
cmd/workflow.go (2)
errors/errors.go (1)
ExitCodeError(379-381)errors/error_funcs.go (1)
Exit(81-83)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: Build (macos-latest, macos)
- GitHub Check: Build (windows-latest, windows)
- GitHub Check: Build (ubuntu-latest, linux)
- GitHub Check: Run pre-commit hooks
- GitHub Check: Analyze (go)
- GitHub Check: Lint (golangci)
- GitHub Check: Summary
🔇 Additional comments (17)
pkg/utils/shell_utils.go (2)
18-18: Import alias addition looks good.
103-103: Doc comment update acknowledged. LGTM.internal/exec/workflow_utils.go (2)
5-5: Importing errors for errors.Join is appropriate. LGTM.
207-209: Good: preserve sentinel and cause with errors.Join.This keeps ErrWorkflowStepFailed for errors.Is while retaining the original error/exit code for errors.As. Nicely aligned with exit-code propagation.
Optionally add/extend a test that:
- asserts errors.Is(err, ErrWorkflowStepFailed) is true, and
- extracts ExitCodeError via errors.As when the step fails with a non-zero shell exit. Based on learnings.
.goreleaser.yml (1)
50-54: Trimpath addition is a solid reproducibility win. LGTM.go.mod (3)
147-156: No alignment concerns detected—aws-sdk-go-v2 dependencies are correctly resolved.The dependency tree shows all aws-sdk-go-v2 packages properly resolved and indirect dependencies correctly marked. Varying minor versions across different subpackages is expected behavior; Go's version selection algorithm accommodates this according to semantic versioning. The state is consistent with a successful
go mod tidyoperation.
3-3: <!-- [scratchpad_start]
[task verification_go_directive_complete]
Observations from web search:
- Source 3 (GitHub issue): "using a patch version is more correct!" and "using patch version is actually there more correct usage now. There is no release in go without a patch version anymore!"
- Source 4 (Alex Bozhenko article, Dec 2024): "The version must be a valid Go version, such as 1.20, 1.22.0, or 1.24rc1" and "to indicate that your module needs a released version, you must include the patch release, e.g. go 1.23.0" and "Confirmed by Go team member"
- Source 5 (GitHub issue #62584): "before go 1.21 it was not allowed to specify the patch version in the go.mod" but NOW (Go 1.21+) it IS allowed
- Script output: go.mod line 3 shows "go 1.24.8" which is a valid three-part version
Analysis: The original review comment is INCORRECT. The go directive DOES support patch versions in Go 1.21+. The format "go 1.24.8" is valid, not invalid. Modern Go tooling (since 1.21) fully supports patch versions in the go directive. This contradicts the review comment's assertion.
Done: The review comment needs to be marked as .
[scratchpad_start] -->The review comment is incorrect.
The
godirective accepts valid Go versions such as1.20,1.22.0, or1.24rc1. Before Go 1.21, patch versions were not allowed in thego.modfile, but using a patch version is correct usage now, and there is no release in Go without a patch version anymore, so using1.24.8is correct while using just1.24would be incomplete.The
go 1.24.8directive in your code is valid and properly formatted.Likely an incorrect or invalid review comment.
19-27: AWS SDK v2 bumps verified—no transitive conflicts.Build and tidy completed successfully with all dependencies resolving correctly. The AWS SDK v2 version bumps are consistent and integrate cleanly.
tests/test-cases/complete.yaml (1)
16-16: Test expectation updated appropriately.The error message now reflects the preserved exit code messaging pattern. This aligns with the exit code preservation changes throughout the codebase.
tests/fixtures/scenarios/exit-codes/stacks/workflows/workflows.yaml (1)
1-21: Clean test fixtures for exit code validation.The workflow definitions are straightforward and focused—each tests a specific exit code scenario. This complements the comprehensive test coverage added in the test-cases.
cmd/workflow.go (2)
4-5: Good addition for exit code type checking.Adding the standard library errors import enables proper use of
errors.As()for unwrapping ExitCodeError.
46-51: Exit code preservation logic looks solid.The implementation correctly unwraps ExitCodeError and preserves the specific exit code. The fallback to exit code 1 for other known workflow errors maintains backward compatibility.
tests/test-cases/workflows.yaml (1)
55-58: Excellent improvement to test accuracy.Using exit code 127 for "command not found" is more precise than the generic exit code 1. The inline comments clearly document the reasoning.
tests/fixtures/scenarios/exit-codes/atmos.yaml (1)
1-33: Comprehensive fixture for exit code testing.The custom commands and configuration provide a solid foundation for validating exit code preservation across different scenarios. The structure is clean and focused.
internal/exec/terraform.go (1)
584-591: Critical fix for terraform plan exit code preservation.The change correctly preserves exit code 2 from
terraform plan -detailed-exitcode(indicating changes detected) while still treating the upload as successful. This restores important signal for downstream tooling that relies on the specific exit code.tests/test-cases/exit-codes.yaml (1)
1-84: Thorough test coverage for exit code preservation.The test suite validates exit code handling across both custom commands and workflow shell steps, covering success (0), terraform-specific (2), and arbitrary (42) exit codes. This provides solid validation of the exit code preservation feature.
tests/snapshots/TestCLICommands_atmos_circuit-breaker.stderr.golden (1)
12-66: Snapshot updated to reflect new error messaging.The consistent replacement of "exit status 1" with "subcommand exited with code 1" throughout the snapshot aligns with the exit code preservation error handling introduced in this PR.
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #1660 +/- ##
==========================================
+ Coverage 65.99% 66.08% +0.09%
==========================================
Files 343 343
Lines 38686 38736 +50
==========================================
+ Hits 25532 25600 +68
+ Misses 11159 11148 -11
+ Partials 1995 1988 -7
Flags with carried forward coverage won't be shown. Click here to find out more.
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
pkg/utils/shell_utils.go (1)
90-102: Good: preserve exit code and diagnostics.Using errors.Join with ExitCodeError and switching to context.Background is the right call. Downstream can errors.As for ExitCodeError while keeping the interpreter message.
🧹 Nitpick comments (6)
pkg/utils/shell_utils.go (1)
105-131: Clamp negative ATMOS_SHLVL and add bounds tests.If ATMOS_SHLVL is negative, we should normalize to 0 to avoid weird recursion semantics. Also consider a small table test for max-depth and negative values.
Apply:
func GetNextShellLevel() (int, error) { defer perf.Track(nil, "utils.GetNextShellLevel")() // Create a new viper instance for this operation v := viper.New() if err := v.BindEnv("atmos_shell_level", "ATMOS_SHLVL"); err != nil { return 0, fmt.Errorf("%w: %v", ErrBindingShellLevelEnv, err) } shellVal := 0 atmosShellLvl := v.GetString("atmos_shell_level") if atmosShellLvl != "" { val, err := strconv.Atoi(atmosShellLvl) if err != nil { return 0, fmt.Errorf("%w: %s", ErrConvertingShellLevel, err) } shellVal = val } + // Normalize negatives to 0 to keep SHLVL semantics sane. + if shellVal < 0 { + shellVal = 0 + } shellVal++ if shellVal > MaxShellDepth { return 0, fmt.Errorf("%w current=%d, max=%d", ErrMaxShellDepthExceeded, shellVal, MaxShellDepth) } return shellVal, nil }As per coding guidelines.
pkg/utils/shell_utils_test.go (3)
65-86: Also assert stdout for the happy path.Verify we captured output for the echo case to protect regression on StdIO wiring.
Apply:
- tests := []struct { + tests := []struct { name string command string expectError bool expectCode int + expectOut string }{ { name: "successful command", command: "echo 'test'", expectError: false, - expectCode: 0, + expectCode: 0, + expectOut: "test\n", }, { name: "command with exit code 0", command: "exit 0", expectError: false, expectCode: 0, }, ... } ... } else { assert.NoError(t, err, "Command should succeed: %s", tt.command) + if tt.expectOut != "" { + assert.Equal(t, tt.expectOut, buf.String(), "stdout should match for: %s", tt.name) + } }
88-104: Avoid brittle string assertions on error text.Rely on errors.As for ExitCodeError; error strings can change (Go/join formatting, interpreter messages).
Apply:
- // Verify the error message contains diagnostic context from the interpreter. - // The errors.Join() should preserve both the ExitCodeError and the original error. - assert.Contains(t, err.Error(), "code 99", "Error message should contain exit code") + // Do not assert on error message formatting; type and code checks above are sufficient.
105-123: Same: don’t assert on exact error string.Keep the ExitCodeError assertion; drop the Contains check to reduce flakiness across platforms/interpreter versions.
Apply:
- errStr := err.Error() - assert.Contains(t, errStr, "code 127", "Error should mention exit code") - // Note: The exact error message from the interpreter may vary, but it should contain something about the command. + // Intentionally avoid asserting on error text; message formats are not stable.errors/error_funcs_test.go (2)
139-193: Nice subprocess harness for ExitCodeError.Confirms exact exit codes 2, 42, 127 propagate via CheckErrorPrintAndExit.
Consider a small helper to DRY the subprocess pattern across tests.
Example:func runAsSubtest(t *testing.T, name, env string) *exec.ExitError { ... }
195-220: Precedence is already correct—consider the test as optional regression coverage.The implementation at lines 63–66 checks
ExitCodeErrorbeforeexec.ExitError(lines 69–71), so precedence is in place. The suggested test would be a useful regression guard, joining both errors to verifyExitCodeErrortakes precedence when both are present. However, the diff has minor issues (e.g.,errors.As(cmd.ProcessState, &ee)is incorrect;ProcessStateisn't an error).If you add this test, clean up the error-creation logic to make the intent clearer.
📜 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.
📒 Files selected for processing (3)
errors/error_funcs_test.go(1 hunks)pkg/utils/shell_utils.go(2 hunks)pkg/utils/shell_utils_test.go(1 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*_test.go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
**/*_test.go: Every new feature must include comprehensive unit tests
Test both happy paths and error conditions
Use table-driven tests for multiple scenarios
**/*_test.go: Use table-driven unit tests for pure functions and focus on behavior; co-locate tests; target >80% coverage for pkg/ and internal/exec/.
Always use t.Skipf() with a clear reason; never use t.Skip() or t.Skipf without a reason.
Files:
errors/error_funcs_test.gopkg/utils/shell_utils_test.go
**/*.go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
**/*.go: All code must pass golangci-lint checks
Follow Go error handling idioms and use meaningful error messages
Wrap errors with context using fmt.Errorf("context: %w", err)
Consider custom error types for domain-specific errors
Follow standard Go coding style; run gofmt and goimports
Use snake_case for environment variables
Document complex logic with inline comments
**/*.go: All Go comments must end with periods; applies to single-line, multi-line, inline, and documentation comments (golangci-lint godot).
Group imports into three sections (stdlib, 3rd-party, Atmos), separated by blank lines; sort alphabetically within each group; preserve existing aliases.
Configuration loading must use Viper with precedence CLI → ENV → files → defaults; bind config name atmos and add path, AutomaticEnv, and ATMOS prefix.
All errors must be wrapped using static errors (defined in errors/errors.go); use errors.Join for multiple errors; fmt.Errorf with %w for context; use errors.Is for checks; never compare error strings.
Distinguish structured logging from UI output: UI prompts/status/errors to stderr; data/results to stdout; never use logging for UI.
Most text UI must go to stderr; only data/results to stdout; prefer utils.PrintfMessageToTUI for UI messages.
All new configurations must support Go templating using existing utilities and available template functions.
Prefer SDKs over external binaries for cross-platform support; use filepath/os/runtime for portability.
For non-standard execution paths, capture telemetry via telemetry.CaptureCmd or telemetry.CaptureCmdString without user data.
80% minimum coverage on new/changed lines and include unit tests for new features; add integration tests for CLI using tests/ fixtures.
Always bind environment variables with viper.BindEnv and provide ATMOS_ alternatives for every env var.
Use structured logging with levels (Fatal>Error>Warn>Debug>Trace); avoid string interpolation and ensure logging does not affect execution.
Prefer re...
Files:
errors/error_funcs_test.gopkg/utils/shell_utils_test.gopkg/utils/shell_utils.go
pkg/**/*.go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
Place business logic in pkg rather than in cmd
Files:
pkg/utils/shell_utils_test.gopkg/utils/shell_utils.go
pkg/**/*_test.go
📄 CodeRabbit inference engine (CLAUDE.md)
Unit tests for packages live under pkg/ alongside implementation files.
Files:
pkg/utils/shell_utils_test.go
**/!(*_test).go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
Document all exported functions, types, and methods with Go doc comments
Add
defer perf.Track()to all public functions and critical private functions; include a blank line after the call; use package-prefixed names; pass atmosConfig when present, else nil.
Files:
pkg/utils/shell_utils.go
🧠 Learnings (3)
📚 Learning: 2025-10-16T15:18:00.319Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-16T15:18:00.319Z
Learning: Applies to cmd/*/*_test.go : Add tests for new commands under cmd/<command>/<command>_test.go.
Applied to files:
pkg/utils/shell_utils_test.go
📚 Learning: 2025-10-16T15:18:00.319Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-16T15:18:00.319Z
Learning: Applies to internal/exec/*_test.go : Add comprehensive tests for template functions under internal/exec/ with *_test.go files.
Applied to files:
pkg/utils/shell_utils_test.go
📚 Learning: 2025-09-13T18:06:07.674Z
Learnt from: samtholiya
PR: cloudposse/atmos#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/utils/shell_utils.go
🧬 Code graph analysis (3)
errors/error_funcs_test.go (2)
errors/errors.go (1)
ExitCodeError(379-381)errors/error_funcs.go (1)
CheckErrorPrintAndExit(55-78)
pkg/utils/shell_utils_test.go (2)
pkg/utils/shell_utils.go (1)
ShellRunner(65-103)errors/errors.go (1)
ExitCodeError(379-381)
pkg/utils/shell_utils.go (1)
errors/errors.go (1)
ExitCodeError(379-381)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Build (windows-latest, windows)
- GitHub Check: Build (ubuntu-latest, linux)
- GitHub Check: Run pre-commit hooks
- GitHub Check: Analyze (go)
- GitHub Check: Lint (golangci)
- GitHub Check: Summary
🔇 Additional comments (2)
pkg/utils/shell_utils.go (1)
18-18: Imports look tidy.Grouping and aliasing follow the repo convention. Nice.
pkg/utils/shell_utils_test.go (1)
21-63: Solid coverage for common exit codes.Good spread (0, 1, 2, 42, 127) and a not-found case. Nice.
…fix-terraform-exit-code-2
There was a problem hiding this comment.
Actionable comments posted: 2
📜 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.
📒 Files selected for processing (4)
errors/error_funcs_test.go(2 hunks)internal/exec/shell_utils_test.go(2 hunks)pkg/utils/shell_utils_test.go(1 hunks)tests/snapshots/TestCLICommands_atmos_circuit-breaker.stderr.golden(1 hunks)
🚧 Files skipped from review as they are similar to previous changes (1)
- tests/snapshots/TestCLICommands_atmos_circuit-breaker.stderr.golden
🧰 Additional context used
📓 Path-based instructions (6)
**/*_test.go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
**/*_test.go: Every new feature must include comprehensive unit tests
Test both happy paths and error conditions
Use table-driven tests for multiple scenarios
**/*_test.go: Use table-driven unit tests for pure functions and focus on behavior; co-locate tests; target >80% coverage for pkg/ and internal/exec/.
Always use t.Skipf() with a clear reason; never use t.Skip() or t.Skipf without a reason.
Files:
internal/exec/shell_utils_test.goerrors/error_funcs_test.gopkg/utils/shell_utils_test.go
**/*.go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
**/*.go: All code must pass golangci-lint checks
Follow Go error handling idioms and use meaningful error messages
Wrap errors with context using fmt.Errorf("context: %w", err)
Consider custom error types for domain-specific errors
Follow standard Go coding style; run gofmt and goimports
Use snake_case for environment variables
Document complex logic with inline comments
**/*.go: All Go comments must end with periods; applies to single-line, multi-line, inline, and documentation comments (golangci-lint godot).
Group imports into three sections (stdlib, 3rd-party, Atmos), separated by blank lines; sort alphabetically within each group; preserve existing aliases.
Configuration loading must use Viper with precedence CLI → ENV → files → defaults; bind config name atmos and add path, AutomaticEnv, and ATMOS prefix.
All errors must be wrapped using static errors (defined in errors/errors.go); use errors.Join for multiple errors; fmt.Errorf with %w for context; use errors.Is for checks; never compare error strings.
Distinguish structured logging from UI output: UI prompts/status/errors to stderr; data/results to stdout; never use logging for UI.
Most text UI must go to stderr; only data/results to stdout; prefer utils.PrintfMessageToTUI for UI messages.
All new configurations must support Go templating using existing utilities and available template functions.
Prefer SDKs over external binaries for cross-platform support; use filepath/os/runtime for portability.
For non-standard execution paths, capture telemetry via telemetry.CaptureCmd or telemetry.CaptureCmdString without user data.
80% minimum coverage on new/changed lines and include unit tests for new features; add integration tests for CLI using tests/ fixtures.
Always bind environment variables with viper.BindEnv and provide ATMOS_ alternatives for every env var.
Use structured logging with levels (Fatal>Error>Warn>Debug>Trace); avoid string interpolation and ensure logging does not affect execution.
Prefer re...
Files:
internal/exec/shell_utils_test.goerrors/error_funcs_test.gopkg/utils/shell_utils_test.go
internal/exec/**/*.go
📄 CodeRabbit inference engine (CLAUDE.md)
Place command business logic in internal/exec/ (separate from cmd/ wiring).
Files:
internal/exec/shell_utils_test.go
internal/exec/*_test.go
📄 CodeRabbit inference engine (CLAUDE.md)
Add comprehensive tests for template functions under internal/exec/ with *_test.go files.
Files:
internal/exec/shell_utils_test.go
pkg/**/*.go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
Place business logic in pkg rather than in cmd
Files:
pkg/utils/shell_utils_test.go
pkg/**/*_test.go
📄 CodeRabbit inference engine (CLAUDE.md)
Unit tests for packages live under pkg/ alongside implementation files.
Files:
pkg/utils/shell_utils_test.go
🧠 Learnings (6)
📚 Learning: 2025-10-16T15:18:00.319Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-16T15:18:00.319Z
Learning: Applies to internal/exec/*_test.go : Add comprehensive tests for template functions under internal/exec/ with *_test.go files.
Applied to files:
internal/exec/shell_utils_test.goerrors/error_funcs_test.gopkg/utils/shell_utils_test.go
📚 Learning: 2025-10-16T15:18:00.319Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-16T15:18:00.319Z
Learning: Applies to cmd/*/*_test.go : Add tests for new commands under cmd/<command>/<command>_test.go.
Applied to files:
internal/exec/shell_utils_test.gopkg/utils/shell_utils_test.go
📚 Learning: 2025-10-16T15:18:00.319Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-16T15:18:00.319Z
Learning: Applies to **/*_test.go : Use table-driven unit tests for pure functions and focus on behavior; co-locate tests; target >80% coverage for pkg/ and internal/exec/.
Applied to files:
internal/exec/shell_utils_test.go
📚 Learning: 2025-06-02T14:12:02.710Z
Learnt from: milldr
PR: cloudposse/atmos#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 the error 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.
Applied to files:
errors/error_funcs_test.go
📚 Learning: 2025-06-02T14:12:02.710Z
Learnt from: milldr
PR: cloudposse/atmos#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:
errors/error_funcs_test.go
📚 Learning: 2025-02-03T06:00:11.419Z
Learnt from: samtholiya
PR: cloudposse/atmos#959
File: cmd/describe_config.go:20-20
Timestamp: 2025-02-03T06:00:11.419Z
Learning: Commands should use `PrintErrorMarkdownAndExit` with empty title and suggestion (`"", err, ""`) for general error handling. Specific titles like "Invalid Usage" or "File Not Found" should only be used for validation or specific error scenarios.
Applied to files:
errors/error_funcs_test.go
🧬 Code graph analysis (3)
internal/exec/shell_utils_test.go (2)
pkg/schema/schema.go (1)
AtmosConfiguration(27-65)internal/exec/shell_utils.go (2)
ExecuteShellCommand(23-79)ExecuteShell(82-104)
errors/error_funcs_test.go (6)
pkg/ui/markdown/renderer.go (1)
NewTerminalMarkdownRenderer(247-267)pkg/schema/schema.go (1)
AtmosConfiguration(27-65)errors/error_funcs.go (3)
CheckErrorAndPrint(29-52)InitializeMarkdown(20-26)CheckErrorPrintAndExit(55-78)pkg/logger/global.go (2)
Default(19-21)SetDefault(24-28)pkg/logger/log.go (3)
SetOutput(94-96)SetLevel(84-86)TraceLevel(179-179)errors/errors.go (1)
ExitCodeError(379-381)
pkg/utils/shell_utils_test.go (2)
pkg/utils/shell_utils.go (1)
ShellRunner(65-103)errors/errors.go (1)
ExitCodeError(379-381)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: Build (macos-latest, macos)
- GitHub Check: Build (ubuntu-latest, linux)
- GitHub Check: Build (windows-latest, windows)
- GitHub Check: Run pre-commit hooks
- GitHub Check: Analyze (go)
- GitHub Check: Lint (golangci)
- GitHub Check: Summary
🔇 Additional comments (10)
internal/exec/shell_utils_test.go (2)
60-164: Comprehensive test coverage for ExecuteShellCommand.The test covers dry run, various redirection modes, platform-specific behavior, error conditions, and environment handling. Well done.
166-210: Good coverage for ExecuteShell function.Tests cover the main execution paths including dry run, syntax errors, and environment handling. Solid work.
errors/error_funcs_test.go (3)
100-118: Good coverage for empty title default behavior.The test properly verifies that an empty title defaults to "Error" in the output.
182-216: Solid exit code preservation tests.The subprocess pattern correctly validates that ExitCodeError codes (2 and 42) propagate through CheckErrorPrintAndExit. Good coverage of the new functionality.
218-238: Good coverage for exec.ExitError propagation.The test validates that exit codes from exec.ExitError properly propagate through the error handling path. Well done.
pkg/utils/shell_utils_test.go (5)
14-76: Excellent exit code preservation tests.The table-driven approach covers exit codes 0, 1, 2, and 42, verifying both error messages and ExitCodeError wrapping. This thoroughly validates the new exit code preservation behavior.
78-84: Good output capture verification.Simple and focused test that validates ShellRunner properly captures command output.
86-93: Solid environment variable test.Validates that custom environment variables properly propagate to the shell command.
95-104: Good working directory test.The test correctly verifies the working directory behavior without making brittle exact path assertions. Smart approach.
106-117: Important parse error validation.This test ensures parse errors are not incorrectly wrapped as ExitCodeError, which is critical for proper error handling. Good work distinguishing error types.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
errors/error_funcs_test.go (1)
217-238: Solid happy path test for InitializeMarkdown.This test verifies successful initialization without errors. If you want to boost coverage further, consider adding a test case for initialization failure scenarios in a future PR.
📜 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.
📒 Files selected for processing (1)
errors/error_funcs_test.go(2 hunks)
🧰 Additional context used
📓 Path-based instructions (2)
**/*_test.go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
**/*_test.go: Every new feature must include comprehensive unit tests
Test both happy paths and error conditions
Use table-driven tests for multiple scenarios
**/*_test.go: Use table-driven unit tests for pure functions and focus on behavior; co-locate tests; target >80% coverage for pkg/ and internal/exec/.
Always use t.Skipf() with a clear reason; never use t.Skip() or t.Skipf without a reason.
Files:
errors/error_funcs_test.go
**/*.go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
**/*.go: All code must pass golangci-lint checks
Follow Go error handling idioms and use meaningful error messages
Wrap errors with context using fmt.Errorf("context: %w", err)
Consider custom error types for domain-specific errors
Follow standard Go coding style; run gofmt and goimports
Use snake_case for environment variables
Document complex logic with inline comments
**/*.go: All Go comments must end with periods; applies to single-line, multi-line, inline, and documentation comments (golangci-lint godot).
Group imports into three sections (stdlib, 3rd-party, Atmos), separated by blank lines; sort alphabetically within each group; preserve existing aliases.
Configuration loading must use Viper with precedence CLI → ENV → files → defaults; bind config name atmos and add path, AutomaticEnv, and ATMOS prefix.
All errors must be wrapped using static errors (defined in errors/errors.go); use errors.Join for multiple errors; fmt.Errorf with %w for context; use errors.Is for checks; never compare error strings.
Distinguish structured logging from UI output: UI prompts/status/errors to stderr; data/results to stdout; never use logging for UI.
Most text UI must go to stderr; only data/results to stdout; prefer utils.PrintfMessageToTUI for UI messages.
All new configurations must support Go templating using existing utilities and available template functions.
Prefer SDKs over external binaries for cross-platform support; use filepath/os/runtime for portability.
For non-standard execution paths, capture telemetry via telemetry.CaptureCmd or telemetry.CaptureCmdString without user data.
80% minimum coverage on new/changed lines and include unit tests for new features; add integration tests for CLI using tests/ fixtures.
Always bind environment variables with viper.BindEnv and provide ATMOS_ alternatives for every env var.
Use structured logging with levels (Fatal>Error>Warn>Debug>Trace); avoid string interpolation and ensure logging does not affect execution.
Prefer re...
Files:
errors/error_funcs_test.go
🧠 Learnings (4)
📚 Learning: 2025-06-02T14:12:02.710Z
Learnt from: milldr
PR: cloudposse/atmos#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 the error 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.
Applied to files:
errors/error_funcs_test.go
📚 Learning: 2025-06-02T14:12:02.710Z
Learnt from: milldr
PR: cloudposse/atmos#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:
errors/error_funcs_test.go
📚 Learning: 2025-02-03T06:00:11.419Z
Learnt from: samtholiya
PR: cloudposse/atmos#959
File: cmd/describe_config.go:20-20
Timestamp: 2025-02-03T06:00:11.419Z
Learning: Commands should use `PrintErrorMarkdownAndExit` with empty title and suggestion (`"", err, ""`) for general error handling. Specific titles like "Invalid Usage" or "File Not Found" should only be used for validation or specific error scenarios.
Applied to files:
errors/error_funcs_test.go
📚 Learning: 2025-09-23T02:30:42.362Z
Learnt from: CR
PR: cloudposse/atmos#0
File: .cursor/rules/atmos-rules.mdc:0-0
Timestamp: 2025-09-23T02:30:42.362Z
Learning: Applies to **/*_test.go : Test both happy paths and error conditions
Applied to files:
errors/error_funcs_test.go
🧬 Code graph analysis (1)
errors/error_funcs_test.go (6)
errors/error_funcs.go (3)
CheckErrorPrintAndExit(55-78)CheckErrorAndPrint(29-52)InitializeMarkdown(20-26)errors/errors.go (1)
ExitCodeError(379-381)pkg/ui/markdown/renderer.go (1)
NewTerminalMarkdownRenderer(247-267)pkg/schema/schema.go (1)
AtmosConfiguration(27-65)pkg/logger/global.go (2)
Default(19-21)SetDefault(24-28)pkg/logger/log.go (3)
SetOutput(94-96)SetLevel(84-86)TraceLevel(179-179)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Build (macos-latest, macos)
- GitHub Check: Build (windows-latest, windows)
- GitHub Check: Build (ubuntu-latest, linux)
- GitHub Check: Analyze (go)
- GitHub Check: Lint (golangci)
- GitHub Check: Summary
🔇 Additional comments (4)
errors/error_funcs_test.go (4)
45-62: Solid test for exit code 1.This subprocess-based test correctly verifies that
CheckErrorPrintAndExitexits with code 1 for a standard error. The pattern is appropriate and the assertions are sound.
83-117: Excellent coverage of ExitCodeError preservation.This test comprehensively verifies that
CheckErrorPrintAndExitcorrectly preserves custom exit codes (2 and 42) fromExitCodeError, which is central to the PR's objectives.
119-139: Good coverage for exec.ExitError propagation.This test validates that
CheckErrorPrintAndExitcorrectly propagates exit codes fromexec.ExitError, ensuring the fallback path works as intended.
196-214: Good edge case for default title behavior.This subtest ensures that when an empty title is provided,
CheckErrorAndPrintdefaults to "Error" as expected. Nice addition for coverage.
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (6)
internal/exec/terraform.go (3)
492-511: Workspace auto-create may trigger on unrelated failures (exit code 1 is broad).Exit code 1 from "terraform workspace select" can be many errors (init not run, backend issues, auth), not only "workspace does not exist". Creating a workspace on any code 1 may mask root causes.
Recommendation:
- Gate creation on both ExitCodeError{Code:1} and a message hint (e.g., contains "workspace" and "does not exist"), or
- Probe with "terraform workspace list" and check membership before creating, or
- Add a config guard (e.g., Components.Terraform.AutoCreateMissingWorkspace, default true) to allow disabling.
Minimal change (safer default): list before create.
Example patch (list check; keeps stderr redirection consistent):
@@ - err = ExecuteShellCommand( + // Verify the workspace truly doesn't exist before creating. + // Terraform returns non‑zero if workspace not present. + listArgs := []string{"workspace", "list"} + if info.RedirectStdErr != "" { + workspaceSelectRedirectStdErr = info.RedirectStdErr + } + lerr := ExecuteShellCommand( + atmosConfig, + info.Command, + listArgs, + componentPath, + info.ComponentEnvList, + info.DryRun, + workspaceSelectRedirectStdErr, + ) + if lerr != nil { + return err // Preserve original select error; don't hide underlying issues with a create attempt. + } + // Only create if desired workspace is absent in the listing. + // Note: ExecuteShellCommand prints list to stdout; prefer capturing output in the future. + // For now, try creating; if it exists, 'workspace new' will fail harmlessly with an informative error. + err = ExecuteShellCommand( atmosConfig, info.Command, []string{"workspace", "new", info.TerraformWorkspace}, componentPath, info.ComponentEnvList, info.DryRun, info.RedirectStdErr, )
565-576: Exit code extraction priority is correct.Checking errUtils.ExitCodeError before *osexec.ExitError preserves real codes end-to-end.
Consider extracting into a helper for reuse and testability:
// exitCodeFromError returns 0 for nil, preserves errUtils.ExitCodeError, // falls back to *exec.ExitError, else 1. func exitCodeFromError(err error) int { if err == nil { return 0 } var ec errUtils.ExitCodeError if errors.As(err, &ec) { return ec.Code } var osErr *osexec.ExitError if errors.As(err, &osErr) { return osErr.ExitCode() } return 1 }Then:
- var exitCode int - if err != nil { - // Prefer our typed error to preserve exit codes from subcommands. - var ec errUtils.ExitCodeError - if errors.As(err, &ec) { - exitCode = ec.Code - } else { - var osErr *osexec.ExitError - if errors.As(err, &osErr) { - exitCode = osErr.ExitCode() - } else { - exitCode = 1 - } - } - } else { - exitCode = 0 - } + exitCode := exitCodeFromError(err)
591-598: Preserve both exit code and original error context for plan uploads.Returning only ExitCodeError{2} drops the original error message/context. Join them so callers can unwrap the exit code and still see the command error details.
- if exitCode == 2 { - // Exit code 2 is success for terraform plan but we must preserve it - return errUtils.ExitCodeError{Code: 2} - } + if exitCode == 2 { + // Exit code 2 is success for terraform plan; preserve code and context. + if err != nil { + return errors.Join(err, errUtils.ExitCodeError{Code: 2}) + } + return errUtils.ExitCodeError{Code: 2} + }internal/exec/terraform_exitcode_test.go (3)
23-40: Test case label mismatch.Case "ExitCodeError with code 0 (success)" passes nil, not ExitCodeError{0}. Rename for accuracy (or pass ExitCodeError{0}).
- { - name: "ExitCodeError with code 0 (success)", - err: nil, - expectedCode: 0, - description: "No error means exit code 0", - }, + { + name: "nil error returns code 0 (success)", + err: nil, + expectedCode: 0, + description: "No error means exit code 0", + },
49-69: Avoid duplicating production logic in tests; use a helper.Tests mirror terraform.go’s exit-code extraction. Prefer calling a small helper (e.g., exitCodeFromError) to keep tests resilient to refactors.
If you adopt the helper suggested in terraform.go, simplify this test to:
exitCode := exitCodeFromError(tc.err) assert.Equal(t, tc.expectedCode, exitCode, tc.description)
133-190: Add a negative test for workspace auto-create on non-existence errors.Since code 1 is broad, add a case where select fails with ExitCodeError{1} but represents a different underlying problem to ensure we don’t attempt creation (e.g., simulate with a sentinel error in chain and assert early return). This will protect against false positives in the auto-create path.
Example test idea:
t.Run("exit code 1 but not missing-workspace message", func(t *testing.T) { // Wrap ExitCodeError{1} with a sentinel indicating unrelated failure. sentinel := fmt.Errorf("backend not configured") err := fmt.Errorf("%w: %w", sentinel, errUtils.ExitCodeError{Code:1}) var exitCodeErr errUtils.ExitCodeError // Current logic only checks code; consider enhancing to also inspect context. shouldReturn := !errors.As(err, &exitCodeErr) || exitCodeErr.Code != 1 assert.True(t, shouldReturn, "Should return immediately and not create workspace") })
📜 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.
📒 Files selected for processing (5)
.goreleaser.yml(2 hunks)internal/exec/terraform.go(4 hunks)internal/exec/terraform_exitcode_test.go(1 hunks)internal/exec/workflow_test.go(4 hunks)internal/exec/workflow_utils.go(3 hunks)
🧰 Additional context used
📓 Path-based instructions (5)
**/*_test.go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
**/*_test.go: Every new feature must include comprehensive unit tests
Test both happy paths and error conditions
Use table-driven tests for multiple scenarios
**/*_test.go: Use table-driven unit tests for pure functions and focus on behavior; co-locate tests; target >80% coverage for pkg/ and internal/exec/.
Always use t.Skipf() with a clear reason; never use t.Skip() or t.Skipf without a reason.
Files:
internal/exec/workflow_test.gointernal/exec/terraform_exitcode_test.go
**/*.go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
**/*.go: All code must pass golangci-lint checks
Follow Go error handling idioms and use meaningful error messages
Wrap errors with context using fmt.Errorf("context: %w", err)
Consider custom error types for domain-specific errors
Follow standard Go coding style; run gofmt and goimports
Use snake_case for environment variables
Document complex logic with inline comments
**/*.go: All Go comments must end with periods; applies to single-line, multi-line, inline, and documentation comments (golangci-lint godot).
Group imports into three sections (stdlib, 3rd-party, Atmos), separated by blank lines; sort alphabetically within each group; preserve existing aliases.
Configuration loading must use Viper with precedence CLI → ENV → files → defaults; bind config name atmos and add path, AutomaticEnv, and ATMOS prefix.
All errors must be wrapped using static errors (defined in errors/errors.go); use errors.Join for multiple errors; fmt.Errorf with %w for context; use errors.Is for checks; never compare error strings.
Distinguish structured logging from UI output: UI prompts/status/errors to stderr; data/results to stdout; never use logging for UI.
Most text UI must go to stderr; only data/results to stdout; prefer utils.PrintfMessageToTUI for UI messages.
All new configurations must support Go templating using existing utilities and available template functions.
Prefer SDKs over external binaries for cross-platform support; use filepath/os/runtime for portability.
For non-standard execution paths, capture telemetry via telemetry.CaptureCmd or telemetry.CaptureCmdString without user data.
80% minimum coverage on new/changed lines and include unit tests for new features; add integration tests for CLI using tests/ fixtures.
Always bind environment variables with viper.BindEnv and provide ATMOS_ alternatives for every env var.
Use structured logging with levels (Fatal>Error>Warn>Debug>Trace); avoid string interpolation and ensure logging does not affect execution.
Prefer re...
Files:
internal/exec/workflow_test.gointernal/exec/workflow_utils.gointernal/exec/terraform_exitcode_test.gointernal/exec/terraform.go
internal/exec/**/*.go
📄 CodeRabbit inference engine (CLAUDE.md)
Place command business logic in internal/exec/ (separate from cmd/ wiring).
Files:
internal/exec/workflow_test.gointernal/exec/workflow_utils.gointernal/exec/terraform_exitcode_test.gointernal/exec/terraform.go
internal/exec/*_test.go
📄 CodeRabbit inference engine (CLAUDE.md)
Add comprehensive tests for template functions under internal/exec/ with *_test.go files.
Files:
internal/exec/workflow_test.gointernal/exec/terraform_exitcode_test.go
**/!(*_test).go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
Document all exported functions, types, and methods with Go doc comments
Add
defer perf.Track()to all public functions and critical private functions; include a blank line after the call; use package-prefixed names; pass atmosConfig when present, else nil.
Files:
internal/exec/workflow_utils.gointernal/exec/terraform.go
🧠 Learnings (4)
📚 Learning: 2025-06-02T14:12:02.710Z
Learnt from: milldr
PR: cloudposse/atmos#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_test.gointernal/exec/workflow_utils.go
📚 Learning: 2025-10-16T15:18:00.319Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-16T15:18:00.319Z
Learning: Applies to **/*.go : Ensure Atmos remains portable across Linux, macOS, and Windows; use filepath.Join, os.PathSeparator, and build constraints when necessary.
Applied to files:
.goreleaser.yml
📚 Learning: 2025-10-16T15:18:00.319Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-16T15:18:00.319Z
Learning: Applies to internal/exec/*_test.go : Add comprehensive tests for template functions under internal/exec/ with *_test.go files.
Applied to files:
internal/exec/terraform_exitcode_test.go
📚 Learning: 2024-11-02T15:35:09.958Z
Learnt from: aknysh
PR: cloudposse/atmos#759
File: internal/exec/terraform.go:366-368
Timestamp: 2024-11-02T15:35:09.958Z
Learning: In `internal/exec/terraform.go`, the workspace cleaning code under both the general execution path and within the `case "init":` block is intentionally duplicated because the code execution paths are different. The `.terraform/environment` file should be deleted before executing `terraform init` in both scenarios to ensure a clean state.
Applied to files:
internal/exec/terraform.go
🧬 Code graph analysis (3)
internal/exec/workflow_utils.go (1)
errors/errors.go (1)
ExitCodeError(379-381)
internal/exec/terraform_exitcode_test.go (1)
errors/errors.go (1)
ExitCodeError(379-381)
internal/exec/terraform.go (1)
errors/errors.go (1)
ExitCodeError(379-381)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (3)
- GitHub Check: Build (windows-latest, windows)
- GitHub Check: Lint (golangci)
- GitHub Check: Summary
🔇 Additional comments (9)
.goreleaser.yml (2)
3-26: Well-structured disk cleanup hook for GitHub Actions.The new
before.hookssection properly guards the cleanup logic withGITHUB_ACTIONSenvironment check and uses|| trueto handle errors gracefully. The detailed rationale (with specific disk sizes) and before/afterdfoutput make it clear what's being freed and why. This addresses the common "no space left on device" issue when building for 14 platform combinations.
47-54: Solid reproducible-build setup.Adding
-trimpath(with clear documentation about its purpose and precedent) pairs well with the existing-s -wflags to reduce binary size and enable reproducible builds. The comments are helpful for future maintainers.Verify that the minimum supported Go version for this project includes
-trimpathsupport (available since Go 1.13, so likely not an issue, but worth confirming if you support older versions).internal/exec/workflow_test.go (1)
126-126: LGTM! Error message expectations updated correctly.The updated error messages reflect the new exit-code preservation behavior. Line 126 correctly expects the specific exit code 1 from
exit 1, while lines 145, 184, and 204 appropriately use the generic "subcommand exited with code" for atmos command failures where the exact code may vary.Also applies to: 145-145, 184-184, 204-204
internal/exec/workflow_utils.go (3)
3-23: LGTM! Import organization follows guidelines.The standard library
errorsimport enables the new error-joining and unwrapping behavior. Import grouping (stdlib → 3rd-party → Atmos) aligns with coding guidelines.
47-63: LGTM! Well-implemented error classification.The function correctly treats
ExitCodeErroras a known (already-reported) error, then checks workflow-specific sentinels. Usingerrors.Asfor type checking anderrors.Isfor sentinel comparison follows Go error handling best practices.
215-219: LGTM! Exit-code preservation properly implemented.The joined error preserves both the workflow sentinel (
ErrWorkflowStepFailed) for classification and the underlying error (potentiallyExitCodeError) for exit-code unwrapping. This addresses the past review comment and aligns with the PR objectives.internal/exec/terraform.go (1)
28-28: Correct flag form for Terraform.Using "-detailed-exitcode" is right. Nice catch.
internal/exec/terraform_exitcode_test.go (2)
73-80: Flag constant assertion looks good.This guards the single-dash syntax across refactors.
82-97: ExitCodeError unwrap behavior verified.Good coverage of wrapping semantics with errors.As.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
internal/exec/terraform_exitcode_test.go (1)
22-39: Test case name doesn't match the error value.The test case at lines 34-39 is named
"ExitCodeError with code 0 (success)"but useserr: nilinstead oferrUtils.ExitCodeError{Code: 0}. The logic correctly handles nil → 0, but the name is misleading. Consider renaming to"nil error (success)"or usingExitCodeError{Code: 0}if you want to explicitly test that scenario.
📜 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.
📒 Files selected for processing (1)
internal/exec/terraform_exitcode_test.go(1 hunks)
🧰 Additional context used
📓 Path-based instructions (4)
**/*_test.go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
**/*_test.go: Every new feature must include comprehensive unit tests
Test both happy paths and error conditions
Use table-driven tests for multiple scenarios
**/*_test.go: Use table-driven unit tests for pure functions and focus on behavior; co-locate tests; target >80% coverage for pkg/ and internal/exec/.
Always use t.Skipf() with a clear reason; never use t.Skip() or t.Skipf without a reason.
Files:
internal/exec/terraform_exitcode_test.go
**/*.go
📄 CodeRabbit inference engine (.cursor/rules/atmos-rules.mdc)
**/*.go: All code must pass golangci-lint checks
Follow Go error handling idioms and use meaningful error messages
Wrap errors with context using fmt.Errorf("context: %w", err)
Consider custom error types for domain-specific errors
Follow standard Go coding style; run gofmt and goimports
Use snake_case for environment variables
Document complex logic with inline comments
**/*.go: All Go comments must end with periods; applies to single-line, multi-line, inline, and documentation comments (golangci-lint godot).
Group imports into three sections (stdlib, 3rd-party, Atmos), separated by blank lines; sort alphabetically within each group; preserve existing aliases.
Configuration loading must use Viper with precedence CLI → ENV → files → defaults; bind config name atmos and add path, AutomaticEnv, and ATMOS prefix.
All errors must be wrapped using static errors (defined in errors/errors.go); use errors.Join for multiple errors; fmt.Errorf with %w for context; use errors.Is for checks; never compare error strings.
Distinguish structured logging from UI output: UI prompts/status/errors to stderr; data/results to stdout; never use logging for UI.
Most text UI must go to stderr; only data/results to stdout; prefer utils.PrintfMessageToTUI for UI messages.
All new configurations must support Go templating using existing utilities and available template functions.
Prefer SDKs over external binaries for cross-platform support; use filepath/os/runtime for portability.
For non-standard execution paths, capture telemetry via telemetry.CaptureCmd or telemetry.CaptureCmdString without user data.
80% minimum coverage on new/changed lines and include unit tests for new features; add integration tests for CLI using tests/ fixtures.
Always bind environment variables with viper.BindEnv and provide ATMOS_ alternatives for every env var.
Use structured logging with levels (Fatal>Error>Warn>Debug>Trace); avoid string interpolation and ensure logging does not affect execution.
Prefer re...
Files:
internal/exec/terraform_exitcode_test.go
internal/exec/**/*.go
📄 CodeRabbit inference engine (CLAUDE.md)
Place command business logic in internal/exec/ (separate from cmd/ wiring).
Files:
internal/exec/terraform_exitcode_test.go
internal/exec/*_test.go
📄 CodeRabbit inference engine (CLAUDE.md)
Add comprehensive tests for template functions under internal/exec/ with *_test.go files.
Files:
internal/exec/terraform_exitcode_test.go
🧠 Learnings (2)
📚 Learning: 2025-10-16T15:18:00.319Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-16T15:18:00.319Z
Learning: Applies to internal/exec/*_test.go : Add comprehensive tests for template functions under internal/exec/ with *_test.go files.
Applied to files:
internal/exec/terraform_exitcode_test.go
📚 Learning: 2025-10-16T15:18:00.319Z
Learnt from: CR
PR: cloudposse/atmos#0
File: CLAUDE.md:0-0
Timestamp: 2025-10-16T15:18:00.319Z
Learning: Applies to **/*_test.go : Use table-driven unit tests for pure functions and focus on behavior; co-locate tests; target >80% coverage for pkg/ and internal/exec/.
Applied to files:
internal/exec/terraform_exitcode_test.go
🧬 Code graph analysis (1)
internal/exec/terraform_exitcode_test.go (1)
errors/errors.go (1)
ExitCodeError(379-381)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (7)
- GitHub Check: Build (macos-latest, macos)
- GitHub Check: Build (windows-latest, windows)
- GitHub Check: Build (ubuntu-latest, linux)
- GitHub Check: Analyze (go)
- GitHub Check: website-deploy-preview
- GitHub Check: Lint (golangci)
- GitHub Check: Summary
🔇 Additional comments (5)
internal/exec/terraform_exitcode_test.go (5)
1-11: Imports look solid.Package declaration and imports are appropriate for the test. Good use of the
errUtilsalias for clarity.
72-79: Clean validation of Terraform flag syntax.This test ensures the flag constant matches Terraform's single-dash convention. Simple and effective.
81-95: Exit code preservation through wrapping looks good.This test validates that
errors.Ascorrectly extractsExitCodeErrorfrom wrapped errors, which is critical for preserving Terraform's exit code 2 (changes detected) signal.
97-130: Priority testing is thorough.
TestExitCodeExtractionPriorityclearly validates thatExitCodeErroris checked before falling back to generic error handling. Both paths are well covered.
132-189: Workspace error handling logic is well tested.This table-driven test comprehensively validates the workspace creation behavior for different error scenarios. The exit code 0 case (lines 158-162) is a bit artificial for an error path, but testing it defensively is fine.
Resolved conflicts in: - internal/exec/shell_utils.go: kept both viper import and exit code changes - internal/exec/shell_utils_test.go: merged auth shell tests with new ExecuteShellCommand tests Includes updates from main: - Exit code preservation for shell commands and workflows (#1660) - Custom golangci-lint build isolation (#1666) - Conductor auto-commits (#1650) - No-color flag fix (#1652) - Performance optimizations (#1639)
…gha-cache * 'fix-gha-cache' of github.com:cloudposse/atmos: Isolate AWS env vars during authentication (#1654) Changes auto-committed by Conductor (#1670) Add global --chdir flag for changing working directory (#1644) Fix: Preserve exit codes from shell commands and workflows (#1660) fix: isolate golangci-lint custom build to prevent git corruption (#1666)
|
These changes were released in v1.195.0-rc.1. |
* feat: Add `atmos auth shell` command for authenticated interactive sessions Implement `atmos auth shell` command to launch an interactive shell with authentication environment variables pre-configured. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: Run packer init before packer validate in test Fix flaky TestExecutePacker_Validate test by running `packer init` to install required plugins before validation. The test now: - Runs packer init to install the amazon plugin - Skips gracefully if init fails (network issues) - Proceeds with validation only if init succeeds This prevents the "Missing plugins" error that was causing test failures. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * test: Update golden snapshot to include new auth shell subcommand * test: Regenerate golden snapshot with proper terminal padding The snapshot test infrastructure pads output to terminal width. Regenerated using --regenerate-snapshots flag to match CI expectations. * fix: Resolve shell command path and honor Windows shell override Addresses CodeRabbit review feedback: 1. Use exec.LookPath to resolve shell command before os.StartProcess - os.StartProcess doesn't search PATH, so bare commands like 'bash' fail - Now resolves to absolute path for non-absolute commands 2. Honor --shell flag on Windows - Previously always used cmd.exe even when --shell was specified - Now respects shellOverride and viper config before falling back to cmd.exe - Only applies -l flag on Unix systems (not Windows) 3. Add osWindows constant to satisfy linting * docs: Add blog post introducing atmos auth shell Comprehensive blog post covering: - Problem statement and motivation - Key features (auto auth, custom shell, nesting, cross-platform) - Real-world use cases (Terraform workflows, debugging, long-running ops) - How it works under the hood - Getting started guide - Environment variables reference - Future enhancements * docs: Simplify blog post to focus on identity-based authentication Condensed key features section to emphasize the core value: identity-based authentication and seamless identity switching. Other features (custom shell, args, nesting, cross-platform) mentioned succinctly in one sentence. * docs: Refocus blog post on primary use case Changed messaging to emphasize the core purpose: launch a shell with active cloud credentials to work naturally without juggling environment variables or re-authenticating. * docs: Remove broken authentication guide link from blog post The /core-concepts/authentication page doesn't exist. Removed the link to fix website build. * docs: Remove command documentation link from blog post Docusaurus can't resolve internal doc links from blog posts during the build. Removed to fix website deploy. * docs: Fix auth shell prompt claim and improve test coverage - Remove incorrect claim about automatic shell prompt modification - Clarify that ATMOS_IDENTITY and ATMOS_SHLVL env vars are exposed - Add tip section showing how to manually customize prompt - Add test coverage for edge cases and environment binding - Improve auth_shell.go coverage from 40% to 67% - Document testing limitations for authentication paths 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * feat: Add mock authentication provider for testing - Create mock provider and identity for testing without cloud credentials - Register mock provider kind in auth factory - Add integration tests using mock provider - Improve auth_shell.go test coverage from 67% to 75% - Add test fixture with mock auth configuration - Enable end-to-end testing of auth shell command The mock provider is for testing purposes only and is not documented for end users. It enables comprehensive integration testing of authentication features without requiring real cloud credentials. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * test: Fix test isolation to prevent environment pollution - Move environment setup into each subtest for TestAuthShellCmd_FlagParsing - Remove test case that was susceptible to config caching - Tests now pass reliably regardless of execution order Fixes test pollution where environment variables from one test would bleed into subsequent tests causing failures in CI. * test: Make mock provider tests cross-platform compatible - Add OS-specific shell and command handling for Windows vs Unix - Use cmd.exe with /c on Windows, /bin/sh with -c on Unix - Explicitly specify shell via --shell flag in tests - Simplify environment variable test to just verify execution Fixes test failures on Windows CI where /bin/bash doesn't exist. * feat: Add shell completion for identity flag in auth shell - Register identity flag completion using AddIdentityCompletion - Enables shell completion to suggest available identities - Matches completion behavior of other auth subcommands * docs: Correct auth shell environment variable documentation - Fix incorrect claim that credentials are exported directly - Clarify that Atmos sets AWS config file paths, not raw credentials - Document actual environment variables: AWS_SHARED_CREDENTIALS_FILE, AWS_CONFIG_FILE, AWS_PROFILE - Emphasize security: credentials never exposed in environment - Explain compatibility with AWS SDK-based tools This corrects the documentation to match actual implementation where Atmos writes credentials to managed config files and sets environment variables pointing to those files, rather than exposing sensitive credentials directly in the environment. * docs: Update auth shell command documentation for secure credential handling - Correct environment variable documentation to show actual AWS config paths - Add info box explaining secure credential handling approach - Document that credentials are written to managed config files, not exposed - Clarify AWS SDK compatibility with file-based credential approach - Remove misleading reference to raw credential environment variables Matches the same correction made to the blog post. * security: Fix mock provider to prevent credential leaks in serialization - Move sensitive credentials from Environment map to Credentials field - Environment map is JSON-serializable and should not contain secrets - Only populate non-sensitive env vars (AWS_REGION, AWS_DEFAULT_REGION) - Add nil-check for info pointer before populating - Only set Expiration when non-zero - Update mock identity to provide AWS config file paths instead of raw credentials - Add comprehensive tests to verify no secret leakage in JSON serialization The mock provider now follows the same secure pattern as real AWS implementations, storing credentials in the non-serializable info.Credentials field and only exposing file paths in Environment. * feat: Add performance tracking and viper precedence to auth shell command - Add perf.Track instrumentation to executeAuthShellCommand and executeAuthShellCommandCore - Change identity retrieval to use viper for CLI → ENV → config precedence - Bind identity flag to viper with ATMOS_IDENTITY support - Store atmosConfig as pointer for consistent downstream usage * test: Add enabled tests for auth commands using mock provider - Add tests for 'atmos auth whoami' with mock identity - Add tests for 'atmos auth env' in json, bash, and dotenv formats - Add test for 'atmos auth exec' with simple command - Add test for 'atmos auth login' with mock identity - All tests use fixtures/scenarios/atmos-auth-mock/ workdir - These replace the previously disabled tests that required real cloud credentials * test: Add mock provider test for auth login - Adds enabled test for 'atmos auth login --identity mock-identity' - Uses fixtures/scenarios/atmos-auth-mock/ workdir with mock provider - Login succeeds without real cloud credentials - Other auth commands (whoami, env, exec) remain disabled as they require credentials to be persisted to keyring, which doesn't work in test isolation * Changes auto-committed by Conductor * [autofix.ci] apply automated fixes * test: fix test snapshots for keyring configuration and mock auth * fix: improve keyring and mock provider code quality - Make keyring_file_test hermetic by using temp HOME directory - Fix keyring_file_test password assertion to actually test behavior - Add perf tracking to keyring_file.go constructor - Fix error wrapping in all keyring implementations to use errors.Join - Add perf tracking to all mock provider methods - Add nil check to mock provider NewProvider All changes follow project coding standards and error handling guidelines. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * Merge branch 'main' into atmos-auth-shell Resolved conflicts in: - internal/exec/shell_utils.go: kept both viper import and exit code changes - internal/exec/shell_utils_test.go: merged auth shell tests with new ExecuteShellCommand tests Includes updates from main: - Exit code preservation for shell commands and workflows (#1660) - Custom golangci-lint build isolation (#1666) - Conductor auto-commits (#1650) - No-color flag fix (#1652) - Performance optimizations (#1639) * chore: trigger CI rebuild Re-run CI after merge conflict resolution to verify all checks pass * fix: improve keyring file test hermiticity and error wrapping - Make TestFileKeyring_EmptyConfig hermetic by using t.TempDir() for HOME - Fix error wrapping in password prompt to use %w instead of %v - Ensures proper error unwrapping for callers checking specific error types * fix: remove 'go get' from Makefile to prevent internal package resolution errors The 'go get' command without arguments tries to resolve ALL imports as external modules, including internal packages like internal/tui/atmos. This causes CI build failures with 'cannot find module providing package' errors. Modern Go (1.17+) deprecates 'go get' for building. Use 'go mod download' for dependencies instead (available via 'make deps'). * fix: pre-download Go dependencies in pre-commit CI workflow Add 'go mod download' step before running pre-commit hooks to ensure all module dependencies are cached. This prevents the go-mod-tidy hook from trying to fetch internal packages as external modules. * fix: unignore internal/tui/atmos and other atmos directories Changed .gitignore rule from 'atmos' to '/atmos' to only ignore the atmos binary in the root directory. The previous rule was ignoring ALL files and directories named 'atmos', including: - internal/tui/atmos/ (TUI package for atmos command) - pkg/datafetcher/schema/atmos/ (schema files) - tests/fixtures/schemas/atmos/ (test schemas) - website/static/schemas/atmos/ (website schemas) - website/static/img/cli/atmos/ (CLI screenshots) - Example atmos.yaml config files This was causing CI build failures with 'no required module provides package github.com/cloudposse/atmos/internal/tui/atmos' because the source files were not committed to git. * fix: normalize timestamps in test snapshots and add empty-dir fixture 1. Add timestamp normalization to sanitizeOutput function - Replace 'Expires: YYYY-MM-DD HH:MM:SS TZ' with 'Expires: <TIMESTAMP>' - Prevents snapshot mismatches due to timezone/time differences 2. Update mock-identity stdout golden file with <TIMESTAMP> placeholder 3. Add empty-dir fixture .gitignore to repository - Tests require this directory to exist for 'empty-dir' test cases - Directory has .gitignore with '*' to keep it intentionally empty * docs: document keyring backends in auth usage documentation Added comprehensive documentation for all three keyring backends: - System keyring (default, OS-native) - File keyring (encrypted file with password prompting) - Memory keyring (in-memory for testing) Includes configuration examples, use cases, and password resolution order for file keyring. * docs: clarify when to use 'auth shell' vs 'auth exec' Added concise comparison notes to both command docs to help users quickly understand the difference: - shell: for interactive multi-command sessions - exec: for single commands and automation Each doc now cross-links to the other for easy reference. * docs: move shell vs exec comparison to tip callout Moved the comparison note out of the intro and into a concise tip callout between the help screenshot and usage section. This provides a quick reference without cluttering the introduction. * fix: make 'get' target delegate to 'deps' for dependency download Changed get target to invoke deps (go mod download) instead of being a no-op. This fixes the inconsistency where build-default, build-windows, lint, testacc, testacc-cover, test-short, and test-short-cover all depend on 'get' but it wasn't downloading dependencies. Maintains backward compatibility for any targets depending on 'get' while following modern Go practices (go mod download instead of go get). * fix: use placeholder timestamp instead of generic token in snapshots Changed timestamp sanitization from '<TIMESTAMP>' to a realistic fixed timestamp '2025-01-01 00:00:00 UTC' to make snapshots more readable and consistent with other placeholder patterns in the codebase. Updated snapshot file to match the new placeholder format. * fix: remove 'go get' from Makefile and use 'deps' directly Removed the deprecated 'get' target entirely and updated all targets to depend on 'deps' directly: - lint: get → deps - build-default: get → deps - build-windows: get → deps - testacc: get → deps - testacc-cover: get → deps - test-short: get → deps - test-short-cover: get → deps This eliminates the unnecessary indirection and makes it clear that 'go mod download' is used for dependency management. The 'get' target was a no-op that we don't need to maintain. * fix: correct broken links in auth shell and exec documentation Fixed broken documentation links: - /cli/commands/auth/auth-exec → /cli/commands/auth/exec - /cli/commands/auth/auth-shell → /cli/commands/auth/shell The page paths don't include the 'auth-' prefix in the URL. * fix: update auth login test snapshots to match current output format The auth login command outputs to stderr (via PrintfMessageToTUI), not stdout. Updated snapshots to reflect this: - stdout.golden is now empty (no stdout output) - stderr.golden has normalized timestamp placeholder (2025-01-01 00:00:00 UTC) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: exclude problematic godbus/dbus version to resolve license issue The CI license checker flagged github.com/godbus/dbus v0.0.0-20190726142602-4481cbc300e2 (pulled by 99designs/keyring and versent/saml2aws) as having an incompatible BSD-2-Clause license. Added exclude directive to force resolution to v4.1.0+incompatible instead. All tests pass with the newer version. If v4.1.0 still fails license check (also BSD-2-Clause), we'll need to either: 1. Get BSD-2-Clause added to allowed license list 2. Replace 99designs/keyring with custom AES encryption for file backend 3. Make keyring backends optional/build-tag gated 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: add NOTICE file with third-party license attributions Added NOTICE file documenting third-party dependencies with non-Apache-2.0 licenses, specifically: - godbus/dbus (BSD-2-Clause) - Used indirectly via 99designs/keyring and versent/saml2aws for Linux D-Bus keyring access - 99designs/keyring (MIT) - Used for encrypted file-based credential storage Both licenses are compatible with Apache-2.0 and approved by the Apache Software Foundation (Category A). This follows ASF best practices for documenting third-party dependencies. Reference: https://www.apache.org/legal/resolved.html#category-a 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: use viper.BindEnv instead of os.Getenv for environment variables Changed environment variable handling to use viper.BindEnv instead of raw os.Getenv, following Atmos conventions per CLAUDE.md: > ALWAYS use viper.BindEnv() for environment variable binding Also fixed most linting issues: - Replaced dynamic errors with static errors (err113) - Added error return value checks (errcheck) - Fixed missing comment periods (godot) - Extracted magic number 0o700 to named constant Remaining linting issues (non-blocking): - cyclomatic complexity in newFileKeyringStore (12 > 10) - acceptable for setup function - unparam in newMemoryKeyringStore - error return for interface consistency - magic number in mock provider test - test data, acceptable 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: fix misleading terraform command examples in auth blog posts Replaced `terraform plan` and `terraform apply` examples in the "Use It Standalone" sections with better examples like `aws s3 ls`, `aws ecs list-clusters`, and `kubectl get pods`. The previous examples were misleading because: - `atmos terraform *` commands natively support `--identity` flag - Using `eval $(atmos auth env)` with raw `terraform` commands suggests bypassing Atmos's built-in identity management - Better examples show using auth for tools that don't have native identity support (AWS CLI, kubectl, etc.) Changed in: - website/blog/2025-10-13-introducing-atmos-auth.md - website/blog/2025-10-15-atmos-auth-shell.mdx 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: emphasize isolation and safety benefits of atmos auth shell Expanded the "Why This Matters" section to clearly articulate the core value proposition: session isolation and credential safety. Key messaging changes: - Traditional approach (shared ~/.aws/credentials) is fundamentally unsafe - atmos auth shell provides isolation: each shell = one identity = one set of credentials - Parent shell environment remains unchanged when you exit - Enables multiple concurrent authenticated sessions without cross-contamination - Credentials are ephemeral and session-scoped Updated real-world examples to emphasize: - Safe production operations with explicit credential cleanup - Multiple concurrent terminals, each with different isolated credentials - Prevention of accidental cross-environment commands This addresses the core differentiation: why use atmos auth shell instead of traditional AWS credential management approaches. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * perf: add performance tracking to keyring store methods Added perf.Track() instrumentation to all exported methods in the keyring credential stores for visibility and consistency with CLAUDE.md guidelines: > Add `defer perf.Track()` to all public functions and critical private functions > Always include a blank line after the perf.Track call Changes: - Added perf.Track to constructors: - NewCredentialStore() - NewCredentialStoreWithConfig() - Added perf.Track to fileKeyringStore methods: - Store, Retrieve, Delete, List, IsExpired, GetAny, SetAny - Added perf.Track to memoryKeyringStore methods: - Store, Retrieve, Delete, List, IsExpired, GetAny, SetAny - Added perf.Track to systemKeyringStore methods: - Store, Retrieve, Delete, List, IsExpired, GetAny, SetAny These are I/O-heavy operations (file/keyring access) where performance tracking provides valuable visibility into credential storage latency. All tests pass with instrumentation in place. Remaining linting issues (non-blocking): - function-length in newFileKeyringStore (61 > 60) - acceptable for setup - add-constant for mock test timestamp - acceptable for test data - unparam for newMemoryKeyringStore - kept for interface consistency 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: remove homedir replace directive for go install compatibility - Remove replace directive that redirected mitchellh/go-homedir to ./pkg/config/homedir - This restores go install compatibility merged from main - Keep exclude directive for incompatible dbus version 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: use configured path as keyring storage directory - Replace `filepath.Dir(path)` with `path` directly as the storage directory - Update comment to clarify path is the directory (e.g., ~/.atmos/keyring) - Files now stored inside the configured directory instead of its parent - Fixes incorrect storage location when custom path is specified Refactoring and linting fixes: - Extract parseFileKeyringConfig and getDefaultKeyringPath helpers - Reduce cyclomatic complexity in newFileKeyringStore (12 -> 8) - Remove error return from newMemoryKeyringStore (always returned nil) - Add mock timestamp constants to avoid magic numbers - Update all test calls to match new signatures - Add nolint:dupl for intentional test duplication 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * feat: use XDG Base Directory Specification for keyring storage - Follow XDG Base Directory Specification for file keyring location - Default path now: $XDG_DATA_HOME/atmos/keyring (typically ~/.local/share/atmos/keyring) - Support ATMOS_XDG_DATA_HOME override following existing XDG pattern from cache.go - Fallback to XDG library default if environment variables not set - Update tests to set XDG_DATA_HOME for hermetic testing - Import github.com/adrg/xdg for cross-platform XDG support Benefits: - Consistent with XDG standards used by modern Linux applications - Separates data (credentials) from config files - Allows users to control storage location via standard XDG_DATA_HOME - Maintains backward compatibility via environment variable override 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: add keyring backends PRD Create comprehensive PRD documenting the credential storage backend system: - Overview of system, file, and memory keyring backends - Backend selection priority (env var > config > default) - XDG Base Directory Specification compliance for file backend - Security considerations for each backend - Configuration examples for different deployment scenarios - Data formats and credential envelope structure - Testing strategy and isolation patterns - Migration paths and backward compatibility - Performance instrumentation approach This is retroactive documentation of the implemented keyring system, capturing design decisions, architecture, and usage patterns. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: fix punctuation and clarify system keyring List() docstring - Add missing period to list item in auth shell blog post - Update systemKeyringStore.List() docstring to accurately reflect that the method is not supported (due to go-keyring limitations) and returns an error instead of aliases - Clarify that callers should use errors.Is to detect not-supported condition 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: eliminate test duplication using shared test suite - Create suite_test.go with shared CredentialStore test suite - Use factory pattern to test all backend implementations - Remove duplicate testStoreAndRetrieve, testIsExpired, testDelete tests - Keep backend-specific tests (List, ConcurrentAccess, Isolation) - Remove nolint:dupl directive (no longer needed) Benefits: - Eliminates code duplication across backend tests - Ensures all backends implement interface correctly - Makes it easy to add new backends with full test coverage - Reduces maintenance burden (update tests once, applies to all backends) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: create reusable XDG utility to eliminate duplication - Add pkg/utils/xdg.go with GetXDGCacheDir, GetXDGDataDir, GetXDGConfigDir functions - Centralize XDG Base Directory Specification logic in one place - Refactor pkg/config/cache.go to use GetXDGCacheDir utility - Refactor pkg/auth/credentials/keyring_file.go to use GetXDGDataDir utility - Add comprehensive tests for XDG utility functions - Add CacheDirPermissions and KeyringDirPermissions constants Benefits: - DRY: XDG logic defined once, used everywhere - Consistency: All XDG paths follow same precedence (ATMOS_XDG_* > XDG_* > default) - Maintainability: Changes to XDG handling only need to happen in one place - Testability: Easier to test XDG path resolution in isolation - Documentation: Clear API with GetXDGCacheDir/DataDir/ConfigDir functions Environment variable precedence (for all XDG functions): 1. ATMOS_XDG_*_HOME (Atmos-specific override) 2. XDG_*_HOME (standard XDG variable) 3. XDG library default (platform-specific from github.com/adrg/xdg) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * test: add TestMemoryKeyring_GetAnySetAny for implementation-specific method - Re-add GetAnySetAny test to memory keyring (accidentally removed during refactoring) - GetAny/SetAny are implementation-specific helper methods, not part of CredentialStore interface - Each backend has its own GetAny/SetAny test since they're not in the shared suite - Shared suite only tests interface methods (Store, Retrieve, Delete, IsExpired) Testing strategy: - Shared suite (suite_test.go): Tests interface contract methods - Backend-specific tests: Test implementation-specific features - Memory: List, GetAny/SetAny, ConcurrentAccess, Isolation - File: GetAny/SetAny, custom paths, password handling, error cases - System: GetAny/SetAny (List returns not-supported error) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: extract XDG utilities to dedicated package Move XDG Base Directory Specification utilities from pkg/utils to pkg/xdg for better organization and clarity. This follows Go idiom of focused, single-purpose packages. Changes: - Create pkg/xdg package with GetXDGCacheDir, GetXDGDataDir, GetXDGConfigDir - Update pkg/config/cache.go to use xdg package - Update pkg/auth/credentials/keyring_file.go to use xdg package - All tests pass with new package structure 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: document XDG Base Directory Specification support Add documentation for XDG environment variables in global flags reference and update file keyring documentation to explain XDG-based default storage location. Changes: - Add ATMOS_XDG_CACHE_HOME, ATMOS_XDG_DATA_HOME, ATMOS_XDG_CONFIG_HOME to global-flags.mdx - Document XDG_CACHE_HOME, XDG_DATA_HOME, XDG_CONFIG_HOME fallback support - Explain default storage locations per platform - Update file keyring docs to mention XDG Base Directory Specification compliance - Add examples for customizing XDG directories - Link to XDG Base Directory Specification 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: add XDG Base Directory Specification PRD Create comprehensive PRD documenting Atmos's implementation of the XDG Base Directory Specification for organizing user data files. This PRD covers: - Problem statement and requirements - Technical architecture and API design - Platform-specific defaults (Linux, macOS, Windows) - Environment variable precedence (ATMOS_XDG_* > XDG_* > defaults) - Integration points (cache, keyring, future config) - Security considerations and file permissions - Testing strategy with hermetic isolation - Migration path from legacy ~/.atmos/ locations - Future enhancements (config discovery, migration tool) The PRD documents the rationale for using isolated Viper instances rather than global Viper/Cobra integration, explains why XDG paths are environment- only (not CLI flags), and provides complete reference tables for default file locations on each platform. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * test: increase XDG package coverage to 92.9% Add comprehensive error path testing for XDG directory utilities to exceed the 80% minimum coverage requirement. New tests: - TestGetXDGDir_MkdirError: Tests directory creation failure when a file blocks the path (covers os.MkdirAll error path) - TestGetXDGDir_DefaultFallback: Tests library default fallback when no environment variables are set (covers empty env var path) Coverage improvements: - Before: 78.6% (below minimum requirement) - After: 92.9% (exceeds 80% requirement) - getXDGDir: 72.7% → 90.9% - All public functions: 100% All 9 tests pass successfully with proper error handling and edge case coverage. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * test: update auth login snapshot for new table format Update golden snapshots and regex pattern to match the new authentication success output format which uses a formatted table with checkmark instead of plain text. Changes: - Update expiresRegex to match new table format with relative time - Handle optional relative time suffix "(59m 59s)" in expiration line - Update stderr golden snapshot with new table format: - Uses ✓ checkmark instead of **bold** text - Displays info in aligned table columns - Normalized expiration to fixed timestamp - Update stdout golden snapshot (was empty, now matches stderr) The regex now matches both old format "Expires: timestamp" and new format "Expires timestamp (relative)" and normalizes both to "Expires 2025-01-01 00:00:00 UTC" for consistent snapshot testing. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * refactor: improve error handling and validation across auth and xdg packages Implement comprehensive improvements to error handling, validation, and testing across authentication credential storage and XDG utilities. **Keyring File Changes (pkg/auth/credentials/keyring_file.go)**: - Add MinPasswordLength constant (8 characters) for consistent validation - Validate environment-supplied passwords meet minimum length requirement - Map keyring.ErrKeyNotFound to ErrCredentialsNotFound in Retrieve() - Map keyring.ErrKeyNotFound to ErrDataNotFound in GetAny() - Improve error message for credential envelope marshal failure - Use constant instead of magic number for password length validation **Credential Store Changes (pkg/auth/credentials/store.go)**: - Add final fallback to memory keyring if system keyring fails - Ensure NewCredentialStore() never returns nil - Improve warning messages to indicate fallback chain **XDG Changes (pkg/xdg/xdg.go)**: - Fix environment variable precedence with separate BindEnv calls - Explicitly check ATMOS_XDG_*_HOME before XDG_*_HOME - Add error handling for each BindEnv call - Clarify precedence in comments and implementation **Test Improvements**: - Fix goroutine test assertions in keyring_memory_test.go - Use error channel to collect errors from goroutines - Avoid testing.T method calls from goroutines (data race) - Update test expectations for new error handling behavior All tests pass with improved coverage: - pkg/auth/credentials: 80.1% coverage - pkg/xdg: 88.9% coverage 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: make mock provider timestamps deterministic for snapshot test stability Replace dynamic time.Now().Add(1 * time.Hour) with fixed timestamp using MockExpiration* constants in mock provider and identity to ensure snapshot tests remain stable and don't become flaky due to changing timestamps. Changes: - Updated MockExpirationYear from 2025 to 2099 (far future) - Updated MockExpirationMonth/Day/Hour to 12/31/23 - Added MockExpirationMinute and MockExpirationSecond constants (59/59) - Changed identity.go to use all shared MockExpiration* constants - Updated comments to explain deterministic testing rationale The test framework normalizes all expiration timestamps to 2025-01-01 00:00:00 UTC via regex replacement for snapshot comparison, so the actual 2099-12-31 23:59:59 timestamp is replaced during test execution. This ensures the mock provider always returns a far-future expiration that won't cause expiration-related test failures while maintaining deterministic snapshot testing. Updated snapshots to match the normalized timestamp format with proper table padding. * fix: normalize only duration in expiration timestamps, preserve actual timestamp Change the snapshot normalization to only remove the relative duration part (e.g., "(59m 59s)") from expiration timestamps instead of replacing the entire timestamp with a hardcoded value. Before: Replaced entire timestamp with "2025-01-01 00:00:00 UTC" After: Only strips duration, preserving actual timestamp like "2099-12-31 23:59:59 UTC" This allows the actual expiration timestamp to be visible in snapshots while still avoiding flakiness from the time-sensitive duration part. The regex now uses a capture group to preserve the timestamp portion while removing only the duration in parentheses. Updated snapshot to show the actual mock provider timestamp (2099-12-31 23:59:59 UTC) instead of the normalized placeholder. * fix: normalize duration in expiration timestamps to deterministic placeholder Change the snapshot normalization to replace the relative duration part (e.g., "(59m 59s)", "(expired)") with a deterministic placeholder "(in 1h)" instead of removing it entirely. This follows the established pattern of replacing dynamic values with placeholders rather than deleting them, maintaining consistent output format while avoiding snapshot flakiness. The regex preserves the actual timestamp (e.g., 2099-12-31 23:59:59 UTC) while normalizing only the time-sensitive duration portion. Updated snapshot to include the normalized duration placeholder. * fix: use realistic duration format in expiration timestamp placeholder Change the duration placeholder from "(in 1h)" to "(1h 0m)" to match the actual format produced by the formatDuration function. The formatDuration function produces formats like: - "1h 30m" for hours and minutes - "30m 15s" for minutes and seconds - "45s" for just seconds - "expired" for negative durations Using "1h 0m" as the placeholder matches the actual output format and represents a realistic value that could be produced by the duration calculation, rather than an invalid format with the word "in". * fix: use MockExpirationMinute and MockExpirationSecond in provider timestamp Update the time.Date call in Provider.Authenticate to use the defined MockExpirationMinute and MockExpirationSecond constants (59, 59) instead of hardcoded zeros (0, 0). This ensures consistency with the Identity.Authenticate implementation and produces the correct timestamp of 2099-12-31 23:59:59 UTC for both the provider and identity authentication paths. Updated the comment to reflect the actual timestamp including minutes and seconds. * [autofix.ci] apply automated fixes * docs: reposition atmos auth shell blog post to emphasize security Refocused the blog post to highlight session isolation and security as the primary value proposition, following the aws-vault exec model. Key changes: - Updated title to emphasize "Isolated Shell Sessions for Secure Multi-Identity Workflows" - Rewrote problem statement to focus on credential leakage and context confusion - Enhanced solution section to emphasize session-scoped security and isolation - Added AWS Vault comparison table showing feature parity across cloud providers - Added "When to Use" section clarifying atmos auth shell vs atmos auth env - Removed redundant "Why This Matters" section - Updated tags from [atmos, authentication, shell, aws, cloud] to [feature, atmos-auth, security, workflows] 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: tone down hyperbole in atmos auth shell blog post Removed "powerful" and other superlatives to make the writing more genuine and approachable. Changed "powerful new command" to just "new command" and "powerful workflow" to "useful workflow". 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: clarify credentials are removed from environment, not deleted Fixed inaccurate language that suggested credentials are "removed" or "gone" entirely. Credentials are removed from the shell environment on exit, but the underlying authentication session remains active (logout is a separate action). Changes: - "credentials are automatically removed" → "credentials are removed from your environment" - "those credentials disappear" → "those credentials are removed from the environment" - "those production credentials are gone" → "those production credentials are removed from your environment" - Updated comparison table to clarify "removed from environment on exit" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: clarify shell destruction and parent shell return Replaced "credentials removed from environment" language with more accurate description: when you exit, the shell is destroyed and you return to your parent shell where those credentials were never present. Changes: - "credentials are removed from your environment" → "you return to your parent shell where those credentials were never present" - "those credentials are removed from the environment" → "the shell is destroyed and you return to your parent shell where those credentials don't exist" - "those production credentials are removed from your environment" → "you're back in your parent shell where those production credentials don't exist" - Table: "Credentials removed from environment on exit" → "Return to parent shell on exit" 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: refocus keyring blog on value, not history Removed "Atmos Auth previously..." language since the feature just launched. Emphasized the Docker/Dev Container use case as the key benefit of file-based keyrings—solving the cross-OS boundary problem where system keyrings aren't accessible inside containers. Key changes: - Replaced "The Problem" section with "Why Different Keyring Types Matter" - Removed historical references ("previously relied exclusively", "previously these tests were skipped") - Added Docker/Dev Container as primary example for file-based keyrings - Updated introduction to highlight cross-boundary credential sharing - Simplified "What This Means for You" to "What This Gives You" - Removed "backward compatibility" language from system keyring description 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: handle ErrNotFound in Delete and update snapshots Fixed TestDelete_Flow test failure by treating keyring.ErrNotFound as success (idempotent delete). Updated snapshots to include proper trailing whitespace formatting for command output alignment. Changes: - pkg/auth/credentials/keyring_system.go: Check for keyring.ErrNotFound in Delete() - tests/snapshots: Updated trailing whitespace to match actual CLI output formatting 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * perf: add performance tracking to mock identity methods Added perf.Track instrumentation to all public methods in mock Identity to comply with performance tracking guidelines. Updated cache.go doc comment to accurately describe current behavior. Changes: - pkg/auth/providers/mock/identity.go: Added perf.Track to all public methods - NewIdentity, Kind, GetProviderName, Authenticate, Validate, Environment, PostAuthenticate, Logout - pkg/config/cache.go: Updated GetCacheFilePath doc comment to remove outdated reference to environment variable binding 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: update CLAUDE.md with correct snapshot regeneration instructions Replaced incorrect snapshot update commands with the documented approach from tests/README.md. The correct method is to use the -regenerate-snapshots flag, not environment variables. Changes: - Replaced "Golden Snapshots (MANDATORY)" section with accurate information - Added snapshot types explanation (.stdout.golden, .stderr.golden, .tty.golden) - Documented proper regeneration commands using -regenerate-snapshots flag - Added workflow for reviewing and committing regenerated snapshots - Added reference to tests/README.md for complete documentation 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs: use semantic definition list for environment variables Replaced bulleted list with semantic <dl> for environment variables in atmos auth shell blog post, following documentation guidelines. Added "(AWS identities only)" qualifier to AWS-specific variables. Changes: - Converted bullet points to <dl> with <dt><code>...</code></dt> and <dd> - ATMOS_IDENTITY, ATMOS_SHLVL: General variables - AWS_SHARED_CREDENTIALS_FILE, AWS_CONFIG_FILE, AWS_PROFILE: AWS-only 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: update suite test to expect idempotent delete behavior Changed suite_test.go to expect NoError when deleting a non-existent credential, matching the idempotent behavior implemented in the Delete method and consistent with main branch expectations. Changes: - suite_test.go: assert.Error → assert.NoError for non-existent delete - Updated comment to clarify idempotent behavior 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * fix: make Delete idempotent for all keyring backends Updated memory and file keyring stores to treat "not found" as success when deleting credentials, matching the idempotent behavior of the system keyring store. Changes: - keyring_memory.go: Remove error check, just delete (idempotent) - keyring_file.go: Check for keyring.ErrKeyNotFound and return nil This ensures consistent idempotent delete behavior across all three credential store backends (system, file, memory). 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> --------- Co-authored-by: Claude <noreply@anthropic.com> Co-authored-by: autofix-ci[bot] <114827586+autofix-ci[bot]@users.noreply.github.com> Co-authored-by: Andriy Knysh <aknysh@users.noreply.github.com>
what
ShellRunnerto capture and preserve exit codes from shell commands usinginterp.ExitStatus.CheckErrorPrintAndExitto correctly handleerrUtils.ExitCodeError, ensuring preserved exit codes are used for program termination.internal/exec/terraform.gowhere exit code 2 fromterraform plan -detailed-exitcodewas not being preserved.why
ShellRunner: Previously,ShellRunnerusing themvdan.cc/shinterpreter would lose specific exit codes, often resulting in a generic exit code of 1 for failures. This change ensures that any non-zero exit code from a shell command is captured and propagated.CheckErrorPrintAndExit: The error handling function was not equipped to recognize or process theExitCodeErrortype created by theShellRunnerfix. This update allowsCheckErrorPrintAndExitto correctly identify and utilize the preserved exit code for program termination.ErrWorkflowStepFailed. This change allows the original error, including its exit code, to be returned, providing more accurate failure reporting.terraform plan -detailed-exitcodewhen it returned exit code 2 (indicating changes were detected). This fix ensures that exit code 2 is preserved, allowing subsequent steps to correctly interpret the plan's status.references
closes #123(Assuming this fixes an issue, replace Opt in or opt out analytics for how atmos is used #123 with the actual issue number)Summary by CodeRabbit
New Features
Bug Fixes
Chores
Tests