feat(mcp): server management commands, self/atmos-pro presets, and config UX fixes Erik Osterman (Cloud Posse) (@osterman) (#2720)
what
- Extracts the standalone
atmos mcp installcommand (client-config installer for AI coding assistants) from the stalledosterman/pro-install-cmdbranch, leaving all Atmos Pro-specific wiring behind. - Adds full MCP server-management commands:
atmos mcp add,atmos mcp remove, andatmos mcp uninstall, rounding out the existinglist/install/export/status/testset.add/removeonly editmcp.serversinatmos.yaml(the source of truth);install/uninstallpush/pull that declared config to/from AI client config files (Claude Code, Cursor, VS Code, Codex, Gemini).- Adds built-in
selfandatmos-propresets soatmos mcp add(no arguments) works out of the box, resolving to Atmos's own MCP server or the Atmos Pro MCP server respectively. - Adding the
selfpreset interactively offers to flipmcp.enabled: trueinatmos.yamlif it isn't already, since that preset requires it to function. atmos mcp list/statusnow nudge users to runatmos mcp add atmos-prowhen Atmos Pro is configured but its MCP server hasn't been added yet.atmos mcp installwith nothing configured now interactively offers to add+install theselfpreset instead of just printing a static hint.
- Adds a new top-level
/mcpdocs landing page (website/docs/mcp/mcp.mdx) covering both directions — Atmos as an MCP client and as an MCP server — cross-linked from/aivia tabs, matching the existing/proand/ailanding pages. - Expands
atmos mcp install/uninstall's supported-client list from 5 to 15: adds Claude Desktop, Windsurf, Cline, Cline CLI, Zed, OpenCode, Goose, GitHub Copilot CLI, Antigravity, and MCPorter, alongside the existing Claude Code, Cursor, VS Code, Codex, and Gemini. Includes a new YAML config writer (for Goose) and bespoke per-client entry renderers where a client's schema differs from the commonmcpServersshape (Zed'scontext_servers, OpenCode'smcp). - Adds a
## Troubleshootingsection to theatmos mcp installdocs covering post-install behavior that varies by client (some need a restart, others need a manual settings toggle, some auto-reload). - Fixes #2628:
atmos ai skill installpreviously always wrote to~/.atmos/skills/, so VS Code / GitHub Copilot's.github/skills/couldn't discover installed skills.- Adds
--path/ATMOS_AI_SKILL_PATHto override the install directory. - Adds zero-flag multi-client skill distribution:
atmos ai skill install <name>now auto-detects which AI clients (Claude Code, VS Code/Copilot, Gemini) are in use in the current project and copies the skill into each one's well-known skill directory, mirroringatmos mcp install's detect/--client/--all-clients/interactive-picker UX.atmos ai skill uninstallgained the same--client/--all-clientssymmetry to clean up those copies. atmos ai skill install/uninstallwith no<source>/<name>now acts on every skill (every bundled skill for install, every installed skill for uninstall) instead of requiring one invocation per skill — mirrorsatmos mcp install/uninstall's existing "no server names given = act on everything configured" convention rather than adding a separate--allflag.
- Adds
- Adds
atmos config set/delete/formatUX polish:- Infers the value's type (
bool,int,float,string,yaml) from the Atmos config schema via reflection onyaml/mapstructurestruct tags, soatmos config set mcp.enabled truewrites a real boolean without needing--type=bool. - Echoes the value that was written/deleted/formatted, and shows paths relative to the current directory instead of absolute.
- Infers the value's type (
- Fixes a CLI error-message bug: a leaf command (no subcommands) whose
Argsvalidator rejects the wrong number of arguments (e.g.atmos config unset mcp.enabled trueagainstExactArgs(1)) previously reported the misleadingUnknown command mcp.enabled— implying an unrecognized subcommand — when the real problem was too many arguments. Now surfaces Cobra's own accurate validator message. - Fixes toolchain installs on macOS: verifier binaries downloaded during
atmos toolchain installcan carry thecom.apple.quarantine/com.apple.provenancexattrs, causing Gatekeeper to block execution even though the binary was already checksum/signature-verified. Strips the xattrs and ad-hoc re-signs the binary on darwin, with averifier_trust: disabledopt-out. - Adds a "Native AWS SigV4 Signing" design section to
docs/prd/atmos-mcp-integrations.md(proposal only, not implemented in this PR) for natively signing requests to AWS-hosted MCP servers using Atmos Auth identities, without a third-partymcp-proxy-for-awsbridge.
why
atmos mcp install's client-installer code was implemented on a branch entangled with unrelated, stalled Atmos Pro onboarding work; splitting it out lets the MCP work ship independently.- Early testing surfaced a real gap: there was no way to get Atmos's own MCP server, or the Atmos Pro MCP server, into an AI client's config without hand-editing
atmos.yamland running client-specific commands manually —atmos mcp add self/atmos mcp add atmos-procollapse that to one command. atmos config set mcp.enabled truesilently writing the string"true"instead of a boolean is a footgun users would hit immediately while following the new MCP add/enable flow; schema-based type inference removes the need to know or pass--type=bool.- The "Unknown command" error was actively misleading during testing of this same feature (
atmos config unset mcp.enabled true), and the root cause turned out to be a small, generally-applicable flaw in how Cobra Args-validator failures were reported for leaf commands. - The toolchain trust fix was discovered by a concurrent session working in this same worktree; bundling it into this PR (with its own commit and blog post) avoids a second churn cycle through CI/review for an unrelated one-line-symptom, multi-file fix.
- The VS Code/Copilot skill-discovery gap and the narrow 5-client MCP list were both instances of the same underlying problem: Atmos only supported a handful of AI clients by name instead of the broad set real users are on. Expanding both to the same ~15-client list (sourced from add-mcp.com/docs/agents, which also supplied the model for the new Troubleshooting guidance) closes that gap once instead of client-by-client over time, and mirroring MCP's already-proven detect/
--client/--all-clients/picker UX for skills keeps the two subsystems consistent for users learning either one.
references
- Supersedes the MCP-install portion of the stalled
osterman/pro-install-cmdbranch (Atmos Pro-specific--mcpinstall flag intentionally left behind there). - Closes #2628
feat: tags and labels standard for auth, components, and bulk selection Erik Osterman (Cloud Posse) (@osterman) (#2738)
what
- Adds
tagsto auth identities/providers, filterable via--tagsonatmos auth list/login/logout(composes with--providers/--identities). - Adds
metadata.tags(list, any-match) andmetadata.labels(map, all-match) to components, filterable viaatmos list components --tags/--labels. - Adds four new no-arg YAML functions —
!tags,!labels,!labels.keys,!labels.values— that return the current component's ownmetadata.tags/metadata.labels, typed, for bridging intovars(e.g.vars.tags: !labelsforterraform-null-label-style modules). - Adds
--tags/--labelsbulk-selection filtering toterraform,kubernetes,helm, andcontainercommands, composing with (not replacing)--all/--affected, via a new sharedpkg/tagspackage. - Updates the
examples/demo-authandexamples/quick-start-advancedexamples and their casts to demonstrate the new tags/labels filtering.
why
- Implements the
docs/prd/tags-and-labels-standard.mddesign (previously merged docs-only) so users can categorize and bulk-select components and identities without maintaining static name lists per stack/region/environment. - Tags (any-match) and labels (all-match, Kubernetes-selector semantics) give two matching semantics suited to different selection needs — "any of these categories" vs. "all of these key=value constraints" — consistent with existing conventions like
atmos vendor pull --tags.
references
- Implements
docs/prd/tags-and-labels-standard.md. - Relates to #1323 (
mcalhoun's open PR addingmetadata.labels+ a--selectorflag tolist/describecommands) — this PR is broader in scope (adds tags, auth tags, YAML functions, and--tags/--labelsonterraform/kubernetes/helm/container) but shares themetadata.labelsfoundation.
feat(claude): add changelog and fix-log skills, convert roadmap agent Erik Osterman (Cloud Posse) (@osterman) (#2751)
what
- Adds a
changelogskill consolidating blog-post authoring rules (template, tags/authors, problem-first framing, no backtick-opening prose, optional cast embeds, no Go-internals leakage) that were previously duplicated acrossCLAUDE.md, thepull-requestskill, and thedocsskill. - Adds a
fix-logskill (with avalidate-fix-doc.shscript) for durable fix records underdocs/fixes/, using a merged format that keeps the old convention's title/date line but adopts a consistent five-section structure (Summary/Context/Changes/Validation/Follow-ups). - Converts
.claude/agents/roadmap.mdinto.claude/skills/roadmap/SKILL.md, invoked viaSkillinstead ofAgent. - Trims
CLAUDE.md,pull-request/SKILL.md, anddocs/SKILL.mdto short pointers at the newchangelog/roadmapskills instead of embedding or duplicating the rules.
why
- Blog-post guidance had drifted into three places (
CLAUDE.md,pull-request,docs), withdocsindependently restating rules and solely owning one rule (no backtick-opening prose) found nowhere else. - The
roadmapagent's work is a narrow, deterministic data-editing procedure against a fixed schema — it doesn't need an isolated subagent context, matching this repo's agent-vs-skill philosophy. - Analyzing real
website/blog/*.mdxposts turned up recurring style violations (feature-first framing, backtick-opening prose, Go-internals leakage) worth codifying with concrete before/after examples.
references
- None
fix(ci): keep bot dependency PRs labeled and rebased automatically Erik Osterman (Cloud Posse) (@osterman) (#2752)
what
- Add a default
no-releaselabel torenovate.json, matching the label Dependabot PRs already get fromdependabot.yml. - Add
"rebaseWhen": "behind-base-branch"torenovate.jsonso Renovate keeps its own PRs current withmain. - Add a Mergify rule in
.github/mergify.ymlthat runsupdateon any open Dependabot/Renovate PR againstmainthat's conflict-free but behind — Dependabot has no native "keep up to date" option, so this fills that gap.
why
Triaging the ~18 open Dependabot/Renovate PRs turned up two systemic problems instead of one-off bad PRs:
- Every open Renovate PR (10 of them) was failing the required
PR Semver Labelscheck because it carried zero semver label. The check-cleanup logic in.github/workflows/codeql.yml'spr-semver-labelsjob only special-casesdependabot[bot], anddependabot.ymlis the only place a default label gets set — Renovate PRs never got one. main's branch protection requires the PR branch to be up to date (strict: true), but neither bot proactively rebases a clean, non-conflicting PR just becausemainmoved, so PRs accumulate asBEHINDuntil someone manually updates them.
This is purely CI/automation config — no user-facing behavior change, hence no-release.
references
feat(workflows): add native archive step type for zip/tar packaging Erik Osterman (Cloud Posse) (@osterman) (#2730)
what
- Add a native
type: archivestep for workflows and custom commands that packs a directory or file into azip,tar, ortgzarchive using the Go standard library only (archive/zip,archive/tar,compress/gzip) — no externalzip/tarbinary required. - Support
action: replace(always rebuild fresh) on every format, andaction: update(incremental add/refresh) forzipand uncompressedtar;action: create/action: extractandtar.bz2/tar.xzwriters are reserved in the schema for a later phase and return a typed "not yet implemented" error. - Support
subpathnesting,include/excludeglob filtering (reusing the existingpkg/utils.PathMatchmatcher), and format inference from the destination/source extension. - Reuse the existing
kind: stephook bridge instead of adding a dedicated hook kind, sokind: step+type: archiveworks as a component lifecycle hook with zero hook-side code. - Add the
docs/prd/archive-step.mdPRD, step-type reference docs, a changelog blog post, and a roadmap milestone.
why
- Packaging build artifacts (most commonly a Lambda function's
handler.zipbeforeterraform plan/apply) had no native Atmos primitive — the only option was shelling out tozip/tarvia akind: commandhook ortype: shellstep, which breaks on Windows CI and gives no typed validation. - This surfaced while migrating a Terragrunt example that used a
before_hookwrapping thezipbinary; adata "archive_file"Terraform data source was tried first but doesn't reliably run before packaging is needed. - The step ships as a step type only (not a new hook kind) to follow the precedent set by
emulator/http/container, which reach hooks purely through thekind: stepbridge — this avoids duplicating schema and hook-engine code for a capability the step registry already provides.
references
- PRD:
docs/prd/archive-step.md
fix(jit): support oci:// sources in JIT auto-provisioning zack-is-cool (#2747)
what
- JIT auto-provisioning (
before.terraform.inithook, e.g.atmos terraform plan <component> -s <stack>) now supportsoci://component sources, matching whatatmos vendor pullalready supported. - Relocated the OCI-fetch implementation (image pull, auth precedence, tar extraction) from
internal/execinto a new sharedpkg/ocipackage, so both the legacy vendor-pull path and the JIT auto-provisioner can reach it without an import cycle. pkg/provisioner/source.VendorSourcenow branches onvendor.IsOCIURI(uri)and callsoci.ProcessImageinstead of go-getter foroci://sources.- Added support for OpenTofu's native "install modules from OCI registries" format (
artifactType: application/vnd.opentofu.modulepkg, a ZIP-archive layer) — real registries publishing modules this way previously failed witharchive/tar: invalid tar headerbecause the puller assumed every layer was a tar+gzip stream. - OCI pulls are now bounded by a timeout (10 minutes, matching the existing go-getter path) instead of being able to hang indefinitely on a slow/unresponsive registry.
- Zip extraction is capped against decompression-bomb amplification (bounded archive and per-entry sizes).
- Added an in-process fake OCI registry test helper (
pkg/oci/ocitest, both tar and zip layouts) so the new tests are fully hermetic — no real network or registry dependency. - Added end-to-end regression tests reproducing the reported scenario and the OpenTofu module-package scenario.
why
- A component with
provision.workdir.enabled: trueand anoci://source fails when JIT-provisioned with go-getter's own error,download not supported for scheme 'oci', even though the identical source works fine withatmos vendor pull. - While validating the fix against a real registry, a second real-world gap surfaced: registries distributing modules in OpenTofu's native OCI format use ZIP layers, which the puller couldn't extract at all.
- Code review (CodeRabbit) flagged that the OCI pull path had no timeout, unlike its sibling go-getter path, and that zip extraction had no size bound — both addressed here.
references
Closes #2716
feat(terraform): add --all/--affected bulk execution to terraform init Erik Osterman (Cloud Posse) (@osterman) (#2742)
what
- Add
--all,--affected,--max-concurrency,--failure-mode, and--log-orderflags toatmos terraform init, matching the bulk-execution flags already onapply/plan/destroy. - Fix the scheduler's
supportsTerraformConcurrencygate, which silently capped--max-concurrencyto1for any subcommand it didn't explicitly recognize (includinginit). initkeeps the natural forward dependency order (prerequisites before dependents), unlikedestroy, which reverses the graph.- Add unit test coverage for the new flags and the concurrency/ordering behavior, plus a new "Graph-backed Bulk Init" docs section and a changelog post.
why
--all/--affectedlanded onapply/plan/destroywhen Terraform bulk execution moved onto the scheduler-backed dependency graph (#2466), butinitwas left out.- Reinitializing every component after a provider upgrade, or bootstrapping a new environment, required scripting a loop over components by hand.
references
- Closes #2375
feat(claude): autonomous PR merge-readiness automation Erik Osterman (Cloud Posse) (@osterman) (#2718)
What
Builds a full autonomous PR merge-readiness system for Claude Code sessions in this repo, layered up from an initial hourly rebase-and-CodeRabbit loop into a complete one-shot/recurring check-and-fix pipeline:
pr-maintenance-loopskill — schedules the check hourly via Claude Code's nativeCronCreate/ScheduleWakeupprimitives (not GitHub Actions, no external service). CallsCronCreatedirectly rather than the generic/loopskill, since this loop is inherently tied to the localgit/ghsession (signing key, checkout state) and has no cloud-compatible equivalent.fix-allskill — the one-shot merge-readiness passpr-maintenance-loopruns every cycle: sync withorigin/main, check CI, resolve CodeRabbit threads, patch-scoped lint, patch-scoped test/coverage, and a final readiness check that announces (via thesayskill's audible nudge, plus a written summary) once the PR needs nothing but human final review. That readiness check also reconciles the PR title/description against the patch's full accumulated scope before announcing, using a narrowly-scopedgh pr edit --title/--body-filepermission (labels/reviewers/base/milestone stay denied and human-owned).lint/test-coverageskills — patch-aware (--new-from-rev=origin/main) lint and test/coverage checks reused both standalone and fromfix-all.sayskill — a macOS audible nudge (say) for every human-attention exit path, plus the one positive "ready for final review" case.- New agents —
lint-fix,test-coverage-fix,merge-conflict-resolve, andcoderabbit-review(existing, reused) do the actual fixing/resolving work each skill delegates to. atmos fixcustom commands (.atmos.d/fix.yaml) —sync/ci/threads/comments/lint/coverage/tests/--all, the mechanical half each skill step wraps..claude/settings.json— the real enforcement boundary: a narrow allow/deny list covering exactly the git/gh/go commands this automation needs, with explicit denies for force-push,git reset --hard/clean,gh pr merge/close/edit --base/--add-reviewer/--milestone/--add-label, and edits to.github/workflows/**,Makefile,go.mod,go.sum.CLAUDE.md— points every future session/worktree at the loop.
Why
PR maintenance chores — rebasing against main, responding to CodeRabbit threads, keeping lint/coverage green, and knowing when a PR is actually ready for a human's final pass — currently need manual attention every cycle. This automates the full loop hourly in any worktree, using only the local git/gh session already in use (no new external service or secrets), while keeping merge fully human-gated, treating all PR/CodeRabbit content as untrusted data rather than instructions, and giving an explicit audible+written signal once a PR is genuinely ready for review instead of leaving that judgment call to whoever happens to check next.
References
None — internal Claude Code tooling change, no linked issue.
feat(vendor): vendor update/diff, archived-repo checks, component.yaml Erik Osterman (Cloud Posse) (@osterman) (#2715)
what
- Add
atmos vendor update— checks every Git-backed source invendor.yaml(and standalonecomponent.yaml/component.ymlmanifests) for a newer version, honoring each source'sconstraints(semver range, excluded versions,no_prereleases), and writes the newversionin place using the format-preservingpkg/yamlengine. Supports--check(dry run),--pull(fetch after updating),--component,--tags,--outdated, and--component-manifests. - Add
atmos vendor diff— shows the Git diff between two versions (tags, branches, or commits) of a vendored component with no local checkout. - Detect archived upstream GitHub repositories (
pkg/github/archived.go,pkg/vendoring/archived.go) and surface that status during update checks. - Add a spinner/progress UI (
cmd/vendor/update_spinner.go) and a tabular update report (cmd/vendor/update_report.go) forvendor update's output. - Fix
vendor.base_path/--chdirresolution sovendor update/diff/get/setno longer fail to findvendor.yamlwhenatmos.yamlconfigures a non-defaultvendor.base_path. - Supporting changes:
pkg/io/streams.gomasking,cmd/version/formatters.go,pkg/toolchain/info.go,errors/errors.go, plus newpkg/vendoring/resolve.goandpkg/vendoring/component_files.goto unify component resolution acrossvendor.yamlandcomponent.yamlsources. - Adds a blog post (
website/blog/2026-07-09-vendor-diff-and-update.mdx) and roadmap milestone update.
why
- Bumping a vendored component's pinned version previously meant three manual steps: checking upstream tags yourself, cloning the repo somewhere to diff two versions, and hand-editing the
versionfield invendor.yamlwhile risking a stripped comment or mangled YAML anchor. - These commands close that loop using the existing format-preserving
pkg/yamlengine (the same one behindatmos config|stack|vendor get|set|delete), and extend it to repos that only use per-componentcomponent.yamlmanifests.
references
- Blog:
website/blog/2026-07-09-vendor-diff-and-update.mdx
chore(security): resolve all open Dependabot and CodeQL alerts Erik Osterman (Cloud Posse) (@osterman) (#2717)
what
Drives every outstanding GitHub security finding on this repo to zero: all 84 open Dependabot alerts (6 Go, 78 npm) and all 11 open CodeQL alerts.
Go modules (6 alerts, all high):
github.com/containerd/containerd/v2v2.3.1 → v2.3.3 — GHSA-33vj-92qq-66hc, GHSA-rgh6-rfwx-v388, GHSA-xhf5-7wjv-pqxp, GHSA-cvxm-645q-p574, GHSA-jpcc-p29g-p8mqoras.land/oras-go/v2v2.6.1 → v2.6.2 — GHSA-fxhp-mv3v-67qpaws-sdk-go-v2inspector2/ecrpublicbumps (folds in #2605, #2580)NOTICEregenerated viascripts/generate-notice.sh
GitHub Actions (folds in #2699):
actions/setup-pythonv6.3.0,chrnorm/deployment-statusv2.0.4,cloudposse/github-action-pre-commitv4.1.0,github/codeql-actionv4.36.2,hashicorp/setup-packerpin refresh
Website (78 npm alerts, folds in #2690):
- Docusaurus 3.9.2 → 3.10.x plus the rest of the website-group direct-dependency bumps
- New
pnpm.overridesblock pinning every alerted transitive dependency out of its vulnerable range (shell-quotecritical,node-forge,mermaid,dompurify,lodash/lodash-es,ws,minimatch,webpack,serialize-javascript,uuid, and more) — every override is scoped to the affected major version so unrelated majors are untouched - Landing Hero:
react-icons5.6 removedSiAmazonwebservices(Simple Icons trademark cleanup); switched to Font Awesome'sFaAws
CodeQL (11 alerts):
- Fixes all 8
go/allocation-size-overflowalerts by sizing allocations with a singlelen()term instead of alen(a)+len(b)sum (capacity hints only; behavior unchanged):pkg/yaml,pkg/secrets,pkg/merge,pkg/emulator,pkg/ci,internal/exec - The 3
go/weak-sensitive-data-hashingalerts were dismissed on GitHub as false positives: the flagged SHA-256 usage derives cache filenames / dedup keys (content addressing), not password storage
why
- Every push reported dozens of open vulnerabilities/CVEs; this clears the board in one pass
- Dependency alerts auto-close once the default branch's manifests leave the vulnerable ranges; the CodeQL allocation alerts close on the next
mainanalysis
references
- Supersedes and closes: #2690, #2699, #2605, #2580
- Verified:
go build ./..., targeted package tests,atmos lint changed, pre-commit hooks, andcd website && pnpm run buildall pass; every Dependabot alert range was re-checked against the regeneratedgo.mod/pnpm-lock.yamland none still match
[!NOTE]
Zero user-visible behavior change — dependency bumps and allocation-capacity hints only.
feat(workflows): interactive steps run in CI via non-TTY defaults Brian Ojeda (@sgtoj) (#2714)
What
Interactive workflow and custom-command step types (choose, input, confirm, filter, file, write) now fall back to their configured default value when running without a TTY (e.g. in CI) instead of failing with interactive terminal required for step. Without a default, the historical TTY-required error is preserved, so an unattended run never proceeds with an unintended value.
workflows:
build-manifests:
steps:
- name: account
type: choose
prompt: "Account"
options: [dev, prod]
default: !env STACK_ACCOUNT dev # prompts locally; uses env/`dev` in CI
- name: tag
type: input
prompt: "Release tag"
default: !env RELEASE_TAG latest
- type: shell
command: atmos kube build "{{ .steps.account.value }}" --tag "{{ .steps.tag.value }}"Two parts:
- Non-TTY default fallback (
pkg/runner/step) — a sharedBaseHandler.resolveInteractivedecides prompt vs. default vs. error, andBaseHandler.ResolveDefaultcentralizes default resolution. The now-unusedCheckTTYis removed. Because workflows, custom commands, hooks, and the task runner share the step handler registry, one change covers them all. !env/!execin workflow step fields (internal/exec) — workflow stepdefault/prompt/options/placeholderevaluate the context-free!envand!execYAML functions (workflow manifests otherwise leave them as literal!env ...strings; custom commands already resolve them during config load). Stack-dependent functions are intentionally left untouched.
Why
Interactive steps make workflows and custom commands friendly to run by hand but unrunnable in CI, forcing teams to maintain a second, prompt-free copy of the same automation. A configured default is an explicit opt-in to a non-interactive value, so honoring it when there is no TTY is safe. Leaving default unset keeps the guard for steps where a human must choose.
Depends on
Consuming a captured value in a later shell/atmos step ({{ .steps.*.value }}) relies on the workflow command/env: templating fix in #2711 (already merged), which this builds on.
Testing
- Unit tests for each interactive handler: non-TTY returns the default (single/multi/confirm/template-resolved), and still errors without a default.
- Unit tests for
!env/!execfield resolution (and passthrough of plain/template/stack-dependent values), including nested steps. - End-to-end (built binary): a workflow and a custom command both run unattended via
!envdefaults, feed values into ashellstep, and a no-default step still errors (exit 1).
References
- PRD:
docs/prd/interactive-steps-non-tty-defaults.md - Changelog:
website/blog/2026-07-08-interactive-steps-ci-defaults.mdx - Roadmap: Workflows Overhaul milestone in
website/src/data/roadmap.js
Refresh Atmos agent skills for Native CI Erik Osterman (Cloud Posse) (@osterman) (#2493)
what
- Refresh the Atmos agent skills to use current stack naming, dependency, provisioning, auth, and toolchain guidance.
- Replace the old
atmos-gitopsskill withatmos-ci, centered on Native CI, containerized GitHub Actions, OIDC profiles, checks, comments, summaries, affected/all matrices, and direct Atmos commands. - Add an
atmos-aiskill focused on AI providers, MCP configuration/export, Atmos auth in agent workflows, and toolchain-aware MCP usage. - Remove Spacelift promotion from the skill bundle and keep deprecated wrapper actions,
name_pattern, andsettings.depends_ononly as migration warnings.
why
- Agents were recommending stale Atmos syntax and legacy CI patterns.
- Native CI,
dependencies.components,name_template, source provisioning with workdirs, backend provisioning, and Atmos-managed tool dependencies are the preferred guidance. - Terraform/OpenTofu setup actions and legacy Cloud Posse wrapper actions should be discouraged in favor of the Atmos container and toolchain-managed
dependencies.tools.
references
docs: provider-generation default_tags + missing YAML function pages Erik Osterman (Cloud Posse) (@osterman) (#2704)
what
- Add a "Setting Default Tags" section to the Provider Generation page (
components/terraform/providers.mdx), showing how to set the AWS provider'sdefault_tagsvia theproviderssection, populated with!git.repository,!git.sha, and!git.branch. - Add 5 missing YAML function doc pages so they appear in the docs sidebar:
!git.sha,!git.branch,!git.ref,!git.root, and!emulator. - Cross-link the new
!git.*pages from their existing siblings (git.name,git.host,git.owner,git.repository,git.url,repo-root) and from the YAML functions index page. - Fix a pre-existing template bug in
stacks/providers.mdx's dynamic provider example:{{ .stage }}is not a valid template variable and should be{{ .vars.stage }}.
why
- The Provider Generation page had no example showing how to set
default_tags, a common requirement for consistent resource tagging (repository, commit, branch, etc.) across an account. !git.sha,!git.branch,!git.ref,!git.root, and!emulatorare all implemented, working YAML functions, but had no doc page and were therefore invisible in the sidebar.- The
{{ .stage }}bug produced a non-functional example — Atmos's template context only exposes stage/tenant/environment/namespace nested under.vars, never as bare top-level keys.
references
perf(lint): isolate golangci-lint cache and lock per worktree Erik Osterman (Cloud Posse) (@osterman) (#2701)
what
- Point
TMPDIRandGOLANGCI_LINT_CACHEat repo-local dirs inscripts/run-custom-golangci-lint.shso each worktree gets its own golangci-lint lock and cache (GOCACHEstays shared for warm typecheck data); opt out withATMOS_LINT_SHARED_CACHE=1. - Skip rebuilding the
custom-gclbinary inatmos lint custom-gclwhen it is already up to date (staleness guard mirroringlint lintroller/lint gomodcheck). - Drop the redundant standalone
lint lintrollerpass fromatmos lint changed(it already runs as a plugin insidecustom-gcl).
why
- golangci-lint's single-instance lock is a fixed machine-global path (
os.TempDir()/golangci-lint.lock), not insideGOLANGCI_LINT_CACHE, so parallel worktree lints (e.g. rebase storms after a merge) serialized on one lock and a run orphaned by a tool timeout froze every other worktree; relocating the lock via a per-worktreeTMPDIRlets them run in parallel and contains an orphan to its own worktree. - Rebuilding
custom-gcl(clone + compile) on everylint changedwas the single biggest per-run cost during a rebase storm when the binary is identical across worktrees. - Verified empirically: an isolated-
TMPDIRlint ran at ~94% CPU in parallel with another holding the global lock, with its lock file under.golangci-tmp/.
references
- Contributor tooling only (dev lint flow + pre-commit hook); no user-visible change to the Atmos binary — labeled
no-release.
[codex] Add native asciicast recording and demo embeds Erik Osterman (Cloud Posse) (@osterman) (#2672)
what
- Adds Atmos-native asciicast recording, playback, rendering, and cast/workdir step support.
- Adds reproducible cast demo commands, deterministic fixtures, committed website casts, and an asciicast authoring skill.
- Adds website cast playback plus README front matter/example embed support for showing casts on command and example pages.
why
- Makes command demos reproducible, source-controlled, reviewable as plain text, and reusable across docs.
- Lets examples opt into casts without duplicating command-page videos.
- Provides validated demo coverage for Terraform, vendor, describe/list, and AWS emulator workflows.
references
- None.
feat: format-preserving YAML edits + Atmos Version Tracker Erik Osterman (Cloud Posse) (@osterman) (#2664)
what
Format-preserving YAML editing — a shared, format-preserving YAML editing engine in pkg/yaml (built on the yqlib library Atmos already vendors, fed raw bytes) with Get / Set / Delete / Eval / Query / Format plus atomic file wrappers:
- Edits preserve comments, anchors/aliases, Atmos YAML functions (
!terraform.output,!env,!store, …), and Go/Gomplate templates ({{ … }}); a strict guard rejects edits that would alter or expand a YAML anchor. - Three dot-notation command groups on top of the engine, each with a canonical
get|set|delete|format|listinterface and shorthand aliases for the common case:atmos config get|set|delete|format|list <path>— edits the activeatmos.yaml. (No separateconfigsub-namespace — this domain's flat form is the canonical form.)atmos stack config get|set|delete|format|list <path> -s <stack> -c <component>(canonical) /atmos stack get|set|delete|format(alias) — uses provenance to resolve the manifest that actually defines the effective (post-merge) value.listonly exists underconfig: a flatatmos stack listwould collide with the existingatmos list stacks.atmos vendor config get|set|delete|format|list <path>(canonical) /atmos vendor get|set <component> [version](alias) — the alias resolves the component name to itsspec.sources[N].versionpath and delegates to the same engine; the canonical form addresses any path in the manifest.
atmos vendor update/atmos vendor diffon the same engine (check Git sources for newer versions, honoring semver constraints; diff two versions without a local checkout).--typeflag coerces values (string/int/bool/float/null/yaml).- New PRD (
docs/prd/yaml-editing-get-set.md), Docusaurus docs, changelog blog post, and roadmap milestone.
Atmos Version Tracker (#2688) — one catalog of software versions (local tools, GitHub Actions, OCI images, release tags, and other packages), declared under named tracks in atmos.yaml, resolved into a deterministic versions.lock.yaml:
atmos version track add|set|get|list|show|status|update|verify|remove|apply|rendercommands.- Policy-driven updates: strategy caps, cooldown windows, include/exclude rules, prerelease policy.
- SHA/digest pinning writes round-trippable
@<sha> # <version>references. - File managers rewrite GitHub Actions workflows, marker-annotated files, and templates in place;
atmos version track apply --checkacts as a CI drift gate. - New PRD (
docs/prd/atmos-version-management.md), Docusaurus docs, changelog blog post, and roadmap milestone.
Supporting work merged onto this branch:
- Raised unit test coverage across
cmd/stack(46.8%→85.7%),cmd/config(61.7%→87.2%),cmd/vendor(62.1%→86.3%),pkg/yaml(94.4%→96.0%), andpkg/list/renderer(88.7%→92.7%) to close the gaps Codecov flagged on this PR's patch coverage. - Fixed a Windows-only CI test failure (
TestRelativePathForStackDisplayused synthetic paths that aren't recognized as absolute by Go's Windowsfilepath.IsAbs). - Fixed a CodeQL
go/allocation-size-overflowfinding ininternal/exec/utils.goand dismissed the associated alert. - Fixed the "Check Markdown Links" CI job (excluded a known-flaky-but-valid GitHub release URL).
- Fixed the "Version Tracker E2E" CI job's bootstrap:
.atmos.d/build.yamland.atmos.d/dev.yamlused the new!repo-rootYAML tag, which doesn't exist in the pinned olderatmosbootstrap binary CI uses to self-build from source — any occurrence anywhere in.atmos.d/was silently dropping the whole custom-command set. Moved repo-root resolution into the shell layer (git rev-parse --show-toplevel) instead. - Resolved all 13 outstanding CodeRabbit review threads on #2688 (verified each fix against the merged code before resolving).
atmos vendor get|setrefactored from a separate yq-expression implementation into a literal thin wrapper overatmos vendor config get|set(matching howatmos stack get|set|delete|formatalready aliasatmos stack config), and documented the previously-undocumentedatmos stack config */atmos vendor config *command groups (13 new Docusaurus pages) that had been added to the branch after the original docs were written.
why
- Editing Atmos YAML with
sed/yqstrips comments and reformats files; a naive "parse → re-serialize" approach destroys comments, anchors, functions, and templates that carry real meaning and behavior. These commands let users and automation script configuration changes safely, at the YAML-node level, without losing fidelity. The sharedpkg/yamlengine generalizes (and supersedes) the hardcodedsources[].versionwriter from the olderfeat/vendor-diff-and-updatebranch. - Every tool/action/image version an Atmos project depends on is currently either hand-pinned with no drift detection, or left floating. Version Tracker gives teams one declarative, lockfile-backed source of truth with policy-driven, reviewable updates and a CI gate against drift.
- Version Tracker's PR (#2688) was opened against this branch (rather than
main) and merged in directly, so its full history and diff are part of this PR.
references
- PRD (YAML editing):
docs/prd/yaml-editing-get-set.md - PRD (Version Tracker):
docs/prd/atmos-version-management.md - Supersedes the YAML-write approach from the
feat/vendor-diff-and-updatebranch (the broadervendor diff/vendor updatefeature port is tracked as follow-up). - Known limitation: blank lines between entries are not preserved (inherent to the
gopkg.in/yaml.v3node model thatyqlibbuilds on).
Add casts core recording and rendering support Erik Osterman (Cloud Posse) (@osterman) (#2692)
what
- Add casts-core support with
atmos cast play,atmos cast render, and global--castrecording for CLI output, including help output recording. - Add asciicast recorder/playback/rendering packages, terminal/PTY capture plumbing, IO recorder support, and shell writer hooks used by command recording.
- Add workflow/custom-command step support and schemas for
cast,simulate,script, andworkdir, plus config defaults/loading behavior and focused tests. - Include non-cast user-visible fixes discovered while splitting this work, with dedicated fix notes under
docs/fixes.
scope
- This PR is intentionally limited to casts-core implementation, schemas, and related non-cast fixes.
- Full casts documentation, announcement/blog content, demo cast assets/configs, and cast-generation workflows are intentionally omitted from this PR and will be implemented in a follow-up pull request.
- The original
ascii-castbranch remains the source for the follow-up content work and can be rebased after this core PR merges.
non-cast fix notes
docs/fixes/2026-07-06-custom-command-import-merge.mddocs/fixes/2026-07-06-custom-command-include-env.mddocs/fixes/2026-07-06-custom-command-env-map-form.mddocs/fixes/2026-07-06-custom-command-step-execution.mddocs/fixes/2026-07-06-terminal-color-and-force-tty.mddocs/fixes/2026-07-06-help-rendering-without-config.mddocs/fixes/2026-07-06-mask-shell-and-env-output.mddocs/fixes/2026-07-06-chdir-updates-pwd.mddocs/fixes/2026-07-06-local-vendor-source-paths.mddocs/fixes/2026-07-06-step-output-labels.md
why
- Split the reviewable casts-core implementation out of
ascii-castbefore the user-facing documentation and content work lands separately. - Enable recording, playback, rendering, and scripted workflow flows without carrying generated demo/content payloads in this PR.
- Keep the non-cast fixes that are required for the retained custom-command, terminal, env, masking, and workdir behavior.
validation
go test ./cmd/cast ./cmd ./pkg/config ./pkg/schema ./pkg/terminal ./pkg/ui -run 'Test|^$' -count=1COLUMNS=80 GIT_CONFIG_GLOBAL="$PWD/.context/gitconfig-test" go test ./tests -run 'TestCLICommands/(indentation|Invalid_Log_Level_in_Config_File|Invalid_Log_Level_in_Environment_Variable|secrets-masking_describe_config|atmos_toolchain_--help|atmos_toolchain_install_--help|atmos_toolchain_info_--help|atmos_validate_component_failure_with_UI_output|atmos_workflow_shell_command_not_found|atmos_workflow_failure|atmos_workflow_failure_on_shell_command|atmos_workflow_invalid_step_type|atmos_workflow_invalid_from_step|atmos_workflow_invalid_manifest)$' -count=1go test ./cmd/...go test ./pkg/asciicast ./pkg/io ./pkg/terminal/... ./pkg/runner/step ./pkg/process ./pkg/schema ./pkg/configgo test ./cmd/cast -count=1git diff --check -- docs/fixes
Add topic-specific CLI help Erik Osterman (Cloud Posse) (@osterman) (#2696)
what
- Add topic-specific help modes for
--help=usage,--help=flags, and--help=all. - Keep default help focused on command-specific flags while preserving full global flag output behind
--help=all. - Add tests, a PRD, and a blog post announcing the help improvement.
why
- Help pages had become noisy because global flags buried command-specific options.
- Focused help topics make usage examples, command flags, and full reference output easier to discover.
- Validated with
go test ./cmd -count=1,go test ./internal/tui/templates -count=1, and commit/push hooks.
references
- n/a
Add configurable CI log grouping Erik Osterman (Cloud Posse) (@osterman) (#2669)
## what- Add provider-agnostic CI log grouping with step, phase, and invocation dimensions.
- Implement GitHub Actions
::group::/::endgroup::support, grouping mode config, and nested Atmos suppression. - Wrap workflow/custom-command steps, terraform/tofu phases, and optional whole-command invocations in collapsible groups.
- Document the feature in the native CI PRD, user docs, roadmap, and changelog.
why
- CI logs from Atmos workflows and Terraform runs are hard to scan when all output is flat.
- A single
ci.groups.modeavoids unsupported nested groups while letting users choose step/phase grouping or whole invocation grouping. - Invocation labels omit flags and flag values so grouped CI logs stay readable and avoid exposing noisy or sensitive CLI values.
references
- GitHub Actions workflow command groups: https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands#grouping-log-lines
feat: add validation for unsupported YAML tags Erik Osterman (Cloud Posse) (@osterman) (#1515)
what
- Add validation to detect and error on unsupported YAML tags instead of silently ignoring them
- Create comprehensive list of all supported YAML tags for validation
- Provide clear error messages that include the unsupported tag, file location, and list of supported tags
why
Currently, when Atmos encounters an unsupported YAML tag (like !invalid or typos like !envv instead of !env), it silently strips the tag and keeps the value. This makes it very difficult to track down configuration errors and typos in YAML files.
By explicitly validating tags and providing clear error messages, we:
- Help users catch typos and invalid tags early
- Prevent silent configuration failures
- Improve debugging experience with clear error messages
- Make the system more reliable and predictable
Example Error Messages
When an unsupported tag is encountered:
unsupported YAML tag '!invalid' found in file '/path/to/stack.yaml'.
Supported tags are: !exec, !store, !store.get, !template, !terraform.output,
!terraform.state, !env, !include, !include.raw, !repo-root
When there's a typo in a tag:
unsupported YAML tag '!envv' found in file '/path/to/stack.yaml'.
Supported tags are: !exec, !store, !store.get, !template, !terraform.output,
!terraform.state, !env, !include, !include.raw, !repo-root
Test Results
- Added comprehensive unit tests for unsupported tag detection
- Tests verify that unsupported tags trigger errors
- Tests verify that valid tags continue to work correctly
- Tests verify error message content and clarity
references
- Closes DEV-2986
- Linear issue: https://linear.app/cloudposse/issue/DEV-2986/atmos-should-error-for-unsupported-yaml-functions
Summary by CodeRabbit
-
New Features
- Added stricter custom YAML tag validation: unsupported tags now fail fast with an error that lists supported tags.
- Built-in YAML tags (
!!*) are ignored by custom-tag validation to avoid false positives. - Updated supported YAML tag set used for validation and messaging.
-
Tests
- Expanded test coverage for supported/unsupported tags, nested YAML traversal, edge cases, and error-message details.
- Added additional YAML fixtures to verify error handling.
-
Chores
- Updated test command wiring and CI/Make targets to use a unified
acceptancemode with--cover. - Refreshed related docs/blog examples to match the new command surface.
- Updated test command wiring and CI/Make targets to use a unified
feat: YAML key delimiter for dot notation in stack files Erik Osterman (Cloud Posse) (@osterman) (#2139)
what
- Implement configurable
settings.yaml.key_delimiterthat expands dotted YAML keys into nested maps in stack files (e.g.,metadata.component: vpc-base->metadata: { component: vpc-base }) - Unquoted keys containing the delimiter expand automatically; quoted keys stay literal (escape mechanism)
- Support custom delimiters, not just
.(e.g.,::) - Mark feature as experimental with runtime notifications via
ui.Experimental() - 21 comprehensive unit tests covering expansion logic, merging, conflicts, quoted preservation, edge cases
example
With settings.yaml.key_delimiter enabled:
settings:
yaml:
key_delimiter: "."Stack files can use concise unquoted dotted keys for nested component configuration. metadata stays at the component level; it is not nested under vars:
components:
terraform:
vpc:
metadata.component: vpc-base
metadata.description: Base VPC component
vars.tags.environment: prod
vars.tags.team: networkAtmos expands that as if the stack had been written in nested form:
components:
terraform:
vpc:
metadata:
component: vpc-base
description: Base VPC component
vars:
tags:
environment: prod
team: networkQuoted mapping keys remain literal, so existing dotted keys can be preserved:
components:
terraform:
app:
vars:
"output.json": true
'config.file': /etc/app.confwhy
Documentation claimed dot notation worked in stack files but it didn't. Go's yaml.v3 treats dotted keys literally without expansion. Users want concise notation instead of deeply nested YAML structures. This brings feature parity with atmos.yaml's native support via Viper's deepSearch() algorithm. Backwards compatible (disabled by default) and opt-in.
references
Extends experimental features system (like atmos devcontainer, atmos list affected). Respects settings.experimental mode configuration (silence/warn/error/disable).
Summary by CodeRabbit
- New Features
- Experimental YAML key expansion (
settings.yaml.key_delimiter): unquoted dotted keys can expand into nested maps in stack and settings files; custom delimiters are supported. - Enhanced experimental configuration checks so YAML/settings features follow the same warn/disable/error modes.
- Experimental YAML key expansion (
- Documentation
- Added a blog post and expanded YAML dot-notation reference with examples for quoting, custom delimiters, and edge cases.
- Tests
- Added coverage for delimiter expansion behavior and merge/conflict scenarios.
- Chores
- Updated the Playwright-Go license reference.
Refactor Make workflows into Atmos commands Erik Osterman (Cloud Posse) (@osterman) (#2677)
what
- Moves build, lint, test, screengrab, check, format, and cache tasks from Make into grouped
.atmos.dAtmos commands, withatmos buildas the default binary build command. - Converts Makefiles to migration guidance, updates CI/docs to call Atmos commands, adds setup-atmos bootstrapping, and adds Linux/macOS/Windows install script smoke coverage.
- Cleans example commands from root
atmos.yaml, keeps.tool-versionsinstalls explicit toatmos toolchain install, addsatmos shell, and adds a blog post embedding live.atmos.dexamples. - Fixes custom-command dogfooding gaps for
defaultsubcommands,.atmos.dcommand-list merging across files with YAML tags,.cwdtemplate data, and local alias precedence over config custom commands.
why
- Dogfoods Atmos custom commands for Atmos development while removing Make from active local and CI workflows.
- Keeps command definitions grouped and reusable while preserving existing build, lint, test, screengrab, and devcontainer alias behavior.
references
- Validation:
go test ./cmd ./pkg/config ./pkg/schema,git commithooks,./build/atmos build,./build/atmos build --target linux,./build/atmos build version --target linux,./build/atmos screengrabs build --all,./build/atmos screengrabs build about, Makefile migration checks,atmos build deps,pnpm build. - Note:
atmos test shortstill reports unrelated workspace failures ininternal/tui/utilsandpkg/utils.
Add CEL-backed when conditions Erik Osterman (Cloud Posse) (@osterman) (#2689)
what
- Add
pkg/conditionto ownwhenparsing, predicate handling, CEL compilation/evaluation, runtime context, and tests. - Keep
pkg/schemacompatible through aliases while wiring CEL-awarewhenevaluation into workflow steps, custom command steps, and hooks. - Update YAML tag normalization, JSON schemas, validation tests, and docs so bare CEL strings and explicit
!celconditions are supported.
why
- This lets users write expressive, sandboxed boolean
whenexpressions without replacing the existing predicate keywords. - Keeping schema aliases preserves current call sites while allowing condition behavior to evolve in a focused package.
- Hook implicit-success behavior and workflow/custom-command failure predicate restrictions remain intact.
example
workflows:
deploy:
steps:
- name: plan-prod
type: shell
command: terraform plan
when: !cel 'ci && stack == "prod" && status == "success"'references
- Validation:
go test ./pkg/condition ./pkg/schema ./pkg/workflow ./pkg/hooks ./pkg/datafetcher;go test ./cmd -run TestCustomCommandIntegration_SkipsStepWhenConditionIsFalse;go test ./pkg/workflow/... ./pkg/hooks/...;./custom-gcl run --new-from-rev=origin/main.
feat(ci): fork-PR safety gate for `atmos git clone` Erik Osterman (Cloud Posse) (@osterman) (#2661)
what
atmos git clone(Atmos's nativeactions/checkoutreplacement) now refuses by default to clone untrusted fork content under GitHub's elevatedpull_request_targetandworkflow_runevents.- The gate triggers only on the dangerous combination — an elevated event plus a fork-targeting clone (a PR head/merge ref override like
refs/pull/<N>/merge, or an ad hoc clone URI whoseowner/repodiffers from the baseGITHUB_REPOSITORY). Safe no-arg base checkouts and low-privilegepull_request/push/merge_groupevents are unaffected. - Adds a grep-able opt-in:
--allow-unsafe-forkflag,ATMOS_ALLOW_UNSAFE_FORK_EXECUTIONenv var, andci.allow_unsafe_fork_executionconfig key. - Adds the provider-agnostic gate core (
pkg/ci.EvaluateForkCheckout), aContext.ElevatedEventtrust signal set by the GitHub provider, theErrUnsafeForkCheckoutsentinel, and unit tests (incl. negative paths). - Documents it: new PRD
docs/prd/native-ci/framework/fork-pr-trust-gate.md,atmos git cloneCLI docs, CI configuration reference, and a changelog blog post + roadmap milestone.
why
- Because
atmos git clonefills the same role asactions/checkout, it inherited the same "pwn request" risk: cloning a fork's PR code in a job that holds the base repository's secrets/GITHUB_TOKEN/cloud credentials lets a malicious contributor exfiltrate them. - This mirrors the
actions/checkoutv7 hardening (fail-closed by default, explicit grep-able opt-out), bringing Atmos's checkout replacement to parity.
references
- actions/checkout v7 changelog: https://github.blog/changelog/2026-06-18-safer-pull_request_target-defaults-for-github-actions-checkout/
- PRD:
docs/prd/native-ci/framework/fork-pr-trust-gate.md - Docs:
/cli/commands/git/clone#fork-pr-safety-gate
fix(algolia): surface page Intro as search-result subtext Erik Osterman (Cloud Posse) (@osterman) (#2429)
what
- Reorder Algolia
customRankingto[desc(weight.pageRank), asc(weight.position), desc(weight.level)]so the first content record on a page wins the per-URLdistinctdedup. - Broaden the content selector to lead with
article .introso every page's<Intro>block is indexed even when MDX doesn't wrap its children in a<p>. - Update crawler unit-test assertions for the new selector and ranking; add a live-relevance regression test (gated on
ALGOLIA_LIVE_RELEVANCE_TESTS) asserting the topatmos authhit has a non-empty_snippetResult.content.
why
- Queries matching page titles (e.g.
atmos auth) were resolving tolvl1records, which the DocSearch React UI renders title-only with no snippet — so search results looked like a bare list with no description text. - With
desc(weight.level)ahead ofasc(weight.position), heading records always outranked content records insidedistinct, locking in the no-subtext outcome. The reorder lets the Intro content record (position 0) win the dedup so its snippet renders as subtext. - MDX v3 emits
<Intro>foo</Intro>with no blank lines as a bare text node inside<div class="intro">, so the priorarticle p, …selector missed the Intro text entirely. Addingarticle .introindexes it regardless of MDX paragraph wrapping.
references
- Requires a recrawl for the selector change to take effect (
npm run algolia:deploy); the ranking change applies as soon asinitialIndexSettingsis pushed.
Add native container image CI summaries Erik Osterman (Cloud Posse) (@osterman) (#2682)
what
- Add Atmos-native Markdown summaries for container image build and push operations, including rich inspected image metadata, digest selection, runtime/env/label/layer details, and raw inspect JSON.
- Wire summaries into workflow container steps and
atmos container build/pushcomponent commands behind existingci.enabledandci.summary.enabledcontrols. - Add unit coverage plus a GitHub Actions container-step assertion that verifies the job summary is produced in CI.
why
- Lets native Atmos container CI produce the same polished image summary without requiring an additional GitHub Action.
- Makes pushed image digests and runtime metadata visible directly in CI job summaries while keeping inspect/write failures best-effort.
references
🚀 Enhancements
Fix terraform backend subcommands not recognizing `--stack` @[copilot-swe-agent[bot]](https://github.com/apps/copilot-swe-agent) (#2508)
`atmos terraform backend` subcommands were rejecting invocations with `--stack` as “required flag not provided,” even when the flag was explicitly passed. This occurred because backend command parsing relied on parsed positional args/Viper state that did not consistently reflect subcommand CLI flag values.-
Root cause and behavior correction
- Backend subcommands now resolve effective flag values from the command’s changed Cobra flags (local/inherited) before fallback sources.
- This prevents
--stack(and related flags) from being dropped during backend subcommand execution.
-
Backend command updates
- Updated
create,update,delete,describe, andlistRunEhandlers to:- bind flags to Viper at execution time, and
- use effective resolved values for downstream backend operations.
- Updated
-
Shared flag-resolution helper
- Added
cmd/terraform/backend/flag_values.gowith reusable helpers to read changed string/bool flags from local/inherited command flags, enabling consistent behavior across backend subcommands.
- Added
-
Regression coverage
- Added a focused backend command regression test ensuring subcommands no longer fail with missing-stack errors when
--stackis provided and that the resolved value flows into backend config initialization.
- Added a focused backend command regression test ensuring subcommands no longer fail with missing-stack errors when
stack := getCommandFlagString(cmd, "stack")
if stack == "" {
stack = v.GetString("stack")
}Summary by CodeRabbit
- New Features
- Added configurable
--error-modehandling for list and describe commands, supportingstrict,warn, andsilent. - Recoverable backend resolution errors can now continue processing and display
(computed)values with optional summaries. - Added a backend provisioning example demonstrating Terraform backend create, update, and delete workflows using a local emulator.
- Added configurable
- Bug Fixes
- Improved command-line flag precedence over configuration and environment settings.
- Documentation
- Added guidance, examples, and interactive demonstrations for graceful degradation and backend provisioning.
fix(toolchain): install multi-file (onedir) packages completely (aws-cli, node) Brian Ojeda (@sgtoj) (#2750)
what
Fixes atmos toolchain install for multi-file ("onedir") packages — tools that ship a binary alongside runtime siblings (a bundled language runtime, shared libraries, node_modules).
- Adds an onedir gate: when an extracted archive contains files beyond the declared
files[].srcentrypoints (ignoring LICENSE/README/docs), Atmos now preserves the complete archive tree under<versionDir>/.pkgand exposes each entrypoint as a symlink into it. Single-binary tools keep the existing flat layout unchanged. - Recreates tar/zip symlink (and tar hard-link) entries instead of dropping them as "unknown type", with lexical target validation to prevent path escapes.
- Makes the download version fallback authoritative: the effective (prefix-toggled) version that actually downloaded is used to render
files[].src, so a pinnednodejs/node@24.18.0resolves to the publishedv24.18.0archive path. - Uninstall now removes the whole version directory; a failed install cleans up its partial version dir instead of orphaning a binary that fools
FindBinaryPath.
why
The installer cherry-picked only the registry files[].src entrypoints out of the extracted archive and discarded everything else. That works for single self-contained binaries (jq, terraform) but breaks bundles:
aws/aws-cli(#2743): kept onlyaws+aws_completer, dropped the bundled Python runtime (~8,600 files incl.libpython). The install reported success, butaws --versioncrashed withFailed to load Python shared library.nodejs/node(#2744):{{.Version}}rendered without the leadingvso the archive directory never matched, andnpm/npx/corepacksymlink entrypoints were skipped — the install failed outright.
This mirrors how the upstream aqua CLI installs the same packages (preserve the tree, link the entrypoints).
verification
Verified end to end with the built binary (both issues' exact repro):
- aws-cli (linux/amd64, ubuntu:24.04):
atmos toolchain install aws/aws-cli@2.35.15→aws --versionprintsaws-cli/2.35.15 Python/3.14.5 ...(direct and viatoolchain envPATH). - nodejs/node (darwin/arm64):
atmos toolchain install nodejs/node@24.18.0→node,npm,npxall run.
Tests: new unit coverage for the gate, symlink/hard-link recreation (tar + zip), onedir installs for both shapes, tree-aware uninstall, cleanup-on-failure, and the effective-version fallback; a characterization test pins the unchanged flat layout for single-binary tools. pkg/toolchain/installer coverage 80.5% → 84.8%.
references
Summary by CodeRabbit
- New Features
- Multi-file (“onedir”) toolchains now preserve the full extracted archive under version-specific
.pkgand use a sidecar manifest to resolve real entrypoints. - PATH construction includes all distinct onedir entrypoint directories.
- Added
GetBinaryPathsto list all installed onedir entrypoint paths for a version.
- Multi-file (“onedir”) toolchains now preserve the full extracted archive under version-specific
- Bug Fixes
- Improved version fallback to return the effective version/URL and clearer combined “asset not found” errors.
- Safer ZIP/TAR.GZ extraction and improved install/extract rollback, cleanup, and uninstall removal of the entire version directory.
- Updated entrypoint resolution and Windows
.exebehavior.
- Documentation
- Added CLI docs and a blog post explaining on-disk layout and Windows behavior.
fix(hooks): user-defined hooks now fire under bulk dispatch Erik Osterman (Cloud Posse) (@osterman) (#2736)
what
- Component
hooks:(the user-definedhooks.RunAllengine) now fire foratmos terraform apply/plan/deployunder--all,--affected,--components, and--query— previously they silently never ran in these bulk modes, only for single-component invocations. - Adds a
schema.ComponentNodeHooksinterface (Before/After) wired into the scheduler'sTerraformDispatcher.Dispatch— the one choke point shared by every bulk dispatch path — via a newpkg/hooks.RunPerComponentHookshelper. - A before-hook failure now aborts that component's execution before Terraform ever runs; an after-hook failure fails that component even if Terraform itself succeeded. Both respect each hook's existing
on_failure: fail|warn|ignoresetting rather than introducing new failure semantics. - Helmfile gets the same
ComponentNodeHookswiring, which is actually its first user-defined hook support of any kind (it previously had none, single- or multi-component). - A second commit removes ~2,260 net lines of confirmed-dead legacy sequential executor code and its now-orphaned tests (
terraform_executor.go,terraform_affected_graph.go, the associated_test.gofiles, and orphaned helpers interraform_utils.go/terraform_affected.go) that predated the scheduler-based dispatch path and had no live (non-test) callers left.
why
- A
before.terraform.applyhook (e.g. zipping a Lambda handler before apply) worked correctly underatmos terraform apply <component> -s <stack>but silently never ran underatmos terraform deploy --all -s <stack>for the identical component/stack/hook config — the subsequent apply then failed because the hook's output never existed. This is exactly how CI normally invokes Atmos, so any hook-dependent component broke the moment it moved from manual single-component testing to a real pipeline run. - Root cause: bulk dispatch only ever wired a CI-reporting callback (
hooks.RunCIHooks), never the user-defined hook engine (hooks.RunAll), and had no "before" seam of any kind — the code itself documented this as a known, unfixed limitation. - The dead-code removal is split into its own commit so the behavior change stays independently bisectable from the pure deletion; both were confirmed dead via call-graph analysis before removal (only test-file callers remained).
references
- N/A
fix(version-tracker): default track update/lock/status/diff to a table Erik Osterman (Cloud Posse) (@osterman) (#2698)
what
atmos version track update,lock,status, anddiffnow default to a human-readable, TTY-aware table (matchingatmos version track list), instead of raw YAML.--format=yaml/--format=jsonremain available as explicit opt-ins and continue to return the original full-fidelity struct (e.g. digests, ecosystem/datasource detail) unchanged.--format=csv/--format=tsvare now also supported for these four verbs, reusing the samepkg/list/rendererpipeline already used byversion track listandsecret list.lock's default table is scoped to the resolved track's entries (not the entire multi-track lock file);--format=yaml/jsonstill dump the fullversions.lock.yamlcontents.- Updated
website/docs/cli/commands/version/track/{update,status,lock,diff}.mdxandwebsite/blog/2026-07-04-atmos-version-tracker.mdx(the source of a screenshot that surfaced this bug) to show the corrected default output. - Added test coverage in
cmd/version/track/track_test.gofor the new default-format dispatch and for full-fidelity yaml/json output.
why
atmos version track update(and its siblings) were printing raw YAML by default, which violates this project's UX convention that CLI output should be human-readable by default, with structured formats as an explicit opt-in.atmos version track listalready got this right;update/lock/status/diffshared awriteFormatted()helper that had no table option at all.
references
fix(git): support self-hosted Git hosts in describe affected dp89 (#2543)
What
Self-hosted Git hosts (GitHub Enterprise Server, GitLab self-managed, Bitbucket Server, etc.) currently fail atmos describe affected with:
Error: repository host '<host>' not supported
The error originates in GetRepoInfo (pkg/git/git.go) and the --upload path in internal/exec/describe_affected.go. Both call into kubescape/go-git-url, which only recognizes github.com, gitlab.com, and Azure DevOps. Any other host is rejected before atmos's local diff logic gets a chance to run — even though the parsed RepoHost / RepoOwner / RepoName metadata is only consumed by the --upload to Atmos Pro, not by the local affected-stack computation itself.
This PR adds ParseGenericGitURL, a fallback parser covering the URL shapes Git itself supports (http(s)://, ssh://, and scp-style). It's wired through a small shared helper, ParseRepoURL, used by both call sites:
- Try the canonical parser first.
- If it fails, fall back to the generic parser.
- If both fail (a genuinely malformed URL like
not-a-valid-url), surface the canonical error so existing error semantics are preserved.
Why
atmos describe affectedis the documented mechanism for matrix-fan-out CI plans, but it's unusable on self-hosted Git today — even though the feature doesn't actually need the parsed metadata.- Other users have hit the same limitation (see the SweetOps thread under References), but it isn't tracked as an upstream issue, and there's no config option, env var, or flag to bypass the parser.
- The fallback only executes when the canonical parser fails, so every existing code path is byte-for-byte unchanged for
github.com/gitlab.com/ Azure DevOps users. No new config, no new env var, no new flag, and no new dependency — the fallback uses onlynet/urlandregexpfrom the standard library.
Scope of behavior change
| URL type | Before | After |
|---|---|---|
| github.com / gitlab.com / Azure DevOps | ✅ works | ✅ works (same path) |
| gitlab.example.com (kubescape substring match) | ✅ works | ✅ works (same path) |
| GHES / GitLab self-managed / Bitbucket Server | ❌ errors | ✅ works (fallback) |
| Genuinely malformed (not-a-valid-url) | ❌ errors | ❌ errors (preserved) |
All six pre-existing TestGetRepoInfo cases continue to pass without modification.
Test plan
go test -count=1 ./pkg/git/...
go vet ./pkg/git/... ./internal/exec/...
gofmt -l pkg/git/git.go pkg/git/git_test.go internal/exec/describe_affected.go
All three pass cleanly. Four new regression tests in pkg/git/git_test.go cover the shapes the canonical parser rejects:
- GHES over SSH
- GHES over HTTPS
- Nested-path HTTPS on a non-recognized host
ssh://with an explicit port
References
- SweetOps thread reporting the same error: https://www.linen.dev/s/sweetops/t/26768542/i-am-trying-to-run-atmos-describe-affected-using-a-self-host
- Affected call sites:
pkg/git/git.go—GetRepoInfo(used byinternal/exec/describe_affected_helpers.goandinternal/exec/pro.go)internal/exec/describe_affected.go—uploadableQuery(the--uploadpath to Atmos Pro)
fix(io): migrate fmt.Fprintf/Println anti-patterns to pkg/ui, pkg/data Erik Osterman (Cloud Posse) (@osterman) (#2713)
what
- Migrate every remaining raw
fmt.Fprintf/fmt.Fprintln/fmt.Printlncall outsidepkg/io/pkg/uito the sanctionedui.*/data.*API across ~40 files (AI permission prompts, devcontainer/kubeconfig debug diagnostics, CI annotations/log groups, the Cobra root command,pkg/manifest,pkg/runner/stepcast recording, and more). - Delete the pure print-forwarding shims in
pkg/utils(PrintMessage,PrintfMessageToTUI,PrintfMarkdown*) and migrate all ~50 call sites directly ontodata.*/ui.*, consolidating onto the already-establishedui.Markdown/ui.MarkdownMessagerenderer instead of a second, bespoke one. - Add
data.WriteUnmasked/WriteUnmaskedf, an explicit escape hatch for the one legitimate case where masking must not apply (atmos auth env/atmos envemitting real credential values for shell eval). - Add
ui.MarkdownNoWrap/MarkdownMessageNoWrapfor deterministic, snapshot-stable single-line notices (used by the telemetry disclosure). - Fix
RootCmd's default output writer to route through the masked I/O context instead of rawos.Stdout, and extract the oversized Cobra help/usage closures into named helpers while there. - Correct
pkg/devcontainer'sShowConfig/Listoutput to the data channel (primary command output) instead of the UI channel, and update the affected CLI golden snapshots to match.
why
- CLAUDE.md bans raw
fmt.Fprintf/fmt.Printlnoutsidepkg/io/pkg/uibecause it bypasses secret masking, TTY/color degradation, and--castrecording. Several call sites found in this audit (AI permission prompts, devcontainer/kubeconfig debug output, CI annotations, the Cobra help writer) genuinely bypassed masking entirely. - The
pkg/utilsprint helpers were pure forwarding shims duplicatingpkg/ui/pkg/data, adding an unnecessary second code path for callers to reason about; deleting them and routing directly through the canonical API reducespkg/utils's surface area per this repo's ongoing effort to empty that package out. atmos auth env/atmos envneed one legitimate, explicit way to emit unmasked output (the whole point of those commands is exporting real credentials foreval), so a dedicatedWriteUnmaskedAPI makes that intent explicit instead of ad hocfmt.Printcalls.
references
fix: run parallel/matrix, shell, and exec steps through the step registry Erik Osterman (Cloud Posse) (@osterman) (#2703)
what
- Wire
parallelandmatrixcontrol steps into thepkg/runner/stepregistry through a reverse-registeredControlRunnerseam (mirroring the emulator seam), so they execute from custom commands and lifecycle hooks, not onlyatmos workflow. Interactive (TTY) child steps are rejected, since they cannot run concurrently. - Register the
execstep type in the registry sotype: execworks outside the legacy workflow executor too. - Run the registry shell handler and parallel/matrix shell children through the in-process
mvdan/shinterpreter with secret masking, replacing a hostsh -cinvocation; thread acontextso fail-fast cancellation reaches running scripts. - Delete the dormant, duplicate workflow
Executorengine and its adapters/mocks (zero production callers), preserving the still-live helpers that were co-located in those files (BuildConditionContext,CheckAndGenerateWorkflowStepNames).
why
parallel,matrix, andexecwere recognized/validated step types whose handlers only ever worked insideatmos workflow; through the registry (hooks, custom commands) they returned a "requires workflow executor context" error. This closes that registry gap.- The registry shell path shelled out to the host
sh -c, so it applied no secret masking (a leak risk) and broke on Windows. Unifying on the samemvdan/shinterpreter the workflow already used makes every shell step masked, cross-platform, and exit-code-correct. - Two execution engines existed side by side; the newer
pkg/workflow.Executorhad no production callers and was pure split-brain maintenance risk. Removing it (~6.8k lines) leaves one dispatcher.
references
- Branch:
osterman/parallel-step-registry-gap
fix(downloader): authenticate !include GitHub raw-content fetches Erik Osterman (Cloud Posse) (@osterman) (#2706)
what
!include https://raw.githubusercontent.com/...(and GitHub archive/release URLs) now authenticate with a GitHub token when one is available (GITHUB_TOKEN/ATMOS_GITHUB_TOKEN/ATMOS_PRO_GITHUB_TOKEN/gh auth token), reusing Atmos's existingpkg/http.GitHubAuthenticatedTransport(already used by the Terraform registry cache and toolchain downloaders).- Added
(*http.DefaultClient).HTTPClient()so callers needing the concrete*http.Client(likego-getter'sHttpGetter.Clientfield) can get one without duplicating transport/redirect-stripping setup. - Non-GitHub HTTP(S) sources, and GitHub sources with no token configured, are unaffected — behavior only changes when both conditions (GitHub host + token present) are true.
why
- The
!includeYAML function's HTTP(S) downloads went out fully unauthenticated via go-getter's stockHttpGetter, subject to GitHub's much lower anonymous per-IP rate limit onraw.githubusercontent.com, regardless of whether a token was available. pkg/downloader/file_downloader.goalready pre-checks GitHub's rate limit using an authenticated client before fetching, but that check and the actual download were disconnected — the download itself never carried the token, so the pre-check didn't reflect (or prevent) the limit the real request actually hit.- This surfaced as intermittent
bad response code: 429failures in CI for tests exercising!includeagainst a real GitHub raw URL (e.g.tests/yaml_functions_include_test.go), unrelated to whatever PR happened to be running at the time.
references
- Discovered while investigating unrelated CI flakiness on #2705.
Audit missing casts: fix version-track bug, backfill demo embeds Erik Osterman (Cloud Posse) (@osterman) (#2707)
This branch started as an audit of which examples and changelog posts were missing a recorded cast demo. That audit surfaced a real correctness bug along the way, plus a few rough edges in the supporting cast/secret tooling — all fixed here alongside the actual backfill.what
- Fix a real engine bug: a stack's top-level
version: {track: ...}assertion was silently dropped during stack compilation, so!version <name>/{{ .version.* }}in component vars always resolved against the global default track instead of the stack's own asserted track. - Rename
examples/demo-version-trackertoexamples/version-trackerand add a second, independent story to it: dev/prod tracks across three real ecosystems (toolchain,oci,github-actions) feeding a stack's component vars via!version/{{ .version.* }}, usingatmos version track set/getinstead of ad-hoc file edits. Record and wire in its cast (tracks-and-vars.cast). - Widen the credential-free skip list used by every
atmos secret *subcommand so resolving where to read/write a secret no longer requires sibling components' terraform state or store contents to already exist. - Give session-mode cast recordings a themed default prompt instead of leaking the real shell's
PS1(hostname/cwd), and drop the now-redundant manualPS1override from theinteractive-workflowscast. - Make
quick-start-advanced's!terraform.statecatalog references tolerate not-yet-deployed sibling components (// "pending"fallback), and turn itslist-instancescast into a real emulator-backed deploy/teardown recording. - Backfill cast embeds across ~28 existing changelog blog posts (
<CastPlayer>+ a link to the full example), converting the two posts that used the heavierEmbedExamplebundle to the same lightweight pattern for consistency. - Codify in the
atmos-asciicastandpull-requestskills that a feature's changelog blog post should embed its cast going forward, so this doesn't drift out of date again.
why
- The version-track bug meant the documented "stacks can assert their own track" capability never actually worked — anyone using it got silently wrong versions with no error. Auditing which examples were missing casts surfaced the gap; fixing it properly (with real end-to-end regression coverage, verified to fail without the fix) was safer than shipping an example that demonstrated broken behavior.
- The
atmos secret *andquick-start-advancedfixes remove a class of failures where resolving secrets or listing/describing components required unrelated sibling infrastructure to already be deployed — brittle in CI, emulator, and first-run scenarios. - Blog posts without a working demo undersell what shipped; embedding the existing cast recordings makes the changelog self-verifying and more useful to readers, and the skill update keeps future posts from shipping without one.
references
- N/A
feat(terraform): add --log-order to apply, deploy, and destroy Chris Harden (@cmharden) (#2674)
what
- Add
--log-order stream|groupedtoatmos terraform apply,deploy, anddestroy(previouslyplan-only). - Bring
deployto concurrency parity withapply: add--max-concurrencyand--failure-mode, and adddeploy
to the scheduler's concurrency allowlist so those flags (and grouped logs) actually take effect. - Rename the internal plan-specific log-order identifiers to generic (
PlanLogOrder→LogOrder,
TerraformPlanLogOrder→TerraformLogOrder, consts dropPlan). - New env vars:
ATMOS_TERRAFORM_{APPLY,DEPLOY,DESTROY}_LOG_ORDER, plus deploy's
ATMOS_TERRAFORM_DEPLOY_{MAX_CONCURRENCY,FAILURE_MODE}. - Docs updated for the three commands.
why
plancould order concurrent per-component logs, butapply/deploy/destroycould not —atmos terraform apply --all --log-order groupedfailed with "unknown flag". Grouped output (contiguous per-component blocks) is what
makes concurrent--allruns debuggable.- The scheduler adapter already buffered grouped output for any subcommand; only the flag exposure — and deploy's
missing allowlist entry — stood in the way. Renaming the plan-specific identifiers reflects that this is a
scheduler-level concern shared by four commands.
notes
- Backward compatible: the
--log-orderflag name,stream/groupedvalues, andATMOS_TERRAFORM_PLAN_LOG_ORDER
are unchanged; the renamed field is internal/untagged. --log-order groupedengages only under--max-concurrency > 1(matchesplan).- Happy to add a changelog entry / roadmap update in your preferred format if you'd like — left those to maintainer
discretion.
fix(auth): atmos.Component() nested lookups could lose/reuse wrong identity Erik Osterman (Cloud Posse) (@osterman) (#2712)
what
- Fixes
internal/exec/terraform_nested_auth_helper.goso the nested-auth cache (nestedAuthManagerCache, added in #2652/#2656) never caches a result resolved throughauthContextWrapper— itsGetChain()always returns an empty slice by design, so two different real identities propagated this way could otherwise collide on the same cache key and silently reuse the wrong identity'sAuthManager. - Fixes
internal/exec/template_funcs_component.go(atmos.Component()) so it resolves a nested target's ownauth:section (viaresolveAuthManagerForNestedComponent) instead of always reusing the enclosing component's credentials verbatim — bringing it in line with how!terraform.state/!terraform.outputalready behave. - Adds regression tests for both (
TestBuildComponentAuthCacheKey_AuthContextWrapperNeverCaches,TestResolveComponentFuncAuthManager) and adocs/fixeswrite-up.
why
- A user reported
atmos list instancesregressing from v1.221.0 to v1.222.0 under GitHub Actions/OIDC auth (no ambient AWS credentials): a Go template callingatmos.Component(...)to fetch a nested component's terraform output failed withno valid credential sources found ... no EC2 IMDS role found— meaning the subprocess got zero AWS credential material and fell through to the (nonexistent, off-EC2) instance-metadata provider. - Both defects were introduced or exposed by the per-component/nested auth-caching optimizations added in v1.222.0 (#2652, #2656) and match the same symptom already fixed once this cycle in the terraform-hooks path (
docs/fixes/2026-06-27-store-hook-inherit-default-identity.md). - The user's own component topology (nested target with no
auth:override of its own) isn't fully explained by these two fixes alone; that residual uncertainty and a debug-log diagnostic for further narrowing are documented indocs/fixes/2026-07-09-atmos-component-nested-auth-cache-collision.md.
references
fix(workflows): resolve step vars in inline shell/atmos/exec steps Brian Ojeda (@sgtoj) (#2711)
What
Workflow step types handled inline (shell, atmos, exec) now resolve step-variable templates — {{ .steps.*.value }}, {{ .env.* }}, {{ .flags.* }}, plus Sprig/Gomplate functions — in both the step command and the step env: values, matching how custom command steps already behave.
workflows:
deploy:
steps:
- name: component
type: format
content: "vpc"
- type: atmos
command: terraform apply {{ .steps.component.value }} -auto-approve
env:
COMPONENT: "{{ .steps.component.value }}"Before this change, terraform apply {{ .steps.component.value }} -auto-approve was passed through verbatim (literal {{ .steps.component.value }}); now it resolves to terraform apply vpc -auto-approve.
Why
This is a bug, not a new feature. The documented contract (PRD goal “capture step outputs for use in subsequent steps via Go templates”, and its own examples using type: atmos / type: shell command steps) is that any step can consume prior steps' outputs. Handler-routed step types (toast, markdown, container, the interactive prompts, …) and custom command steps already resolved these templates; workflow shell/atmos/exec steps were the outlier — they executed the raw command and merged env: values without running either through the step-variable engine, so the templates were emitted literally. It went unnoticed because the shipped examples only surface step values through handler-routed display steps, never a raw shell/atmos command.
How
ExecuteWorkflowconfigures the workflow step executor with the same template engine as the custom command executor (cmd/cmd_utils.go): the full Atmos renderer (Sprig/Gomplate), multi-pass rendering, and flag protection — so templating behaves identically in workflows and custom commands.- Inline step commands and
env:values are resolved through that engine before execution. - New
Variables.ResolveWithapplies a per-call environment overlay without mutating the shared executor env, so a step's environment is visible as{{ .env.* }}without leaking across steps. prepareStepEnvironmentstays a pure merger (its existing tests are unchanged); env values are resolved ahead of it.
Commands and env: values without template markers are returned unchanged.
Testing
- Unit tests:
Variables.ResolveWith(overlay + non-mutation), command resolution (.steps+ env overlay, non-leak, Sprig parity, invalid-template error), andenv:resolution. - End-to-end (built binary): a workflow feeding a captured value into a
shellstep's command,env:, and a Sprig function all resolve correctly. go build ./...,pkg/runner/step+internal/execsuites, andatmos lint changedare clean.
References
docs/fixes/2026-07-07-workflow-step-variable-templating.md- Reference implementation mirrored:
cmd/cmd_utils.go(custom command step executor)
fix: correct out-of-date atmos-manifest workflow schema (#2708) Brian Ojeda (@sgtoj) (#2710)
what
- Bring the published
atmos-manifestJSON Schema (served atatmos.toolsand mirrored to SchemaStore) back in sync with the workflow features shipped in1.222.0, so editors using# yaml-language-server: $schema=…stop drawing false-positive errors on valid workflows. workflow_manifest(the object under each named workflow): add the 7 missing workflow-definition fields —dependencies,working_directory,env,container,output,viewport,show(reusing the existingdependenciesdefinition via$ref).workflow_step: add the 75 missing step fields with descriptions, spanning every step-type family (options/default/interactive/tty/script, plushttp,cast,container,style,log,say,env,exit,emulator,junit,require,workdir,table, control/parallel). SetadditionalProperties: trueand makelevela plain string (see why).- Add three shared sub-definitions:
workflow_viewport,workflow_show,workflow_container(the latter also acceptsfalseto run on the host). - Add a reflection-based ratchet test (
schema_workflow_coverage_test.go) that fails the build if aWorkflowStep/WorkflowDefinitionfield is ever authored without a matching schema property — the same "you can't forget the schema" pattern already used for top-level/component sections. - Add regression tests (
schema_workflow_validation_test.go) reproducing the issue's exact snippets, covering one field from every step-type family, all workflow-level fields, and confirming typed fields (e.g.options,code) are still validated.
why
- The issue named three rejected fields (
dependencies,options,default), but the schema had drifted far more broadly: it modeled only 21 of ~96 authored step fields and 3 of 10 workflow-definition fields, both withadditionalProperties: false. Any workflow using a newer field produced misleading "Property X is not allowed" red squiggles on config that Atmos parses and runs correctly. - I validated the updated schema against all 60 real workflow files in the repo. That surfaced two ways the old strict schema over-tightened against shipped examples: an ignored/unknown key, and
level:values outside a log-level set. Workflow steps are a lenient, polymorphic union of ~30 step types and Atmos ignores unknown keys at runtime, soadditionalProperties: true(plus fully enumerated known fields for autocomplete + type checks) matches real behavior and eliminates the whole false-positive class — present and future — mirroring howproviders/templatesare modeled.levelis a plain string field, so it is no longer constrained by an enum. - The ratchet test makes this drift a build failure going forward, so the published schema can't silently fall behind the structs again.
- Scope is intentionally the published (website/SchemaStore) schema; the embedded schemas do not model workflow steps and
atmos validate stacksis unchanged.
references
- Closes #2708
- Follow-ups discovered while validating real workflows (out of scope here, worth separate issues):
examples/quick-start-advanced/stacks/workflows/{backend,validation}.yaml(and the mirrored doc) usecommand: true— an unquoted-YAML-boolean footgun where a string command belongs; the schema correctly flags it.- Dedicated schemas for custom-command files (
commands:) and profile files (auth:) — the issue's "ideally" ask; purely additive (no false-positives today) and a larger separate effort.
fix(ci): exclude auth caches from ci.cache (defensive hardening) Erik Osterman (Cloud Posse) (@osterman) (#2705)
what
ci.cache(the CI build cache) now excludesaws-sso,azure-device-code,aws-webflow, andauth— the subdirectories under~/.cache/atmoswhere Atmos persists session credentials — unconditionally, with no opt-out, regardless ofci.cache.paths.- Applies in both Atmos's own cache backend (
pkg/ci/cache/archive.go) and theatmos ci cache pathsoutput used with the nativeactions/cacheGitHub Action (rendered as!-prefixed glob exclusions, working around a knownactions/toolkitglob-depth limitation). - Drift-guard tests in each owning auth package assert their subdir constants stay in sync with the exclusion list, so a rename can't silently reopen the gap.
- Docs (
website/docs/cli/configuration/ci/cache.mdx,docs/prd/native-ci/framework/ci-cache.md) and an internal fix note (docs/fixes/) are included.
why
ci.cache's default behavior (cache the entire XDG cache root whenci.cache.pathsis unset) meant Atmos's own auth session material (AWS SSO tokens/refresh token/client secret, Azure device-code tokens, Atmos's webflow refresh token) sat in the same directory tree that gets archived by default.- This is purely defensive hardening —
ci.cachewas never demonstrated to be exploitable or vulnerable in CI. A repo's GitHub Actions cache is already scoped to that repo, and everything involved is short-lived, rotating session material. This change just makes it structurally impossible for these directories to end up in a cache archive, rather than relying onci.cache.pathsconventions. - OIDC-based auth (GitHub/GCP/Azure) was confirmed unaffected either way — those flows never write credentials to disk.
references
docs/prd/native-ci/framework/ci-cache.md#default-excluded-auth-paths
fix(website): repair broken /blog announcement link Erik Osterman (Cloud Posse) (@osterman) (#2702)
What
- Fix the broken announcement-bar link:
/blog/native-ci-integration→/changelog/native-ci-integration. - Add a
createRedirectsrule toplugin-client-redirectsso any other stale/blog/<slug>URL (old bookmarks, indexed search results) forwards to its current/changelog/<slug>page, matching the existing bare/blog→/changelogredirect.
Why
The blog plugin's routeBasePath was changed from /blog to changelog a while back (#1707), but the announcement bar (website/src/data/announcements.js) still hardcoded the old /blog/... path, so it 404'd live on atmos.tools.
Neither existing safety net catches this class of bug:
.github/workflows/link-check.yml(lychee) is scoped to**.mdand explicitly excludeswebsite/**.- Docusaurus's
onBrokenLinks: 'throw'only analyzes links it discovers via rendered MDX/<Link>content — the announcement bar renders itscontentstring throughdangerouslySetInnerHTML, which is invisible to that check.
This PR fixes the one known broken instance and adds the redirect as a safety net for any other pre-#1707 /blog/* links floating around externally.
Test plan
-
cd website && npm run buildsucceeds. - Confirmed
build/blog/native-ci-integration/index.htmlcontains a meta-refresh to/changelog/native-ci-integration. - Confirmed 236
/blog/*redirect pages are generated (one per changelog post). - Live-verified
https://atmos.tools/changelog/native-ci-integrationreturns 200 (the corrected target).
fix: deep merge drops keys under unquoted-integer YAML map keys Erik Osterman (Cloud Posse) (@osterman) (#2700)
what
- Fixes
normalizeMapReflectinpkg/merge/merge.goso amap[interface{}]interface{}(the shape yaml.v3 produces for a YAML mapping with an unquoted non-string key, e.g.1:) is stringified intomap[string]anyinstead of being preserved as an opaque typed map. - Adds
pkg/merge/merge_yaml_integer_key_test.go, which reproduces the bug end-to-end via realyaml.v3unmarshaling and unit-tests the normalization contract directly. - Documents the root cause and fix in
docs/fixes/2026-07-07-deep-merge-yaml-integer-key.md.
why
- Since the native deep-merge rewrite (#2201), a nested map whose parent key is an unquoted integer in YAML (e.g.
eni.1:) was silently replaced instead of deep-merged whenever a stack overrides a subset of its keys, dropping catalog-only keys with no error. - Root cause: yaml.v3 only decodes a mapping into
map[string]interface{}when every key resolves to!!str; an unquoted integer key forcesmap[interface{}]interface{}for that mapping node.normalizeMapReflectlumped this together with genuinely-typed non-string-key Go maps (e.g.map[int]schema.Provider) and preserved both, sodeepMergeNative'smap[string]anyfast path saw a type mismatch and fell back to "src overrides dst" for the whole submap. - The previous partial regression fix (#2248) addressed list↔map type-mismatch guards but did not address this map-key normalization gap.
references
- Closes #2376
Migrate devcontainer commands to flag handler Erik Osterman (Cloud Posse) (@osterman) (#2595)
what
- Migrates devcontainer subcommands with positional names or passthrough semantics to
flags.StandardFlagParser. - Centralizes devcontainer name positional arg setup with
flags.NewPositionalArgsBuilder. - Updates
devcontainer execto preferexec <name> -- <command> [args...]while preserving the legacy no-separator form. - Adds parser coverage for required/optional devcontainer names, flag extraction, and
exec --validation.
why
- Keeps upgraded devcontainer commands on the command registry and flag handler path consistently.
- Avoids manual
args[0]/args[1:]handling bypassing positional and separated-arg parsing. - Ensures native command flags after
--are preserved fordevcontainer exec.
references
- Follow-up to #2594.
[codex] Fix CI and native component compatibility regressions Erik Osterman (Cloud Posse) (@osterman) (#2685)
what
- Fix Terraform native CI planfile upload for workdir-provisioned components by resolving the effective
.workdirartifact path. - Fix native Helm plugin binary resolution to use the
helmdependency scope, and expose--cion native Kubernetes operation commands. - Stabilize tests by disabling telemetry in schema dogfood tests, clearing color-disabling env vars in TUI output tests, and ignoring environment-dependent GitHub token debug noise in snapshots.
- Document each fix separately under
docs/fixes. - Fail loudly when
--verify-plan(orATMOS_TERRAFORM_VERIFY_PLAN=true) is set but planfile storage is not configured undercomponents.terraform.planfiles— previously the flag silently no-op'd and deploy applied an unverified fresh plan. A config-setverify:mode without storage now logs a warning. Docs updated everywhere the flag/prerequisite is mentioned, and the missingplanfilessection was added to the terraform configuration reference. - Raise patch test coverage: REST artifact download error paths, workdir artifact-path failure path, and emulator shared-network attachment.
- Fix the Acceptance Tests Playwright failure and broken
aws/samlBrowser-driver installs: playwright-go's driver download hits the retired azureedge.net CDN (build purged from the replacement CDN), so Atmos now pre-seeds the driver from the official npm registry and nodejs.org with checksum verification (docs/fixes/playwright-driver-retired-cdn.md).
why
- These changes address compatibility gaps found during the recent merged-code audit, including issue #2684 where Terraform planfiles could be skipped for auto-provisioned workdir components.
- The test updates remove unrelated environment and network side effects so local and CI verification are deterministic.
references
- Fixes #2684
- Verified with
go test ./cmd/... ./internal/... ./pkg/...andgo test ./tests -count=1.
Support --profile flag for custom commands with autocomplete Erik Osterman (Cloud Posse) (@osterman) (#2190)
what
- Added early parsing of
--profileflag (before Cobra parses) to ensure profiles are applied during initial config load, so custom commands receive a properly profiledatmosConfig - Extended
StringSliceFlagto support custom completion functions - Implemented profile autocomplete that lists available profiles from the profile manager
- Updated
FlagRegistry.SetCompletionFunc()to support bothStringFlagandStringSliceFlagtypes
why
Previously, custom commands could accept --profile but the flag was never applied because atmosConfig was loaded at boot time (before Cobra parsed flags). Custom commands captured this pre-profile config in their closure. Built-in commands worked because they called GetConfigAndStacksInfo() which re-extracted --profile during command execution.
This fix follows the same pattern as --chdir: parse the flag early from os.Args and pass it to InitCliConfig, ensuring all commands (built-in and custom) get properly profiled configuration.
The autocomplete feature provides users with shell suggestions of available profiles when typing atmos commands.
references
Resolves the gap where custom commands couldn't use the --profile flag effectively.
Summary by CodeRabbit
-
New Features
- CLI supports profiles via a global --profile and ATMOS_PROFILE; profiles are loaded at startup and available for shell completion.
- Early config selection via command-line or environment variables (base path, config, config-path).
- Improved shell completion for profile and multi-value flags.
-
Tests
- Added tests for flag completion and for profile/config selection parsing.
fix: replace log.Info/Error with ui.* in vendor non-TTY output Erik Osterman (Cloud Posse) (@osterman) (#2268)
what
- Replaced all
log.Infoandlog.Errorcalls in the vendor non-TTY code path with properui.*methods (ui.Success,ui.Error,ui.Info) - Per-package status lines now use
ui.Successf/ui.Errorfwhich provide consistent icons and styling - Summary and dry-run messages use
ui.Successf/ui.Infoinstead of structured log key-value pairs - Extracted repeated format string into a
pkgStatusFmtconstant
why
- The vendoring non-TTY path was abusing
log.Infofor user-facing status messages, which are UI output not diagnostic logs log.Inforenders as structured key-value pairs (e.g.,INFO package=vpc version=(v1.0)) instead of the clean styled output the TUI provides- Using
ui.*methods ensures consistent formatting with proper icons (checkmarks/X marks) across both TTY and non-TTY environments
references
pkg/ui/formatter.go— UI output methods useddocs/logging.md— logging level guidelines
Summary by CodeRabbit
- Improvements
- Refined vendoring CLI output for non-interactive runs with consistent symbol-based status lines and clearer success/error reporting.
- Improved per-component status messages to clearly distinguish failures vs. successes.
- Updated completion/dry-run summaries to use consistent info/check symbols and explicit success/failed counts.
- Tests
- Updated stderr golden snapshots and matchers for the new formatting.
- Added non-TTY test coverage for per-component and final vendoring status output.
- Chores
- Updated the Playwright-Go license reference in the NOTICE file.
Redesign Atmos CI comment badge Erik Osterman (Cloud Posse) (@osterman) (#2687)
what
- Redesign the Atmos CI comment badge served from the existing
atmos-ci-gradientimage paths as compact GitHub-style light and dark SVGs. - Update Terraform CI plan/apply summaries to render the badge with GitHub's
<picture>/prefers-color-schemeresponsive image pattern. - Preserve the previous alien-head CI lockups under new media-kit asset names and expose both badge and lockup variants in the media kit.
- Refresh CI summary golden files and checked fixtures for the new markup.
why
- Makes Atmos CI branding fit GitHub PR comments better without changing the public comment image URL.
- Lets GitHub choose the correct badge artwork for light and dark themes.
- Keeps the prior CI lockup available for other brand/media-kit uses.
references
- Validated with
pnpm generate:media-kit, SVG render checks,go test ./pkg/ci/plugins/terraform,go test ./tests -run 'Test.*Terraform.*CI|Test.*NativeCI|Test.*PlanCI', andpnpm build. - Commit hooks were retried after building
custom-gcl;golangci-lint --new-from-rev=origin/mainstill reports existing unrelated lint findings outside this change set.
Split help command sections and redesign error formatting Erik Osterman (Cloud Posse) (@osterman) (#2678)
what
- Split help output into
BUILT-IN COMMANDSandCUSTOM COMMANDS, while keeping config aliases in the dedicatedALIASESsection. - Redesign error formatting so explanations render without a heading as styled callouts, and hints render as standalone
💡action lines. - Reclassified clear hint/explanation misuses and regenerated affected CLI snapshots.
why
- Makes command help clearer by separating Atmos built-ins from commands loaded from configuration.
- Makes error output distinguish diagnostic context from remediation steps without noisy section headers.
- Keeps fallback/non-colored help and error paths consistent with the primary renderer.
references
- Validation:
go test ./errors,go test ./cmd, targeted CLI snapshot checks, fullTestCLICommandssnapshot regeneration, and pre-commit hooks.
Fix GitHub artifact signed URL download Erik Osterman (Cloud Posse) (@osterman) (#2683)
what
- Fetch GitHub artifact REST redirect signed blob URLs with the unauthenticated blob client instead of the OAuth-backed GitHub API client.
- Reuse the signed blob status/body handling before zip extraction for both runtime and REST artifact download paths.
- Add regression coverage for REST fallback downloads where a signed blob endpoint rejects requests carrying an
Authorizationheader.
why
- GitHub's REST artifact download endpoint redirects to a signed blob URL that can reject extra GitHub authorization headers and return non-zip XML.
- This fixes completed-run planfile downloads so deploy verification receives the uploaded artifact zip instead of passing an error response to the zip reader.
references
- Closes #2680
Fix native CI dogfood regressions Erik Osterman (Cloud Posse) (@osterman) (#2681)
what
- Fix native CI bootstrap so
atmos git clonecan run before repo-local profile/config files exist, and make the cache action fail fast when Atmos cache metadata is missing. - Add regressions and fixes for local backend
pathstate reads, remote source-provisioned lock persistence, Dockerpython3, Aqua latest lookup fallback, and emulator job-container networking. - Attach emulator containers to the current GitHub job container network with aliases so Terraform can reach emulator endpoints without a nested
docker run --network hostwrapper.
why
- Dogfooding Atmos native CI exposed release gaps across checkout bootstrap, cache setup, Terraform fixture state reads, source-provisioned workdirs, inherited toolchain installs, and emulator endpoint resolution.
- These changes let GitHub Actions run the Atmos image as the job container while still using the host Docker socket for emulator-backed Terraform tests.
references
- Dogfood PR: cloudposse-examples/atmos-native-ci#49