github cloudposse/atmos v1.226.0-rc.4

pre-release3 hours ago
fix(ai): close DX gaps found in atmos ai field test @osterman (#2903) ## what
  • Adds atmos ai skill update [name], a new command that compares each installed bundled skill's recorded version against the catalog embedded in the running binary and reinstalls only the ones that are actually outdated — closes the "no update command" gap the fixes below originally left deferred. See the blog post for the full story.
  • Enforces the compatibility.atmos version-compatibility gate for bundled and multi-skill Git package skill installs, not just single-skill Git clones (it was previously skipped entirely for those two paths).
  • Rejects unrecognized atmos ai skill install/uninstall/update --client values instead of silently no-op'ing, by extending pkg/flags's WithValidValues to work on string-slice flags generically (this also fixed a latent bug where WithValidValues was silently dead for every command that binds flags via BindFlagsToViper without calling the full Parse() pipeline).
  • Warns when --path is combined with --client/--scope/--global/--all-clients on skill install, since --path skips auto-distribution and those flags are otherwise silently ignored.
  • Gives the skill-registry-corruption error an actionable hint via the error-builder pattern instead of a bare wrapped JSON error.
  • Shows a skill's minimum required Atmos version in skill list --detailed, and flags when an installed skill has a newer catalog version available (using the same comparison update now acts on).
  • Fixes agent-skills/skills/atmos-ai/SKILL.md doc drift (it never documented skill install at all) and removes a phantom info subcommand from atmos ai skill --help.
  • Adds local-path/file:// support to skill source parsing and the downloader.
  • Documents --scope/--global precedence on skill install/uninstall/update.
  • Makes atmos ai exec/ask --session actually persist and resume conversations — previously a documented flag that was a complete no-op.
  • Resolves a session's Model from the constructed AI client instead of an independent config lookup, fixing sessions export/import for the default zero-config claude-code provider path (previously exported checkpoints for that path could never be re-imported).
  • Applies --mcp server filtering for CLI providers (claude-code/codex-cli/copilot-cli/gemini-cli) too — it was silently ignored, so all configured MCP servers were always passed through regardless of the flag.
  • Rejects invalid --format values on ai exec instead of silently falling back to text.
  • Behavior change: ai exec can now return exit code 2 for a genuine infrastructure-level tool failure (e.g. an unregistered tool) immediately, without waiting on the 25-iteration tool-call loop to exhaust as it did before.
  • Behavior change: sessions clean --older-than 0d now deletes all sessions immediately, distinguished from the flag not being passed at all (which still defaults to 30 days); negative durations are now a hard parse error instead of silently falling back to the default.
  • Remediates 7 open Dependabot alerts (2 high, 4 medium, 1 low) in transitive website dependencies: js-yaml (GHSA-5p4m-2wfm-xmqj, quadratic CPU consumption in !!omap resolution) and mermaid (5 advisories), via pnpm.overrides bumps within their existing major versions. No CodeQL alerts were open.
  • Fixes a flaky TestManager_ExportSession_WarnsOnUnimportableCheckpoint CI failure: the test asserted on raw ANSI-styled ui.Warning() output, which the formatter renders as two adjacent styled runs under CI=true — same visible text, different byte layout, so the test passed locally and failed in CI. Strips ANSI before asserting on content now.

why

  • These are all findings from a hands-on field test of the atmos ai command surface — reading the real implementation, hypothesizing plausible misuse an automated test wouldn't catch, and executing for real against isolated fixtures — rather than a spec change or feature request. Most are silent DX gaps (a flag that looks like it works but doesn't, an error with no way forward, a validation check that only applies on some of the paths that need it).
  • The two behavior changes exist because the current behavior actively undermines the documented contract: an exit code that's "practically unreachable" is useless to scripted consumers, and a duration flag that silently no-ops on 0d instead of doing what it says is a footgun in the other direction (a user who deliberately asks to delete everything gets nothing, silently).
  • atmos ai skill update exists because, once asked, leaving "no update command" as a documented gap wasn't the right call — bundled skills going stale after a binary upgrade is exactly the kind of silent drift this whole PR is about fixing elsewhere.
  • The security fix was picked up automatically after pushing this branch (GitHub reported the alerts against the default branch) and is bundled here per this repo's standing policy of fixing security alerts directly on the branch already in flight rather than opening a separate PR.

references

  • No tracked GitHub issues — everything here was discovered fresh during this field test and addressed directly in this PR, including the atmos ai skill update command that was initially scoped out and then built once asked for.

🚀 Enhancements

fix(config): honor --config across internal reloads and multi-file merges @osterman (#2875) ## what
  • Internal reloads of the CLI config (many call sites across internal/exec, pkg/vendoring, cmd/, etc. calling InitCliConfig(schema.ConfigAndStacksInfo{}, false)) now fall back to parsing --config/--config-path/--base-path from os.Args/env instead of silently discarding the selection made at startup.
  • A second --config file that sets a conflicting value for an array-typed key (e.g. stacks.included_paths) no longer aborts stack discovery for entries that still legitimately match; a real "nothing matched at all" case now returns a distinct error instead.
  • atmos config get now reports the effective, fully-merged configuration for the invocation (all --config files, --config-path dirs, and profiles applied) instead of reading a single physical file.
  • VendorDirAbsolutePath/WorkflowsDirAbsolutePath are now precomputed once (mirroring the existing top-level base_path resolution), so vendor/workflow path joins no longer re-derive a possibly still-relative BasePath.

why

  • atmos --config <file> terraform plan/test was failing with failed to find import even though atmos --config <file> list stacks worked with the identical flag, because a downstream InitCliConfig re-invocation lost the --config selection mid-command.
  • Splitting config across two --config files with a conflicting array value made stacks.included_paths unusable for stack discovery, while atmos config get misleadingly reported the config as unchanged.

references

Closes #2867
Closes #2868

Summary by CodeRabbit

  • New Features

    • atmos config get now reads the fully merged configuration, including multiple files, paths, and profiles.
    • Added support for comma-separated configuration values from environment variables.
    • Relative vendor, workflow, profile, and hook paths now resolve more consistently.
  • Bug Fixes

    • Improved handling of configuration precedence and profile paths.
    • Errors now provide clearer, safer paths without exposing machine-specific locations.
    • Invalid glob patterns and ambiguous configuration edits report more actionable messages.
  • Documentation

    • Updated configuration, profile, and command references for multi-file behavior and path resolution.
fix(steps): resolve relative paths against step.WorkingDirectory @osterman (#2880) ## what
  • Fix type: archive, file, workdir, junit, and container build step handlers to resolve relative source/destination/path/files/context/dockerfile fields against step.WorkingDirectory instead of the Atmos process's own cwd.
  • Add a shared BaseHandler.ResolveInWorkingDirectory helper (pkg/runner/step/handler_base.go) used by all five handlers; container build additionally anchors Dockerfile to the resolved Context, matching Docker's own convention.
  • Add regression tests for each fixed handler plus a hooks-integration test (TestStepEngineRunsArchiveTypeWithRelativeWorkingDirectory) reproducing the original bug end-to-end.
  • Update two pre-existing container tests that had hardcoded the old (buggy) relative-path behavior to assert the corrected absolute-path behavior.

why

  • type: archive steps run as component lifecycle hooks ignored step.WorkingDirectory, even though the hooks engine (pkg/hooks/step_engine.go) already correctly computes and sets it to the resolved component path before dispatch — the field was just never read back out by the handler.
  • Auditing for the same defect class turned up four more handlers (file, workdir, junit, container build) with the identical bug: relative paths resolved via template substitution only, then silently anchored to process cwd instead of the step's configured working directory.

references

Summary by CodeRabbit

  • Bug Fixes

    • Relative paths in archive, file, working-directory, JUnit, and container build steps now resolve against the configured working directory.
    • Dockerfiles, Bake files, build contexts, and local cache paths resolve correctly while preserving absolute, non-local, and fallback behavior.
    • Workflow-level working directories now apply to extended steps.
    • Improved error reporting preserves useful hints, context, and template-validation details.
  • Documentation

    • Clarified working-directory behavior for hooks, workflows, and custom commands.
  • Tests

    • Added regression and integration coverage for path resolution and workflow error handling.
fix(auth): cover legacy ARM audience and seed refresh token in Azure CLI cache @aknysh (#2890) ## what
  • Store the seeded Azure management access token in the Azure CLI MSAL cache with all ARM scope forms in its target field — the modern scope (https://management.azure.com/.default) plus the legacy audience forms (https://management.core.windows.net/.default and the double-slash variant), with matching forms for the US Government and China clouds (new LegacyManagementScopes field on CloudEnvironment).
  • Copy the account's refresh token from the Atmos realm MSAL cache (~/.azure/atmos/<realm>/msal_token_cache.json) into the Azure CLI cache after login (new CopyAtmosRefreshTokensInto; UpdateAzureCLIFiles gains a realm parameter). Skipped for service principals, empty realms, or unmatched home account IDs.
  • Regression tests written first to reproduce both failures, now pinning the fix (pkg/auth/providers/azure/token_audience_test.go).
  • Fix doc: docs/fixes/2026-08-06-azure-cli-cache-legacy-audience-refresh-token.md.

why

  • After atmos auth login, Terraform providers that authenticate via AzureCLICredential request an ARM token for the legacy audience https://management.core.windows.net/ (the azidentity/azapi default). The cache write-back only seeded the modern scope, so MSAL's cache lookup missed and azapi-based modules (all modern Azure Verified Modules) failed mid-apply with AzureCLICredential: ERROR: Can't find token from MSAL cache — while azurerm resources in the same apply succeeded. Observed in a real cold-start apply of a state backend component.
  • MSAL matches a requested scope as a subset of a cache entry's space-separated target, and ARM accepts both audiences interchangeably, so a single entry carrying every form satisfies every lookup.
  • No refresh token was seeded at all, so once the access tokens expired (~1h) every az-side lookup failed the same way. Atmos authenticates with the Azure CLI's own public client ID, so the refresh token in the Atmos realm cache is directly usable by az — seeding it lets az self-mint tokens for any audience and survive access-token expiry.
  • Until now the workaround was to run a real az login alongside atmos auth login, defeating the purpose of single-command auth.

Manually verified end-to-end on a real Azure tenant:

  1. Logged out completely and wiped all caches: az logout, az account clear, removed ~/.azure/msal_token_cache.json and ~/.azure/atmos/ (confirmed with az account show failing).
  2. Ran atmos auth login alone — no az login at any point.
  3. Confirmed the refresh token was copied into the Azure CLI cache: jq '.RefreshToken | length' ~/.azure/msal_token_cache.json returned 1 (previously 0).
  4. Requested a token for the legacy ARM audience — the exact request azidentity/azapi make: az account get-access-token --resource https://management.core.windows.net/ succeeded (previously failed with Can't find token from MSAL cache). Its expiry matched the login session's, proving MSAL served it from the seeded multi-audience entry via subset matching rather than minting a new token.
  5. Ran atmos terraform plan on an azapi-heavy component (the exact field failure): refresh and plan completed clean with no MSAL errors.

references

Summary by CodeRabbit

Bug Fixes

  • Improved Azure CLI authentication cache updates for legacy ARM audiences across public and sovereign Azure environments.
  • Preserved refresh tokens after authentication, reducing unnecessary az login prompts.
  • Improved compatibility across user, device-code, guest, OIDC, and service-principal authentication flows.
  • Enhanced support for isolated credential environments.

Documentation

  • Added guidance for diagnosing and recovering from Azure CLI cache issues.
fix(kubernetes): close gaps found field-testing Kustomize GitOps delivery @osterman (#2905) ## what

Field-tested the Kustomize/git-delivery GitOps pipeline shipped in #2874 (real k3s cluster, real local git remotes, real fixtures — not mocks) and fixed the gaps found:

  • validate: "false" (a quoted YAML string, an easy typo) was silently ignored by atmos kubernetes validate/apply/deploy, leaving validation enabled with no warning. It now fails closed with a clear error.
  • provision.targets.<name>.split had no type enforcement in the runtime-embedded JSON Schema (pkg/datafetcher/schema/atmos/manifest/1.0.json) — only in the docs-facing copy (stacks/stack-config/1.0.json) — so split: "yes" passed atmos validate stacks and was silently dropped at runtime, falling back to path-based auto-inference. The embedded schema is now synced, and the git target's parseConfig also fails closed as defense in depth (since kubernetes validate/apply/deploy don't go through the stack-config schema path).
  • Flipping a git delivery target between directory and single-file mode now emits a warning before the unconditional RemoveAll that replaces whatever currently exists at the managed path.
  • The managed git workdir cache never reconciled with a changed git.repositories.<name>.uri — an already-cloned repo kept using its original remote forever. reconcile now syncs the local remote URL to the configured URI before fetching.
  • The DNS-1123 invalid-name error embeds a regex containing [, ], (, ) with no spaces; rendered as plain markdown it was both mangled (brackets collide with link syntax) and hard-wrapped mid-token. It's now backtick-fenced as a code span, so it renders verbatim.
  • Fixed a copy-pasted config example in the atmos-git skill doc: it showed a nested signing: {mode: auto} instead of the real flat signing: auto string field — copying it verbatim fails to parse.
  • Documented that atmos kubernetes validate --server fails on a manifest set that creates its own namespace and delivers into it in the same batch (inherent to Kubernetes server-side dry-run semantics — each object's dry-run is evaluated independently against already-persisted state), even though apply/deploy of the identical objects succeeds.

Also includes an unrelated, incidental fix: bumped js-yaml/mermaid pnpm overrides in website/ to close 7 open Dependabot alerts (triggered by this repo's post-push security-remediate automation).

why

A /field-test pass is a hands-on DX pass that builds real fixtures and runs the actual CLI against them, specifically to catch "looks fine in review, breaks or misleads a real user" gaps that unit tests (which mostly exercise fake clients and hand-built inputs) don't cover. Every fix here was independently reproduced live before being fixed, and re-verified live after. The pass also confirmed several things work correctly as documented (directory/single-file delivery, the Kustomize metadata.name exemption, validate:false + --server interaction, offline validation gating delivery) — those aren't included here since nothing needed to change for them.

references

  • Follow-up to #2874 (Kustomize/git-delivery GitOps support)

Summary by CodeRabbit

  • New Features
    • Git targets now support split output as separate files or a single multi-document YAML file, with automatic mode inference when unspecified.
    • Existing Git workdirs now synchronize changed repository URLs before fetching.
  • Bug Fixes
    • Invalid Kubernetes validation settings and Git split values now produce clear errors.
    • Switching Git output modes safely replaces conflicting files or directories and provides warnings.
    • Kubernetes validation messages now format invalid names more clearly.
  • Documentation
    • Added guidance for server-side dry-run validation when manifests create namespaces and dependent resources.
    • Updated repository signing configuration guidance.

Don't miss a new atmos release

NewReleases is sending notifications on new releases.