v1.226.0-rc.4
Pre-releasefix(ai): close DX gaps found in atmos ai field test Erik Osterman (Cloud Posse) (@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.atmosversion-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 --clientvalues instead of silently no-op'ing, by extendingpkg/flags'sWithValidValuesto work on string-slice flags generically (this also fixed a latent bug whereWithValidValueswas silently dead for every command that binds flags viaBindFlagsToViperwithout calling the fullParse()pipeline). - Warns when
--pathis combined with--client/--scope/--global/--all-clientsonskill install, since--pathskips 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 comparisonupdatenow acts on). - Fixes
agent-skills/skills/atmos-ai/SKILL.mddoc drift (it never documentedskill installat all) and removes a phantominfosubcommand fromatmos ai skill --help. - Adds local-path/
file://support to skill source parsing and the downloader. - Documents
--scope/--globalprecedence onskill install/uninstall/update. - Makes
atmos ai exec/ask --sessionactually persist and resume conversations — previously a documented flag that was a complete no-op. - Resolves a session's
Modelfrom the constructed AI client instead of an independent config lookup, fixingsessions export/importfor the default zero-configclaude-codeprovider path (previously exported checkpoints for that path could never be re-imported). - Applies
--mcpserver 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
--formatvalues onai execinstead of silently falling back totext. - Behavior change:
ai execcan 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 0dnow 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!!omapresolution) andmermaid(5 advisories), viapnpm.overridesbumps within their existing major versions. No CodeQL alerts were open. - Fixes a flaky
TestManager_ExportSession_WarnsOnUnimportableCheckpointCI failure: the test asserted on raw ANSI-styledui.Warning()output, which the formatter renders as two adjacent styled runs underCI=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 aicommand 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
0dinstead 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 updateexists 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 updatecommand that was initially scoped out and then built once asked for.
🚀 Enhancements
fix(config): honor --config across internal reloads and multi-file merges Erik Osterman (Cloud Posse) (@osterman) (#2875)
## what- Internal reloads of the CLI config (many call sites across
internal/exec,pkg/vendoring,cmd/, etc. callingInitCliConfig(schema.ConfigAndStacksInfo{}, false)) now fall back to parsing--config/--config-path/--base-pathfromos.Args/env instead of silently discarding the selection made at startup. - A second
--configfile 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 getnow reports the effective, fully-merged configuration for the invocation (all--configfiles,--config-pathdirs, and profiles applied) instead of reading a single physical file.VendorDirAbsolutePath/WorkflowsDirAbsolutePathare now precomputed once (mirroring the existing top-levelbase_pathresolution), so vendor/workflow path joins no longer re-derive a possibly still-relativeBasePath.
why
atmos --config <file> terraform plan/testwas failing withfailed to find importeven thoughatmos --config <file> list stacksworked with the identical flag, because a downstreamInitCliConfigre-invocation lost the--configselection mid-command.- Splitting config across two
--configfiles with a conflicting array value madestacks.included_pathsunusable for stack discovery, whileatmos config getmisleadingly reported the config as unchanged.
references
Summary by CodeRabbit
-
New Features
atmos config getnow 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 Erik Osterman (Cloud Posse) (@osterman) (#2880)
## what- Fix
type: archive,file,workdir,junit, and containerbuildstep handlers to resolve relativesource/destination/path/files/context/dockerfilefields againststep.WorkingDirectoryinstead of the Atmos process's own cwd. - Add a shared
BaseHandler.ResolveInWorkingDirectoryhelper (pkg/runner/step/handler_base.go) used by all five handlers; containerbuildadditionally anchorsDockerfileto the resolvedContext, 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: archivesteps run as component lifecycle hooks ignoredstep.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, containerbuild) 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 Andriy Knysh (@aknysh) (#2890)
## what- Store the seeded Azure management access token in the Azure CLI MSAL cache with all ARM scope forms in its
targetfield — the modern scope (https://management.azure.com/.default) plus the legacy audience forms (https://management.core.windows.net/.defaultand the double-slash variant), with matching forms for the US Government and China clouds (newLegacyManagementScopesfield onCloudEnvironment). - 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 (newCopyAtmosRefreshTokensInto;UpdateAzureCLIFilesgains arealmparameter). 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 viaAzureCLICredentialrequest an ARM token for the legacy audiencehttps://management.core.windows.net/(the azidentity/azapi default). The cache write-back only seeded the modern scope, so MSAL's cache lookup missed andazapi-based modules (all modern Azure Verified Modules) failed mid-apply withAzureCLICredential: ERROR: Can't find token from MSAL cache— whileazurermresources 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 byaz— seeding it letsazself-mint tokens for any audience and survive access-token expiry. - Until now the workaround was to run a real
az loginalongsideatmos auth login, defeating the purpose of single-command auth.
Manually verified end-to-end on a real Azure tenant:
- Logged out completely and wiped all caches:
az logout,az account clear, removed~/.azure/msal_token_cache.jsonand~/.azure/atmos/(confirmed withaz account showfailing). - Ran
atmos auth loginalone — noaz loginat any point. - Confirmed the refresh token was copied into the Azure CLI cache:
jq '.RefreshToken | length' ~/.azure/msal_token_cache.jsonreturned1(previously0). - Requested a token for the legacy ARM audience — the exact request
azidentity/azapimake:az account get-access-token --resource https://management.core.windows.net/succeeded (previously failed withCan'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. - Ran
atmos terraform planon anazapi-heavy component (the exact field failure): refresh and plan completed clean with no MSAL errors.
references
- Follow-up to #2861 and #2862 (Azure auth +
azure/interactiveprovider, shipped in v1.225.0) - azidentity legacy ARM audience default: https://github.com/Azure/azure-sdk-for-go/tree/main/sdk/azidentity
- MSAL cache scope (subset) matching: https://learn.microsoft.com/en-us/entra/identity-platform/scopes-oidc
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 loginprompts. - 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 Erik Osterman (Cloud Posse) (@osterman) (#2905)
## whatField-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 byatmos kubernetes validate/apply/deploy, leaving validation enabled with no warning. It now fails closed with a clear error.provision.targets.<name>.splithad 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) — sosplit: "yes"passedatmos validate stacksand was silently dropped at runtime, falling back to path-based auto-inference. The embedded schema is now synced, and the git target'sparseConfigalso fails closed as defense in depth (sincekubernetes validate/apply/deploydon'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
RemoveAllthat 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.reconcilenow 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-gitskill doc: it showed a nestedsigning: {mode: auto}instead of the real flatsigning: autostring field — copying it verbatim fails to parse. - Documented that
atmos kubernetes validate --serverfails 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 thoughapply/deployof 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.