feat(auth): add azure/interactive browser authentication provider Andriy Knysh (@aknysh) (#2862)
what
- New
azure/interactiveprovider kind: MSAL interactive browser authentication — authorization code + PKCE on a localhost redirect, the same flowaz loginuses (AcquireTokenInteractive). One command (atmos auth login) opens the browser, completes SSO, and mints Management/Graph/Key Vault tokens. interactiveProviderembedsdeviceCodeProvider, reusing the MSAL client, silent token acquisition (refresh tokens make repeat logins silent — no browser), token fan-out, and the Azure CLI cache write-back with the correct guest home account ID from #2861. Spec shape is identical toazure/device-code.- The shared machinery is parameterized by auth method: credentials persist
auth_method: interactive, and the MSAL cacheaccount_sourcemirrors az's own labels (authorization_codefor the browser flow,device_codeotherwise). - Docs: provider reference, Azure tutorial (interactive flow recommended for humans; device code reframed as fallback with the Conditional Access caveat), kind lists, blog post (
azure-interactive-auth), shipped roadmap milestone, PRD (docs/prd/azure-interactive-auth.md), and the fix doc for #2861 (docs/fixes/2026-08-03-azure-cli-cache-corruption-guest-users.md). - Test coverage ≈92% of changed lines: two injection seams (
acquireInteractive,checkInteractive) per the repo's DI convention, since the real interactive flow needs a live IdP and a browser; tests cover the success path, acquisition failure, and headless refusal against a sandboxedHOME.
why
- Microsoft-managed Conditional Access policies now block the device code flow in many tenants, so
azure/device-codeincreasingly fails;azure/clirequires a pre-existingaz loginsession (two commands);azure/oidcis CI-only. Azure users had no one-command human login equivalent toaws/iam-identity-center. - The interactive browser flow carries full Conditional Access context (MFA, device state), so tenants allow it — and it uses the Azure CLI public client, which pre-authorizes localhost redirects.
why the name azure/interactive
"Interactive" is Microsoft's own term for this flow, not our invention:
- MSAL's API for it is literally
AcquireTokenInteractive, and Microsoft's flow taxonomy divides authentication into "interactive and non-interactive flows" — where "interactive" specifically means the browser-based authorization-code sign-in a user completes, as opposed to device code, silent, or client-credential flows. - Provider kinds in Atmos name the auth mechanism, not the UX:
aws/iam-identity-centeropens a browser too, but the kind names the mechanism;aws/samltreats the browser as adriver:option;gcp/workload-identity-federationnames the federation mechanism.azure/interactivefollows the same rule using the platform's own vocabulary. - Alternatives considered:
azure/browser(self-explanatory but bakes UX into the kind name, contrary to the convention above) and aspec.flow: browseroption onazure/device-code(avoids a new kind, but makes the kind name actively misleading when the flow isn't device code).
manual verification
Tested end to end in a real Entra tenant where the device code flow is blocked by a Microsoft-managed Conditional Access policy, with an operator who is a guest (B2B) user in that tenant. Starting from a fully wiped ~/.azure:
- One-command login —
atmos auth loginopened the default browser, SSO completed (no device code, noaz login), and tokens were minted. - Silent repeat login — running
atmos auth loginagain succeeded without opening the browser (same token expiry), confirming refresh-token persistence. atmos auth whoami— reported provider, identity, subscription principal, tenant, and expiry.- az CLI drop-in —
az account showworked even thoughaz loginwas never run, thanks to the Azure CLI-compatible cache write-back. - Cache forensics — exactly one MSAL Account entry, labeled
account_source: authorization_code(matching what az itself records), carrying the true home account ID. This is the guest-user case that used to corrupt the az cache before #2861.
Details in the PRD's Verification section (docs/prd/azure-interactive-auth.md).
references
- Builds on #2861 (guest-user az cache corruption fix — the
AuthMethod/HomeAccountIDplumbing) - MSAL interactive flow: https://learn.microsoft.com/en-us/entra/identity-platform/msal-authentication-flows
- Microsoft-managed Conditional Access policies (device code blocking): https://learn.microsoft.com/en-us/entra/identity/conditional-access/managed-policies
Summary by CodeRabbit
-
New Features
- Added Azure interactive browser authentication using authorization code + PKCE.
- Supports silent sign-in from cached credentials, MFA, and Azure CLI compatibility.
- Added the
azure/interactiveprovider and browser sign-in prompts.
-
Bug Fixes
- Prevented Azure CLI token-cache corruption for guest users.
- Preserved authentication details across Azure sign-in methods.
-
Documentation
- Updated Azure authentication guides, examples, and provider references.
- Added guidance for Conditional Access scenarios, troubleshooting, and interactive authentication.
refactor: consolidate file locking Erik Osterman (Cloud Posse) (@osterman) (#2856)
what
- Consolidate production file locking behind
pkg/cache.FileLock, covering cloud configuration, Helm repositories, and workdir metadata. - Add explicit lock-path and nonblocking shared-read APIs with Unix and Windows coverage.
why
- Preserve each caller's existing lock paths and timeouts while removing duplicated
gofrs/flockacquisition and release logic. - Standardize Windows graceful degradation and prevent contended metadata reads from falling back to unlocked access.
references
- None.
Summary by CodeRabbit
-
Bug Fixes
- Improved reliability when credential, configuration, cache, repository, and metadata files are accessed simultaneously.
- Added consistent lock timeouts and clearer handling when files are temporarily unavailable.
- Preserved existing updates, cleanup behavior, and error reporting during file operations.
-
New Features
- Added non-blocking read-lock support for safer access to shared files.
- Improved coordination of file access across supported platforms.
docs: add succinct Gomplate datasource example Erik Osterman (Cloud Posse) (@osterman) (#2866)
what
- Adds a concise "Example: Using a Gomplate Datasource" section to the Datasources documentation, showing how to configure a
file://datasource and reference its values from a component'svars.
why
- Issue #2650 asked for a practical example of using Gomplate datasources.
- Two independent PRs (#2748, #2651) were opened to address it, both adding a much longer, AWS-specific walkthrough. Neither was updated after review feedback asking for something more succinct.
- This lands a minimal, general-purpose example directly, superseding both.
references
Summary by CodeRabbit
- Documentation
- Added an advanced example showing how to configure and use a Gomplate file datasource in a Terraform component template.
- Documented rendering the component with
atmos describeand viewing the resulting YAML output. - Clarified the distinction between Gomplate datasources and Atmos
!includefunctions.
feat(store): add atmos store CRUD CLI and type: store workflow step Erik Osterman (Cloud Posse) (@osterman) (#2858)
what
- Adds
atmos store— a new CLI command group (set/get/delete/list) for raw CRUD access to any store backend configured understores:inatmos.yaml(AWS SSM, AWS Secrets Manager, HashiCorp Vault, Azure Key Vault, GCP Secret Manager, Redis, Artifactory, 1Password, Keychain, GitHub Actions). Unlikeatmos secret, no declaration is required — any key can be read, written, or deleted directly by name, optionally scoped to a stack and component. - Adds a
type: storeworkflow step that writes a value from a workflow, custom command, or hook — usable automatically as a hook too via the existing generickind: stepbridge, with no extra wiring. - Both close the write-side gap next to the existing read-only
!store/!store.getYAML functions, so pipeline metadata (an image tag from a build step, a build number, a deployment marker) can be handed off to a completely different stack or component. - Includes unit tests for the new
pkg/store.Servicefacade, thecmd/storecommand family, and thestorestep handler, plus CLI/website docs, a changelog post, and a roadmap milestone.
why
- Store backends previously supported reads only (
!store/!store.get); the only existing write path was a single Terraform-output-specific hook (kind: store), so anything else needing to be written into a store meant scripting around Atmos with a cloud CLI. - This gives Atmos a native, declaration-free CRUD surface and a first-class workflow step for the common build → push → record-value → later-read pattern, without requiring the value to be formally declared as a secret.
references
- N/A
Summary by CodeRabbit
-
New Features
- Added experimental
atmos storecommands to set, get, list, and delete values across configured backends. - Added scoping, interactive or stdin input, deletion confirmation, raw output, multiple formats, key enumeration, and secret-value masking.
- Added
type: storeworkflow steps for writing templated values. - Added Terraform output and refresh lifecycle hooks for store integrations.
- Added experimental
-
Documentation
- Added CLI, configuration, workflow, hooks, and usage documentation with examples and backend limitations.
-
Tests
- Added comprehensive coverage for store commands, workflow behavior, and Terraform hooks.
Add tfmigrate support for Terraform components Erik Osterman (Cloud Posse) (@osterman) (#2534)
what
- Add
atmos terraform migrate plan,apply, andlistfor running user-authoredtfmigratemigrations in Terraform component context. - Add
kind: tfmigratelifecycle hooks with dynamic/static modes, toolchain resolution, Terraform/OpenTofu exec path wiring, and same-identity auth handling. - Export stack/component/workspace-scoped history variables and supported Terraform backend settings so users can configure durable
tfmigratehistory storage. - Update schemas, PRD, command docs, hook docs, roadmap, and changelog for the new migrate command family and history persistence limitation.
why
- Terraform state migrations need to run after Atmos auth, source/workdir provisioning, generated files, init, and workspace selection so automation matches normal Terraform operations.
- Rerun-safe automation depends on durable
tfmigratehistory storage, so Atmos now documents and exposes the values users need without taking on history persistence in v1.
references
Summary by CodeRabbit
- New Features
- Added experimental
atmos terraform migrate plan,apply, andlistcommands. - Added
tfmigratelifecycle hooks with dynamic and explicit execution modes. - Added migration history support for local, S3, and GCS backends.
- Added affected-component workflows and configurable migration output.
- Added experimental
- Bug Fixes
- Improved hook validation, dry-run handling, terminal color precedence, and provider resolution.
- Missing migration directories now safely produce no-op results.
- Documentation
- Added CLI guidance, migration patterns, advanced examples, and help pages.
- Tests
- Added comprehensive unit, integration, and end-to-end coverage.
feat(vendor): native component updater PR workflow Erik Osterman (Cloud Posse) (@osterman) (#2756)
validation
Manually tested end-to-end as a real user — following --help/docs, in isolated sandboxes, and against a real repository (cloudposse/infra-live):
- Opened two real pull requests exercising both the default and the full
vendor.ci.pull_requestconfig surface (title/body templates, labels, draft, reviewers, assignees) — #1701, #1702 (left as drafts, not merged). - That testing surfaced and fixed 5 real bugs along the way:
--pull-requestcreated a pull request but never printed its URL in the default table output.atmos.yaml'svendor.update.*/vendor.ci.*config (groups, execution mode, PR title/labels/draft/reviewers) was silently ignored — read from the wrongviperinstance instead of the parsed config.--alldouble-counted every component when a repo vendors exclusively viacomponent.yamland leaves an unused component type (e.g.packer) unconfigured.- A pull request's link was discarded entirely when a post-creation step failed (hit for real: GitHub rejecting a review request from the PR's own author).
- SBOM's
oci-artifactscoverage entry always claimed "complete" regardless of whether any OCI artifact existed in the project.
- Also manually verified
atmos vendor verify/clean/--refresh-lock/--lock-enforcement(all three modes) andatmos sbom generate(CycloneDX vs. SPDX, NTIA mode, experimental gating, upload-outside-CI) in isolated sandboxes. - Added
ATMOS_PRO_GITHUB_TOKEN(the tokengithub/stsmints) to the Component Updater's GitHub token precedence, soatmos auth exec --identity <github-sts-identity> -- atmos vendor update --pull-requestgets a token that triggers downstream Actions workflows on the PR it opens — unlike the defaultGITHUB_TOKEN, which GitHub excludes from re-triggering workflows.
references
Summary by CodeRabbit
- New Features
- Added experimental
atmos sbom generatewith provenance/NTIA modes, SPDX/CycloneDX output, and optional CI artifact upload. - Added native
atmos vendor update --pull-requestworkflows with component/group selection and deterministic PR publishing. - Added
atmos vendor verify,atmos vendor clean, andvendor.lock.yamldrift protection. - Added lock refresh/enforcement options, semver-range resolution, and improved source provenance metadata.
- Added experimental
- Bug Fixes
- Improved cancellation handling and transient OCI decompression recovery.
- Documentation
- Expanded SBOM, vendoring, lockfile, and component-updater guidance.
feat(workflow): support tags and labels selectors zack-is-cool (#2857)
what
- Enable
--tagsand--labelsonatmos workflow, forwarding them to nestedtype: atmossteps alongside optional--stack. - Preserve selector forwarding for parallel and matrix workflow controls.
- Document the feature, publish its changelog post, and add its workflow-roadmap milestone.
why
- Target existing workflows by component metadata without duplicating workflows or reconstructing their commands manually.
references
- Closes #2852
validation
go test ./internal/exec -run TestExecuteWorkflow_ForwardsCommandLineFilters -count=1go test ./pkg/workflow -run 'TestAppendAtmosStepFlags|TestControlCommandExecutorExecuteAtmos' -count=1go test ./cmd/workflow -run TestWorkflowSelectorFlags -count=1git diff --check
Summary by CodeRabbit
-
New Features
- Added
--tagsand--labelsselectors to workflow commands. - Selectors are forwarded to nested Atmos steps, including parallel and matrix workflows.
- Selectors can be combined with
--stackwhile preserving workflow ordering and execution behavior.
- Added
-
Documentation
- Updated CLI references with selector options and usage examples.
- Added workflow selector guidance to the blog and roadmap.
feat: add date-anchored default editions Erik Osterman (Cloud Posse) (@osterman) (#2762)
what
- Add date-anchored
edition:defaults, the--editionoverride, andatmos describe/list editioncommands. - Journal default changes and add config, CLI, docs, snapshot, and cast coverage for edition-aware behavior.
- Make describe component/dependents honor graceful YAML error handling without requiring implicit identity authentication.
why
- Let projects upgrade Atmos without silently adopting later default changes, while giving operators visibility into effective defaults.
- Avoid duplicate post-auth error output and let component inspection continue when recoverable YAML values cannot resolve.
references
- N/A
Summary by CodeRabbit
-
New Features
- Added experimental date-pinned configuration editions via
--edition,ATMOS_EDITION, oratmos.yaml. - Added
describe editionandlist editionscommands for inspecting default changes. - Added component mock support with
--use-mocks. - Added configurable component filtering, provenance display, error handling, and help filtering.
- Added experimental date-pinned configuration editions via
-
Bug Fixes
- Improved table sizing, terminal wrapping, whitespace, tree output, authentication handling, and validation exclusions.
- Preserved clearer error details and prevented unintended authentication attempts.
-
Documentation
- Added documentation and examples for editions and updated command and configuration references.
refactor(store): move backends into pkg/store/providers subpackage Erik Osterman (Cloud Posse) (@osterman) (#2575)
what
- Move the concrete store backend implementations (AWS SSM, Azure Key Vault, Google Secret Manager, Redis, Artifactory) out of
pkg/storeinto a newpkg/store/providerssubpackage, keepingpkg/storeas a pure interface/type boundary (theStoreinterfaces,StoreConfig/StoresConfig/StoreRegistrytypes, auth-config types, error sentinels, and generated mocks). - Introduce a self-registering registry:
pkg/storeownsStoreRegistry,NewStoreRegistry, and aRegister(type, factory)API; each backend registers its factory from aninit(), so the typeswitchis replaced by a map lookup.pkg/configblank-importspkg/store/providersso the built-in backends register at startup (database/sql driver pattern). - Update call sites and add a
pkg/store/providersexclusion to theprovider-agnostic-authdepguard rule so its cloud-SDK imports are permitted (matchingpkg/auth/providers).
why
- Isolates the cloud-SDK-heavy backend code from the store contract, mirroring the established
pkg/auth/providers/pkg/secrets/providerslayout and makingpkg/storea clean type/interface package. - The registry pattern removes the awkward split where the factory lived under
providersbut was named after theStoreRegistrytype it returned;pkg/storenow owns both the registry type and its construction, and adding a backend is one self-contained file that registers itself. - No user-visible change: identical store types resolve, unknown types still return
ErrStoreTypeNotFound, identity warnings are preserved. Builds clean, all affected tests pass, andgolangci-lint --new-from-rev=origin/mainreports zero issues.
references
- N/A
Summary by CodeRabbit
- New Features
- Storage backends now use a consistent registration model, improving support for configured providers and aliases.
- Bug Fixes
- Improved handling of missing configuration, invalid values, authentication failures, access errors, and unavailable data.
- Tests
- Expanded coverage for provider validation, key behavior, error scenarios, and concurrent registry operations.
- Documentation
- Clarified storage registry configuration in the schema.
- Chores
- Improved CI reproducibility and license-report generation.
feat(toolchain): add --format=plain/json to atmos toolchain get Erik Osterman (Cloud Posse) (@osterman) (#2845)
what
- Adds a
--formatflag toatmos toolchain getwith three modes:table(default, unchanged),plain(bare version string only), andjson(structured output with tool/version/installed fields, or a full version list under--all). - Routes the new
plain/jsonoutput through the data channel (stdout, pipeable) instead of the styled UI channel (stderr), via newprintVersionsPlain/printVersionsJSONhelpers inpkg/toolchain/get.go. - Rejects
--format=plaincombined with--allwith a newErrToolchainPlainFormatWithAllFlagsentinel, since there's no single version to print in that case. - Adds a changelog post and links it into the toolchain roadmap milestone.
why
- Extracting a tool's version in scripts/CI previously required regex-scraping the human-styled table output (checkmark indicator, ANSI colors,
2>&1since it's written to stderr), e.g.atmos toolchain get vale-cli/vale 2>&1 | grep -oE '[0-9]+\.[0-9]+\.[0-9]+' | head -1. --format=plaincollapses that toversion=$(atmos toolchain get vale-cli/vale --format=plain), and--format=jsongives scripts structured access to installed status without adding a new dependency.
references
Summary by CodeRabbit
- New Features
- Added table, plain, and JSON output formats to
atmos toolchain get. - Added
--format/-f, environment-variable support, and shell completion. - Plain output provides version strings; JSON provides structured tool and installation details.
- Added table, plain, and JSON output formats to
- Bug Fixes
- Added validation for unsupported formats and incompatible
--alland plain output options.
- Added validation for unsupported formats and incompatible
- Documentation
- Updated command documentation, scripting examples, and roadmap information.
feat: dependency-closure selection (--include-dependencies/--include-dependents) + scope --labels/--tags evaluation before filtering Erik Osterman (Cloud Posse) (@osterman) (#2807)
what
Dependency-closure selection (new)
- Every multi-component terraform selection (
--all,--components,--query,-s,--tags,--labels,--affected) accepts two new depth-carrying flags onplan/apply/deploy/destroy/init:--include-dependencies[=N]— also process everything the selection depends on (its prerequisites), N levels deep (bare flag = unlimited).--include-dependents[=N]— the reverse direction. Previously a bool wired only into--affected; now works with every selection and accepts a depth (true/falseremain accepted for compatibility).
- Selectors choose the seed; closure flags expand it: closure members execute even when they don't match the selectors that seeded them, in dependency order (reversed for destroy), with cross-stack edges followed.
destroy --include-dependencieswarns that it also destroys shared prerequisites. - The same flags on
atmos list components/stacks/instancespreview the exact execution set a bulk run would touch. - Depth support in the graph engine:
pkg/dependency.Filtergains per-direction depth bounds with a best-depth BFS; the scheduler adapter is restructured to seed-then-expand (tags/labels/query moved from post-filter to seed narrowing, per-node query skip suppressed when the seed already applied it). - The three-phase scoped evaluation built for
list dependenciesis generalized intodependencies.ResolveScopedClosureand shared with the terraform bulk paths, so closure runs fully evaluate only the stacks the closure touches.
Selector purity (new, by design)
metadata.tags/metadata.labelsare selectors evaluated before auth/templating/YAML functions, so their values must be resolvable without authentication or process execution. Values using!terraform.state,!terraform.output,!store,!store.get,!secret,!aws.*,!emulator,!exec,!random, or template calls toatmos.Component/atmos.Store/atmos.GomplateDatasource/datasources are rejected with a by-design error and migration hint on any command that processes the manifest. Plain strings, simple templates, and local functions (!env,!git.*,!include) remain allowed.- Validation parses template actions with the configured delimiters (
templates.settings.delimiters) via Go's template parser — no regex heuristics — andtags.SelectorUnresolvedis now delimiter-aware too.
Scope-before-evaluate for --labels/--tags (original scope)
- Early-skip scope check in the shared describe-stacks processor: a component excluded by
--tags/--labelsskips auth/template/YAML-function evaluation entirely, generalizing the existing-s/--stackearly-skip. Now also threaded intolist stacks/components/instances(previously they row-filtered after full evaluation). atmos list dependenciescomputes a lightweight dependency graph, derives the reachable closure, and only fully evaluates the stacks that closure touches — re-converging against the resolved graph so templated same-stack dependency targets still resolve.describe.settings.eager_evaluationremains the rollback switch forcing the old full-evaluation behavior.
Tests and docs
- Unit tests across
pkg/tags(purity + delimiter cases),pkg/dependency(depth/cycles/diamonds),pkg/scheduler/adapters(closure retains non-matching prereqs, destroy ordering, query suppression, depth merging),pkg/list/dependencies(scoped-evaluation convergence with a poisoned unrelated stack), and the list/cmd layers; regenerated help-text golden snapshots. - Docusaurus docs for all new flags (terraform + list commands), a Selector Purity section in the stack metadata docs, a changelog blog post (
website/blog/2026-07-27-include-dependencies-closure.mdx), and a roadmap entry.
why
- On a monorepo spanning multiple AWS accounts,
atmos terraform ... --all --labels=.../--tags=...andatmos list dependencies --stack <stack>fully evaluated every stack in the repo (templates, YAML functions, auth/backend) before consulting the selector, so an unrelated account's unreachable backend failed the command. - Deploying "a stack and everything it needs" (or tearing down "a component and everything that depends on it") required hand-maintained bash wrappers around Atmos, even though the dependency graph and topological scheduler already existed — the selection just never followed the edges.
- Making tags/labels drive scoping decisions before evaluation requires them to be cheaply resolvable; the purity contract makes that explicit design rather than a silent perf cliff.
references
- Builds on
docs/fixes/2026-06-22-describe-stacks-scope-and-cache-per-component-auth.md. - See
docs/fixes/2026-07-25-scope-before-evaluate-labels-tags-list-dependencies.mdfor root-cause and verification detail of the scope-before-evaluate work (its Recommendations section is implemented by this PR's closure flags).
Summary by CodeRabbit
Release Notes
-
New Features
- Added dependency and dependent expansion with optional depth limits and cross-stack relationships.
- Added dependency-closure previews for component, stack, and instance selections.
- Added tag and label filtering across list commands, including vendor tags.
- Added
levelsoutput for shortest dependency distances. - Label filters now support
key=valueandkey:valueformats.
-
Improvements
- Scoped evaluation skips unrelated components for filtered selections.
- Added eager-evaluation controls for bulk selections.
- Added clearer validation and warnings for unsupported flag combinations and dependency expansion during destroy operations.
- Expanded CLI documentation and help text for new filtering and dependency options.
test(config): regression test for atmos.d commands merge (#2570) Erik Osterman (Cloud Posse) (@osterman) (#2840)
what
- Add
TestMergeConfig_AtmosDCommandsMerging_TopLevelYamlFunctiontopkg/config/config_import_test.go, reproducing the exact minimal repro from #2570: two.atmos.d/files where the last-processed file has a top-level Atmos YAML function (!repo-root) directly on a command's own field.
why
- Confirms the reported "commands silently dropped" bug is already fixed incidentally by commit
0b1182c6bb(PR #2677, released inv1.223.0), which strips thecommandskey from file content beforepreprocessAtmosYamlFuncruns so it can no longer overwrite the already-merged commands array. - Adds a permanent regression guard pinning this exact shape so it can't silently reappear.
- No production code changes — test-only.
references
- Closes #2570
fix(test): stop Windows toolchain-vanishing flake in acceptance tests Erik Osterman (Cloud Posse) (@osterman) (#2834)
what
- Stops
TestPrintTelemetryDisclosureOnlyOnce(and its three sibling telemetry disclosure
tests) from deleting the shared<cache>/atmosroot; they now isolate via a per-test
ATMOS_XDG_CACHE_HOME/XDG_CACHE_HOMEredirect instead. - Closes a TOCTOU race in
internal/execterraform/tofu version tests:RequireTerraformPath/
RequireTofuPathnow resolve the binary path once and hand it back directly, instead of a
second independentexec.LookPathcall at each call site. - Makes
requireExecutablePathfail loudly (t.Fatalf) instead of silently returning an empty
path whenATMOS_TEST_SKIP_PRECONDITION_CHECKS=trueand the binary isn't found. - Isolates several
pkg/toolchaininstall/uninstall/list tests from the real shared XDG
toolchain cache directory (Toolchain.InstallPathnow points at each test's ownt.TempDir()). - Widens
requireExecutablePath's retry window (2s → 15s) as a defensive backstop, and adds
permanent forensic instrumentation (executableLookupForensics) that dumps per-PATH-entry
toolchain directory state on a lookup failure. - Adds a
docs/fixes/entry recording the root cause and investigation.
why
- The Windows "Acceptance Tests" CI job had been flaky for weeks with a recurring
executable file not found in %PATH%failure fortofu/terraform, always on a different
victim test ininternal/execeach run. - Root cause (confirmed via the forensic instrumentation added here):
TestPrintTelemetryDisclosureOnlyOnce
was callingos.RemoveAllon the shared<cache>/atmosroot — the same root the toolchain
install path lives under since #2579 — twice per run (setup + defer), deleting every
CI-provisioned tool (terraform, opentofu, helm, helmfile) out from under the other
concurrently-runninggo testpackage binaries. - The other fixes in this PR are the real-but-secondary hazards found and closed during the same
investigation, kept together because each one was ruled in/out as a contributing cause before
the actual root cause was found. - Extracted out of #2812 (which bundles an unrelated CI git-clone-bootstrap feature) so this
CI-reliability fix can ship and be reviewed independently.
references
- Extracted from #2812
ci: run required checks for GitHub merge queue Erik Osterman (Cloud Posse) (@osterman) (#2813)
what
- Run the existing required test, CodeQL, CODEOWNERS, and symlink workflows for merge-group commits.
- Run golangci lint for merge groups and keep CODEOWNERS validation safe when no pull-request payload is present.
why
- GitHub merge queues require required checks to report on their synthetic merge commits before a queued pull request can merge.
references
Summary by CodeRabbit
- Chores
- Added merge queue support to automated security, testing, linting, CODEOWNERS, and symlink verification checks.
- Ensured required validations run consistently for merge queue commits, including appropriate handling of CODEOWNERS checks.
Support parent-scoped multi-file stacks Erik Osterman (Cloud Posse) (@osterman) (#2787)
what
- Support one logical stack across multiple top-level parent manifests without merging parent scopes.
- Keep each parent's
metadata.inheritsgraph self-contained while canonicalizing equivalent imported duplicates by lexical parent path. - Add the top-level stack composition PRD and regression fixtures for naming, conflicts, isolation, explicit imports, and invalid inheritance.
why
- Multiple top-level files that represented layers of the same logical stack were previously rejected or treated independently.
- This enables aggregate component discovery without implicitly making another parent's imports part of a manifest's inheritance dependencies.
- Shared inheritance bases remain intentional and reviewable through each parent's normal import graph.
references
docs/prd/top-level-stack-composition.md
Summary by CodeRabbit
-
New Features
- Added parent-scoped multi-file stack discovery: multiple top-level parent manifests with the same stack identity are composed into one logical stack.
- Kept parent-specific imports/globals/locals/component config isolated while aggregating components across parents.
- Enabled controlled component inheritance within the composed logical stack.
-
Bug Fixes
- Duplicate component configurations now resolve deterministically.
- Conflicting duplicates and unsupported peer-only inheritance are rejected with clearer diagnostics.
-
Documentation
- Added a PRD and published a blog post on parent-scoped multi-file stacks.
- Updated the public roadmap to mark the feature as shipped.
feat(stacks): support global-scope metadata defaults Erik Osterman (Cloud Posse) (@osterman) (#2808)
what
- Adds support for a restricted allowlist of
metadatafields (labels,tags,custom,enabled,locked,terraform_workspace_pattern) at the stack-manifest root, deep-merged into every component's ownmetadataas a stack-wide default. - Merge precedence, lowest to highest: global (stack-wide) → the
metadata.inheritsbase-component chain → the component's own localmetadata:block, which always wins. - Component-identity fields (
component,inherits,type,name,terraform_workspace) remain component-only; setting one of these at global scope is now a hard validation error, both at runtime and via the manifest JSON Schema, instead of a silent no-op. - Updates
metadata.mdxdocs and adds a changelog post explaining the new global scope.
why
- A stack-wide
metadata:block (e.g. in_defaults.yaml) was previously accepted by the schema but never applied —metadata.labels/metadata.tags/etc. set there silently did nothing, which is worse than an error, since users had no signal their config wasn't taking effect. - Sharing labels, tags, or a stack-wide lock/enable flag across every component in a stack required copy-pasting the same
metadatablock into each component definition instead of declaring it once.
references
Summary by CodeRabbit
-
New Features
- Added stack-root
metadata:defaults that are deep-merged into each component’smetadata. - Enforced 3-tier precedence: stack defaults →
metadata.inheritschain (when enabled) → component-local metadata.
- Added stack-root
-
Validation & Tests
- Added schema allowlisting for stack-scope
metadata(onlylabels,tags,custom,enabled,locked,terraform_workspace_pattern). - Expanded tests to cover precedence, custom component behavior, and new error paths for invalid global/identity fields.
- Added schema allowlisting for stack-scope
-
Documentation
- Updated component metadata docs and added a blog post explaining scope, allowed keys, and merge behavior.
Clarify Atmos CI concurrency guidance Erik Osterman (Cloud Posse) (@osterman) (#2798)
what
- Add a concise warning about using GitHub Actions concurrency around Atmos/Terraform commands.
- Fix Markdown indentation in the modernization skill so affected validation passes.
why
- Concurrency groups are not a FIFO deployment queue, and cancellation can interrupt Terraform work.
references
Summary by CodeRabbit
- Documentation
- Clarified GitHub Actions
concurrencybehavior for Atmos/Terraform runs, including in-progress vs pending handling and pending-run eviction. - Added warnings that
cancel-in-progress: truecan cancel an in-flight Terraform apply and potentially leave remote state locks requiring manual recovery. - Documented
queue: maxlimits (up to 100 pending) and noted it can’t be combined withcancel-in-progress. - Recommended explicit promotion/deployment workflows for strict execution ordering and updated the modernization checklist wording/formatting.
- Clarified GitHub Actions
🚀 Enhancements
fix(output): render concurrent carriage-return updates safely zack-is-cool (#2860)
what
- Keep concurrent component output readable when underlying tools emit carriage-return progress updates.
- Serialize prefixed stdout and stderr writes to their shared terminal.
- Disable animated output-lookup spinners while concurrent Terraform work is running.
- Add regression coverage for carriage-return handling and nested spinner suppression.
why
- Concurrent writers and terminal redraw controls can otherwise reposition the cursor or interleave output, corrupting rendered lines.
references
- Closes #2859
validation
go test ./pkg/scheduler/adapters ./pkg/io -count=1go test ./pkg/terraform/output -run '^TestSuppressSpinnersRestoresNestedScopes$' -count=1pre-commit run --files pkg/scheduler/adapters/terraform.go pkg/terraform/output/executor_utils.go pkg/terraform/output/spinner.go pkg/terraform/output/spinner_test.go
Summary by CodeRabbit
-
Bug Fixes
- Improved line-prefixed output for Unix, Windows, and standalone carriage-return line endings.
- Prevented partial lines from being lost during flushing or after write errors.
- Prevented interleaving of Terraform standard output and error output during concurrent execution.
-
Improvements
- Suppressed transient spinners and provisioning messages during streamed or concurrent output.
- Improved spinner cleanup across successful runs, errors, and nested operations.
- Standardized carriage-return output as newline-delimited, prefixed lines.
- Routed hook output consistently through the appropriate component streams.
- Improved synchronization for grouped and concurrent Terraform output.
fix(config): resolve git-root base_path for --config/--config-path Erik Osterman (Cloud Posse) (@osterman) (#2864)
what
- Fixes
pkg/config/load_config_args.goso that loading configuration via--config/--config-pathalso applies git-root discovery for an empty (or.)base_path, matching the plain auto-discovery flow inLoadConfig(). - Adds a regression test (
TestLoadConfigFromCLIArgs_AppliesGitRootBasePath) that reproduces the bug and verifiesbase_pathnow resolves correctly. - Bumps the
fast-uritransitive dependency (website) from 3.1.4 to 3.1.5 to remediate a high-severity host-confusion vulnerability (GHSA-7p8r-x3mc-p8w7 / CVE-2026-18446), flagged by Dependabot after this branch was pushed.
why
loadConfigFromCLIArgs()never calledapplyGitRootBasePath(), unlike the mainLoadConfig()auto-discovery path. As a result, a project withbase_path: ''inatmos.yamlresolved correctly via plain auto-discovery but leftbase_pathempty when the identical config was loaded via--config, breaking component/stack path resolution (e.g.atmos terraform test) withError: failed to find import.- The
fast-uribump addresses a live Dependabot alert (a patch-level version bump, not blocked by.github/dependabot.yml's major-version ignore policy) surfaced automatically after pushing this branch.
references
- closes #2863
Summary by CodeRabbit
-
Bug Fixes
- Configuration loaded through command-line options now resolves an empty or
"."base path to the Git repository root. - Configuration loading continues when repository root discovery encounters an error.
- Improved ZIP extraction safety by blocking path traversal and preventing writes outside the intended destination.
- Improved error reporting for ZIP directory and file creation failures.
- Configuration loaded through command-line options now resolves an empty or
-
Tests
- Added coverage for configuration base-path resolution and ZIP extraction security and failure handling.
fix(auth): prevent Azure CLI cache corruption for guest users Andriy Knysh (@aknysh) (#2861)
what
- Skip the Azure CLI cache write-back entirely when credentials originated from the
azure/cliprovider — az's own cache is authoritative, and writing back what came from az is what corrupted it. - Record the originating auth method on
AzureCredentials(cli/device_code/oidc) so the write-back can be gated per provider kind. - Capture MSAL's real home account ID in the
azure/device-codeprovider (silent and interactive flows) and use it in both Azure CLI cache writers (UpdateAzureCLIFilesand the provider-levelupdateAzureCLICache), falling back to the previous{oid}.{tenant}derivation when unavailable. - Replace the
azure/subscriptionidentity's field-by-field credential copy with a struct copy plus explicit overrides, and add a reflection-based regression test that fails if any futureAzureCredentialsfield is dropped by the wrap. - Isolate
TestNewMSALCache's default-path case from the real~/.azure.
why
- After
atmos auth login, the Azure CLI cache write-back created an MSAL Account entry withhome_account_idderived as{oid}.{target-tenant}and hardcodedaccount_source: "device_code". For guest (B2B) users the home tenant differs from the target tenant, so az ended up with two Account entries for the same username and failed every subsequent command withFound multiple accounts with the same username(azure-cli#20168) — includingaz account get-access-token, which theazure/cliprovider itself shells out to. In other words, oneatmos auth loginbroke both az and the next atmos login for any guest user. - Reproduced and verified end to end against a real tenant where the operator is a B2B guest: before the fix,
az login→atmos auth login→ az broken; after the fix, az stays healthy, the cache keeps exactly one Account entry, and the persisted credentials carryauth_methodso the gate holds across credential caching. - The subscription identity's field-by-field copy silently dropped the new fields before they reached the cache writer (found only by the end-to-end test), which is why the copy is now structural and guarded by a reflection test.
references
- Azure/azure-cli#20168 (the az failure mode this triggered)
Summary by CodeRabbit
New Features
- Azure credentials now retain authentication method and account identity details.
- Improved support for guest and cross-tenant Azure accounts during authentication and token caching.
- Subscription-based authentication preserves provider credential settings while applying subscription-specific values.
Bug Fixes
- Azure CLI authentication no longer unexpectedly modifies CLI credential cache files.
- Corrected account identification and tenant details for guest-user authentication.
- Improved cache path handling across different environments.
fix(scaffold): resolve relative write-target directories consistently Erik Osterman (Cloud Posse) (@osterman) (#2855)
what
- Fixes
atmos scaffold generateso a relative target directory (e.g. the CLI's own default./my-project) works, instead of rejecting every file withpath traversal not allowed. validateWriteTargetinpkg/generator/engine/templating.gonow resolves the write directory (realDir) through the sameResolveAndCleanBasePathhelper already used for the target base (realBase), instead of a barefilepath.EvalSymlinksthat stays relative for relative inputs.- Adds a regression test,
TestProcessFile_RelativeTargetPath, covering a relativetargetPathend-to-end (previous tests only exercised absolutet.TempDir()targets, so this case was never caught).
why
realBasewas always absolutized before comparison, butrealDirwas resolved with a barefilepath.EvalSymlinks, which returns a relative path unchanged when given a relative input. Comparing an absolute path against a relative one never matched the containment check, so it fired as a false-positive path traversal on every write whenever the target directory was relative — including the command's own default target.- Absolute targets happened to work only because
filepath.Dir(fullPath)was already absolute in that case, masking the bug.
references
- Closes #2851
Summary by CodeRabbit
-
Bug Fixes
- Fixed file generation for relative target paths, such as
./my-project. - Improved path resolution while preserving containment and symlink safety checks.
- Fixed file generation for relative target paths, such as
-
Tests
- Added coverage confirming generated files are written to the expected destination with the correct content.
fix(config): stop recommending deprecated stacks.name_pattern Erik Osterman (Cloud Posse) (@osterman) (#2842)
what
atmos aws eks update-kubeconfigand Spacelift stack-name generation now checkstacks.name_templatebefore falling back to the deprecatedstacks.name_pattern, instead of only supporting the deprecated field.- Error messages in
pkg/config,errors/errors.go, andpkg/helmfile/cluster.gothat previously only pointed users at the deprecated fields now recommendname_template/cluster_name_template. - The Getting Started tutorial and the
aws eks update-kubeconfigcommand help/docs no longer teach the deprecatedname_pattern/cluster_name_patternfields. - Converted all
examples/,demo/, and non-backward-compat-testtests/fixturesscenarios fromname_patterntoname_template(a handful of fixtures that specifically test the deprecated field, precedence, or backward compatibility were left untouched on purpose). - Added unit tests covering both the new
name_templatesupport and continuedname_patternbackward compatibility for the two code paths that changed.
why
- Investigating #2827 ("profile-merged
stackssettings are not consistently applied") showed the profile-merge pipeline was already correct and consistent betweendescribe configandlist dependencies. - The actual bug was that
stacks.name_pattern— deprecated in favor ofstacks.name_template— was still treated as the primary/only stack-naming mechanism in a couple of code paths and in several error messages, which is what produced the reported inconsistency and general user confusion about which field to use. name_patterncontinues to work unchanged for backward compatibility; only the recommended path and documentation change.
references
- Investigates #2827
Summary by CodeRabbit
- New Features
- Added Go-template support for stack, Spacelift stack, and EKS cluster naming using context variables.
- Compatibility
- Existing pattern-based naming remains supported as a deprecated fallback.
- Template settings take precedence when both options are configured.
- Documentation
- Updated configuration guidance, examples, and error messages to promote template-based naming.
- Tests
- Added coverage for naming precedence, fallback behavior, template errors, duplicate names, and profile-based stack discovery.
fix(toolchain): accept semver release-candidate strings in --use-version Erik Osterman (Cloud Posse) (@osterman) (#2841)
what
- Fix
isValidSemver()inpkg/toolchain/version_spec.goso it accepts semver pre-release and
build-metadata suffixes (e.g.1.225.0-rc.3,1.2.3+build.5), by delegating to the
already-vendoredMasterminds/semver/v3library instead of a hand-rolled digit-only check. - Add regression tests for the fix in
pkg/toolchain/version_spec_test.go(unit-level
isValidSemver/ParseVersionSpeccases) andpkg/version/reexec_test.go(end-to-end at the
--use-versionentry point). - Add a fix record at
docs/fixes/2026-07-31-use-version-release-candidate-semver.md.
why
atmos --use-version=1.225.0-rc.3failed withinvalid version output format, even though
1.225.0-rc.3is a spec-compliant semver string. The version parser split on.and required
every part to be pure digits, which rejects any release-candidate/pre-release version.- Tracing the install pipeline confirmed the parser was the only place the bug lived — the rest
of the install path already handles arbitrary explicit versions (including prereleases)
correctly, so no other changes were required.
references
- closes #2839
Summary by CodeRabbit
-
New Features
--use-versionnow accepts release-candidate versions such as1.225.0-rc.3, including versions with build metadata.- Version specifications support standard semantic version formats while retaining support for
latest.
-
Bug Fixes
- Improved version validation to reject malformed or overly specific versions and correctly handle pre-release identifiers.
-
Documentation
- Added guidance covering supported semantic version formats and release-candidate usage.
fix(dag): stop concurrent map crash in bulk terraform commands Erik Osterman (Cloud Posse) (@osterman) (#2831)
what
- Fix a
fatal error: concurrent map iteration and map writecrash in DAG-scheduled bulk terraform commands (terraform <cmd> --all/--affected/--query) at higher--max-concurrency. ProcessComponentConfignow shallow-clones the component section before any downstream code mutates it, so concurrent workers never write into the map tree owned by the sharedFindStacksMapcache.- Apply the same shallow-clone-before-mutate fix to two adjacent cache-corruption sites in the describe-stacks processor (deleting
imports, andterraform_workspace_pattern/terraform_workspace_template, from cache-owned maps in place). - Add regression tests (
internal/exec/process_stacks_shared_cache_test.go) that fail pre-fix both deterministically and under-race. - Bump the
brace-expansionpnpm.overrides(website) to1.1.18/2.1.4, patchingCVE-2026-14257/GHSA-mh99-v99m-4gvg(high-severity DoS via unbounded expansion length), reported by Dependabot alert #261.
why
FindStacksMapcaches processed stack config and returns it by reference on cache hits, shared across all goroutines within a process.ProcessStacksandmergeGlobalAuthConfigwrite top-level keys into that shared component section, whilefindComponentInStackshas every DAG worker iterate every stack's component section (not just its own) looking for a match — so one worker's write races with another worker's read/iteration of the same cached map, crashing exactly as reported.- The describe-stacks processor had the identical hazard in two more places (both mutate cache-owned maps in place), corrupting the cache for every subsequent
ProcessStackscall in the same process even outside the crash path. - The
brace-expansionbump addresses an open, high-severity Dependabot alert; deferring it risks a DoS crash if attacker-influenced input reaches an affected glob/brace-pattern code path in the docs site tooling.
references
- Dependabot alert: https://github.com/cloudposse/atmos/security/dependabot/261
Summary by CodeRabbit
-
Bug Fixes
- Prevented concurrency-related crashes when processing multiple stacks or components in parallel.
- Preserved cached configuration data during stack and component processing.
- Improved template handling for computed Terraform and Atmos sections.
- Ensured generated Spacelift and Atlantis names are available during template evaluation.
- Corrected
describeoutput to include referenced imports consistently. - Improved propagation of configuration and template-processing errors.
-
Documentation
- Clarified dependency advisory exceptions and their removal criteria.
- Documented the concurrency crash fix and validation coverage.
Support explicit CI git checkout and bundle Docker CLI Erik Osterman (Cloud Posse) (@osterman) (#2812)
what
- Add clone-local
--ciandATMOS_CIcontrols for no-argument CI checkout, including explicit opt-out behavior. - Replace hand-rolled argv/flag parsing in
cmd/root.gofor CI git-clone bootstrap detection with Cobra-identity + the existingcmd/gitflag-handler infrastructure (resolveCICloneMode), removing ~140 lines of bespoke parsing. - Fix
atmos git clone's no-arg CI checkout to resolve the branch from the CI provider's parsed short name instead of the raw ref, which previously failed real branch/PR checkouts. - Fix a Windows CI acceptance-test flake: a telemetry test was deleting the shared Atmos cache root mid-suite, which since #2579 also holds the toolchain install tree.
- Add Docker CLI support to the official Atmos image and update CI, command, and modernization guidance.
- Cover selector precedence, bootstrap gating, and invalid environment input.
why
- Enables checkout bootstrap before repository configuration is available while preserving explicit control.
- Keeps CI bootstrap detection consistent with the repo's flag-handler architecture instead of a parallel hand-rolled path.
- The raw-ref checkout bug meant the documented CI checkout replacement for
actions/checkout(docs/prd/git-ops.md) never actually worked for a real branch or PR. - The shared-cache-root deletion was causing multi-week Windows CI flakiness unrelated to this PR's own diff, blocking merge.
- Removes the need for Docker installation steps in Atmos container jobs.
references
- N/A
Summary by CodeRabbit
-
New Features
- Added
--cisupport foratmos git clone, configurable withATMOS_CI. - No-argument cloning can now automatically use the current CI checkout.
- Explicit CLI settings take precedence over environment configuration.
- Added
-
Bug Fixes
- Fixed CI checkout failures caused by using full ref paths instead of branch names.
- Improved handling of invalid CI configuration values.
-
Documentation
- Updated Git clone, GitHub Actions, and CI setup guidance.
- Added notes covering Docker support and CI bootstrap behavior.
fix(config): stop silently dropping malformed atmos.d/.atmos.d files Erik Osterman (Cloud Posse) (@osterman) (#2837)
what
atmos.d/and.atmos.d/config files that fail to parse now hard-failLoadConfigwith the offending file path and YAML line number, instead of being silently swallowed at debug log level.- Applies uniformly to both the normal path (an
atmos.yamlwas found, its co-locatedatmos.d/.atmos.dis checked) and the zero-config fallback path (noatmos.yamlanywhere, opportunistic git-root.atmos.dcheck). atmos version/--version,--help,atmos config validate/atmos validate config/atmos validate schema config, and CI git-clone bootstrap are unaffected — those commands already continue past config-init errors by design.- Added unit tests covering malformed YAML in
atmos.d/and.atmos.d/, the exact sort-order repro from the issue (good/broken/good files), and fullLoadConfigintegration tests for both call paths. - Added
docs/fixes/2026-07-30-atmos-d-malformed-yaml-silent-drop.mddocumenting the fix.
why
- Before this change, a YAML syntax error in
atmos.d//.atmos.d/caused Atmos to exit0with no visible error, and because the merge loop bails on the first bad file, every file sorting after the broken one silently never loaded either — whether a setting applied depended on its filename's sort position relative to an unrelated broken file. - This is inconsistent with every sibling config source: a malformed root
atmos.yamland malformed profile configs (#2825) already hard-fail. This closes the one remaining silent source, reusing the exact error already produced by the existing merge code (only the swallow points needed to change).
references
- Closes #2836
Speed up container discovery and harden emulator listings Erik Osterman (Cloud Posse) (@osterman) (#2828)
what
- Cache automatic Docker/Podman selection, eliminate duplicate probes, and show progress during uncached discovery.
- Fetch container statuses in one bulk runtime query and recover from stale cached runtimes.
- Return an empty emulator status list when invoked outside an Atmos stack project.
why
- Repeated container commands avoid unnecessary runtime checks while still recovering when a runtime changes.
- List commands now respond predictably when no stack manifests are present.
references
- None.
Summary by CodeRabbit
-
New Features
- Added support for deleting cached entries to keep container/runtime selection up to date.
- Container listings now compute instance status using a single bulk query for improved responsiveness.
-
Bug Fixes
- Container runtime auto-selection is more resilient: corrupted cache data triggers fresh discovery, and failed runtime operations invalidate cached selections.
- Emulator listing now returns an empty result (no error) when no stacks/manifests are found.
- Improved runtime environment propagation when supported, and more reliable auto-start recovery between Docker and Podman.
fix: don't process Go templates in Terraform source code thejrose1984 (#2830)
what
- Exclude the
component_infosection fromGotemplate rendering in both the
ProcessStacksanddescribe stackspipelines component_infostays in the template context, so{{ .component_info.component_path }}
and friends keep working in stack manifests- Add a regression test plus a
terraform-source-go-templatesfixture whose Terraform
descriptioncontains{{project}} - Document the exclusion on the Templates page
why
- Atmos parses a component's Terraform/OpenTofu source with
terraform-config-inspectand
stores the result — including every variable and outputdescription— in
component_info.terraform_config. That section is part of the component section Atmos
serializes and renders as aGotemplate. - A
descriptionthat legitimately contains double curly braces, such as the GCP resource
name formatprojects/{{project}}/locations/{{location}}/services/{{name}}, aborted every
Atmos command withtemplate: templates-all-atmos-sections:156: function "project" not defined. - Terraform code is not Atmos configuration. Atmos templating belongs to the abstraction
above Terraform modules, so this needs no new config knob — the section is simply never
rendered.
references
- closes #2145
Summary by CodeRabbit
-
Bug Fixes
- Terraform-derived
component_infocontent is no longer incorrectly processed as Go templates. - Terraform strings containing
{{...}}and}}are preserved while other stack templates continue to render normally.
- Terraform-derived
-
Documentation
- Clarified that
component_infois protected from template rendering but remains available for use in other templates.
- Clarified that
fix(auth): never cache ambient provider credentials in the keyring thejrose1984 (#2819)
what
Stops the auth manager persisting credentials to the keyring for providers
that re-resolve their principal from the environment on every authentication
(gcp/adc, gcp/workload-identity-federation, azure/cli, azure/oidc, github/oidc).
Such chains are never served from, nor written to, the keyring, and stale
entries are purged so poisoned keyrings self-heal.
why
gcp/adc is documented as stateless, but its short-lived token was cached and
replayed, so after gcloud auth application-default login switched accounts
Atmos kept authenticating as the previous principal. atmos/pro is deliberately
excluded: it mints a single-use token, so cached reuse is load-bearing.
references
Closes #2695
Suggested release label: patch
Summary by CodeRabbit
- New Features
- Ambient authentication providers now explicitly declare non-persisted credentials for fresh resolution from current environment on each login.
- Bug Fixes
- Prevented stale ambient credentials from being replayed from cache or written to the keychain.
auth logout,auth logout provider, andauth logout --allnow clear ambient-related keyring entries even without--keychain.- Improved ambient-aware
--dry-runlogout previews to match real cleanup behavior.
- Documentation
- Updated ambient provider docs describing token/credential rotation and non-caching behavior.
- Tests
- Added expanded ambient-aware coverage across caching and identity/provider/logout-all flows.
fix: close yq/merge concurrency races, route yq logs through Atmos logger Erik Osterman (Cloud Posse) (@osterman) (#2826)
what
- Route yq's internal (
go-logging) diagnostics through the Atmos logger via a newinternal/yqpackage, so they inherit Atmos's formatting, configured log destination, and secret masking instead of writing straight to stderr, unformatted and unmasked. - Centralize yq's process-global logger backend and expression-parser init in that same package, shared by both
pkg/utilsandpkg/yaml, closing a cross-package data race left over after #2822. - Fix an unrelated data race in
pkg/merge.MergeContext.WithFile, where concurrent per-stack-file goroutines sharing a parent import chain could write into the same backing-array slot at the same time. - Bump the website's
brace-expansiondependency (viapnpm.overrides) to patched versions, closing Dependabot alert #261.
why
- yq processes YAML that can carry secrets, so letting its diagnostics bypass Atmos's masking-aware I/O layer was a real leak risk whenever Trace-level logging is enabled.
go-logging'sSetBackendwraps any plainBackendin an unsynchronized, map-based type unless the backend itself implementsLeveled. #2822's mutex-based fix for #2821 only coveredpkg/utils, leavingpkg/yaml/edit.gofree to mutate the same global state independently — confirmed withgo test -race.- The
MergeContext.WithFilerace surfaced incidentally while validating the yq fix under-race(TestExecuteHelmfile_ComponentNotFound), and turned out to be a genuine, separate concurrency bug worth fixing here rather than leaving in place. brace-expansion'sexpand()bounded the number of results but not their length, letting a small attacker-controlled input crash the Node process with an uncatchable out-of-memory error (CVE-2026-14257). The fix stays within the pinned major lines, so it isn't blocked bydependabot.yml's major-version-bump policy.
references
- #2821 / #2822 (the yq concurrency fix this PR builds on and closes the remaining gap in)
- https://github.com/cloudposse/atmos/security/dependabot/261
Summary by CodeRabbit
- Bug Fixes
- Improved concurrency safety when creating child merge contexts, preventing sibling interference.
- Centralized yq diagnostics/logging so it routes to the configured destination, masks secrets, and remains properly silenced during YAML edits.
- Improved yq evaluation concurrency and isolation with evaluation-scoped logging controls and one-time parser initialization.
- Updated Podman lifecycle integration tests to skip when runtime start fails.
- Tests
- Added race-focused regression tests for merge-context siblings and yq concurrent evaluation/logging behavior.
- Expanded unit tests for yq backend routing, masking, parser initialization, and evaluation scoping.
- Documentation
- Added fix notes for merge-context, yq diagnostics, and Podman test skipping.
- Reduced CI link-check flakiness by excluding specific flaky GitHub blob URLs.
fix(config): validate invalid configuration Erik Osterman (Cloud Posse) (@osterman) (#2825)
what
- Make built-in configuration validation run after configuration decoding fails, and include the affected file in parser errors.
- Validate every YAML file discovered through recursive profile configuration discovery, including nested profile files.
- Add coverage for command selection, fallback logging, schema validation, and profile discovery.
why
atmos config validatemust diagnose invalid Atmos configuration instead of being blocked by the same decoding failure it is intended to report.
references
- Zack profile/workdir reproduction.
Summary by CodeRabbit
- Bug Fixes
- Made built-in configuration validation commands run even when main configuration loading fails.
- Excluded generated/irrelevant discovery fragments to prevent incorrect config merging.
- Improved schema validation output by including the affected file path in errors.
- Adjusted schema checks for the embedded built-in config schema so unnecessary checks are skipped.
- Tests
- Added/extended coverage for built-in config/schema matching, validation behavior, and profile stack override scenarios.
Add env-step export controls to workflows and hooks Erik Osterman (Cloud Posse) (@osterman) (#2814)
what
- Add default-on process export control for
type: envsteps while preserving template assignment. - Propagate exported values through workflows, custom-command steps, and ordered step hooks.
- Add behavior-focused regression coverage, documentation, and a fix record.
why
- Separate template state from child-process state so template-only values are explicit and subprocess propagation is consistent.
references
- Closes #2810
Summary by CodeRabbit
- New Features
- Added an
exportoption for workflow and taskenvsteps to control whether values propagate to later child-process environments (default:true).
- Added an
- Bug Fixes
- Improved
envpropagation and precedence so later steps can reliably resolve{{ .env.NAME }}and so exports are isolated across retries and ordered hooks. - Ensured
export: falsekeeps values template-only (not set for later subprocess environments).
- Improved
- Documentation
- Updated workflow/task and hook docs, plus schemas, to clarify scoping and precedence for
envstep exports.
- Updated workflow/task and hook docs, plus schemas, to clarify scoping and precedence for
- Tests
- Added unit and end-to-end coverage for propagation, template-only behavior, and hook/step isolation.
fix(container): apply Buildx driver and cache configuration Erik Osterman (Cloud Posse) (@osterman) (#2815)
what
- Propagate resolved Buildx driver and cache settings from workflow container builds to Docker.
- Support cache settings for Buildx Bake, reject ineffective non-Buildx cache/driver configuration, and stabilize builder option ordering.
- Add workflow, component, command-argv, registry-cache integration, and CI coverage.
why
- Prevent native workflow builds from silently using Docker's default builder and bypassing remote cache settings.
references
Summary by CodeRabbit
-
New Features
- Added full propagation for Buildx driver and registry-backed build cache, including bake cache
cache-from/cache-to.
- Added full propagation for Buildx driver and registry-backed build cache, including bake cache
-
Bug Fixes
- Strengthened build configuration validation: driver/cache settings now require a
buildx-compatible engine (unless using bake). - Improved Buildx builder creation behavior for consistent driver option ordering.
- Strengthened build configuration validation: driver/cache settings now require a
-
Tests / CI
- Added remote registry cache integration coverage (gated) and expanded local/unit test coverage for Buildx args and cache wiring.
- Enhanced fake Docker runtime verification and improved a few flaky test synchronizations.
fix(utils): initialize yq globals once instead of per evaluation Michael Pursifull (@arcaven) (#2822)
Concurrent stack file processing can end an Atmos run with `fatal error: concurrent map writes`. That is a runtime fatal error rather than a panic, so the global handler added in #2334 cannot intercept it and no retry inside Atmos can either. `go test -race` flags the cause on `main` from a 20 line test, and the fix keeps the hot path read-only.what
configureYqLoggerno longer callslogging.SetLevelon everyEvaluateYqExpressionandEvaluateYqExpressionWithTypecall. The default level is installed ininit(), and later calls rewrite it only when the wanted level is not already installed, under a mutex.- yq's process-global expression parser is initialized under a
sync.Oncerather than being lazily assigned by everyEvaluatecall. - Adds
TestEvaluateYqExpression_ConcurrentCallsAreRaceFree, which fails under-racewithout either change.
why
Both globals were written on every evaluation, from the per stack file goroutines that processYAMLConfigFileWithContextInternal spawns:
logging.SetLevelwrites an unsynchronized map insidego-logging, and yq reads that same map from every decoder throughLogger.Debugf. The Go runtime checks concurrent map access, so this is the one that reaches users as a crash.yqlib.InitExpressionParserassigns the exported globalyqlib.ExpressionParserbehind a plain nil check. That write is a pointer, so the runtime does not catch it and it corrupts quietly instead.
On unmodified main at fc4960a the new test reports Found 2 data race(s). With the change, pkg/utils, pkg/yaml/... and internal/exec all pass, the last two unchanged here but sharing the same yq globals.
Two notes for reviewers:
- Installing the level in
init()is what removes the write entirely for a non-Trace run.--logs-level Tracestill performs one write on the first transition, which could in principle race a yq read already in flight. Hoisting the call to configuration load would close that as well; I did not want to reach intopkg/configuninvited. pkg/yaml/edit.go:71callslogging.SetLevelon everyevaluateWithOptions, so it writes the same map. I have not measured whether that path runs concurrently, so I left it alone rather than guess. Happy to fold it in here, or to file it separately, whichever you prefer.
Cost: yqlib.InitExpressionParser measures about 0.4ms once, and the sync.Once keeps that off commands which never evaluate an expression.
references
- Closes #2821
- #2347 looks like the same class of failure in the same goroutine fan-out, for the shared merged context
- #2334 added the global panic handler that cannot catch this one
Summary by CodeRabbit
-
Bug Fixes
- Improved reliability when evaluating yq expressions concurrently.
- Prevented unexpected yq logging changes during parallel evaluations.
- Ensured expression parsing is initialized consistently before use.
-
Tests
- Added coverage for concurrent evaluations, including result consistency and logging behavior.