github cloudposse/atmos v1.223.0

4 hours ago
feat(mcp): server management commands, self/atmos-pro presets, and config UX fixes @osterman (#2720)

what

  • Extracts the standalone atmos mcp install command (client-config installer for AI coding assistants) from the stalled osterman/pro-install-cmd branch, leaving all Atmos Pro-specific wiring behind.
  • Adds full MCP server-management commands: atmos mcp add, atmos mcp remove, and atmos mcp uninstall, rounding out the existing list/install/export/status/test set.
    • add/remove only edit mcp.servers in atmos.yaml (the source of truth); install/uninstall push/pull that declared config to/from AI client config files (Claude Code, Cursor, VS Code, Codex, Gemini).
    • Adds built-in self and atmos-pro presets so atmos 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 self preset interactively offers to flip mcp.enabled: true in atmos.yaml if it isn't already, since that preset requires it to function.
    • atmos mcp list/status now nudge users to run atmos mcp add atmos-pro when Atmos Pro is configured but its MCP server hasn't been added yet.
    • atmos mcp install with nothing configured now interactively offers to add+install the self preset instead of just printing a static hint.
  • Adds a new top-level /mcp docs landing page (website/docs/mcp/mcp.mdx) covering both directions — Atmos as an MCP client and as an MCP server — cross-linked from /ai via tabs, matching the existing /pro and /ai landing 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 common mcpServers shape (Zed's context_servers, OpenCode's mcp).
  • Adds a ## Troubleshooting section to the atmos mcp install docs 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 install previously always wrote to ~/.atmos/skills/, so VS Code / GitHub Copilot's .github/skills/ couldn't discover installed skills.
    • Adds --path/ATMOS_AI_SKILL_PATH to 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, mirroring atmos mcp install's detect/--client/--all-clients/interactive-picker UX. atmos ai skill uninstall gained the same --client/--all-clients symmetry to clean up those copies.
    • atmos ai skill install/uninstall with 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 — mirrors atmos mcp install/uninstall's existing "no server names given = act on everything configured" convention rather than adding a separate --all flag.
  • Adds atmos config set/delete/format UX polish:
    • Infers the value's type (bool, int, float, string, yaml) from the Atmos config schema via reflection on yaml/mapstructure struct tags, so atmos config set mcp.enabled true writes 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.
  • Fixes a CLI error-message bug: a leaf command (no subcommands) whose Args validator rejects the wrong number of arguments (e.g. atmos config unset mcp.enabled true against ExactArgs(1)) previously reported the misleading Unknown 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 install can carry the com.apple.quarantine/com.apple.provenance xattrs, 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 a verifier_trust: disabled opt-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-party mcp-proxy-for-aws bridge.

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.yaml and running client-specific commands manually — atmos mcp add self/atmos mcp add atmos-pro collapse that to one command.
  • atmos config set mcp.enabled true silently 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-cmd branch (Atmos Pro-specific --mcp install flag intentionally left behind there).
  • Closes #2628
feat: tags and labels standard for auth, components, and bulk selection @osterman (#2738)

what

  • Adds tags to auth identities/providers, filterable via --tags on atmos auth list/login/logout (composes with --providers/--identities).
  • Adds metadata.tags (list, any-match) and metadata.labels (map, all-match) to components, filterable via atmos list components --tags/--labels.
  • Adds four new no-arg YAML functions — !tags, !labels, !labels.keys, !labels.values — that return the current component's own metadata.tags/metadata.labels, typed, for bridging into vars (e.g. vars.tags: !labels for terraform-null-label-style modules).
  • Adds --tags/--labels bulk-selection filtering to terraform, kubernetes, helm, and container commands, composing with (not replacing) --all/--affected, via a new shared pkg/tags package.
  • Updates the examples/demo-auth and examples/quick-start-advanced examples and their casts to demonstrate the new tags/labels filtering.

why

  • Implements the docs/prd/tags-and-labels-standard.md design (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 adding metadata.labels + a --selector flag to list/describe commands) — this PR is broader in scope (adds tags, auth tags, YAML functions, and --tags/--labels on terraform/kubernetes/helm/container) but shares the metadata.labels foundation.
feat(claude): add changelog and fix-log skills, convert roadmap agent @osterman (#2751)

what

  • Adds a changelog skill 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 across CLAUDE.md, the pull-request skill, and the docs skill.
  • Adds a fix-log skill (with a validate-fix-doc.sh script) for durable fix records under docs/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.md into .claude/skills/roadmap/SKILL.md, invoked via Skill instead of Agent.
  • Trims CLAUDE.md, pull-request/SKILL.md, and docs/SKILL.md to short pointers at the new changelog/roadmap skills instead of embedding or duplicating the rules.

why

  • Blog-post guidance had drifted into three places (CLAUDE.md, pull-request, docs), with docs independently restating rules and solely owning one rule (no backtick-opening prose) found nowhere else.
  • The roadmap agent'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/*.mdx posts 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 @osterman (#2752)

what

  • Add a default no-release label to renovate.json, matching the label Dependabot PRs already get from dependabot.yml.
  • Add "rebaseWhen": "behind-base-branch" to renovate.json so Renovate keeps its own PRs current with main.
  • Add a Mergify rule in .github/mergify.yml that runs update on any open Dependabot/Renovate PR against main that'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 Labels check because it carried zero semver label. The check-cleanup logic in .github/workflows/codeql.yml's pr-semver-labels job only special-cases dependabot[bot], and dependabot.yml is 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 because main moved, so PRs accumulate as BEHIND until someone manually updates them.

This is purely CI/automation config — no user-facing behavior change, hence no-release.

references

  • Investigated as part of a broader Dependabot/Renovate PR triage (closed #1357 and #1846 as stale/superseded, left explanatory comments on #2732 and the other blocked PRs).
feat(workflows): add native archive step type for zip/tar packaging @osterman (#2730)

what

  • Add a native type: archive step for workflows and custom commands that packs a directory or file into a zip, tar, or tgz archive using the Go standard library only (archive/zip, archive/tar, compress/gzip) — no external zip/tar binary required.
  • Support action: replace (always rebuild fresh) on every format, and action: update (incremental add/refresh) for zip and uncompressed tar; action: create/action: extract and tar.bz2/tar.xz writers are reserved in the schema for a later phase and return a typed "not yet implemented" error.
  • Support subpath nesting, include/exclude glob filtering (reusing the existing pkg/utils.PathMatch matcher), and format inference from the destination/source extension.
  • Reuse the existing kind: step hook bridge instead of adding a dedicated hook kind, so kind: step + type: archive works as a component lifecycle hook with zero hook-side code.
  • Add the docs/prd/archive-step.md PRD, step-type reference docs, a changelog blog post, and a roadmap milestone.

why

  • Packaging build artifacts (most commonly a Lambda function's handler.zip before terraform plan/apply) had no native Atmos primitive — the only option was shelling out to zip/tar via a kind: command hook or type: shell step, which breaks on Windows CI and gives no typed validation.
  • This surfaced while migrating a Terragrunt example that used a before_hook wrapping the zip binary; a data "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 the kind: step bridge — 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.init hook, e.g. atmos terraform plan <component> -s <stack>) now supports oci:// component sources, matching what atmos vendor pull already supported.
  • Relocated the OCI-fetch implementation (image pull, auth precedence, tar extraction) from internal/exec into a new shared pkg/oci package, so both the legacy vendor-pull path and the JIT auto-provisioner can reach it without an import cycle.
  • pkg/provisioner/source.VendorSource now branches on vendor.IsOCIURI(uri) and calls oci.ProcessImage instead of go-getter for oci:// 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 with archive/tar: invalid tar header because 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: true and an oci:// source fails when JIT-provisioned with go-getter's own error, download not supported for scheme 'oci', even though the identical source works fine with atmos 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 @osterman (#2742)

what

  • Add --all, --affected, --max-concurrency, --failure-mode, and --log-order flags to atmos terraform init, matching the bulk-execution flags already on apply/plan/destroy.
  • Fix the scheduler's supportsTerraformConcurrency gate, which silently capped --max-concurrency to 1 for any subcommand it didn't explicitly recognize (including init).
  • init keeps the natural forward dependency order (prerequisites before dependents), unlike destroy, 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/--affected landed on apply/plan/destroy when Terraform bulk execution moved onto the scheduler-backed dependency graph (#2466), but init was left out.
  • Reinitializing every component after a provider upgrade, or bootstrapping a new environment, required scripting a loop over components by hand.

references

feat(claude): autonomous PR merge-readiness automation @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-loop skill — schedules the check hourly via Claude Code's native CronCreate/ScheduleWakeup primitives (not GitHub Actions, no external service). Calls CronCreate directly rather than the generic /loop skill, since this loop is inherently tied to the local git/gh session (signing key, checkout state) and has no cloud-compatible equivalent.
  • fix-all skill — the one-shot merge-readiness pass pr-maintenance-loop runs every cycle: sync with origin/main, check CI, resolve CodeRabbit threads, patch-scoped lint, patch-scoped test/coverage, and a final readiness check that announces (via the say skill'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-scoped gh pr edit --title/--body-file permission (labels/reviewers/base/milestone stay denied and human-owned).
  • lint / test-coverage skills — patch-aware (--new-from-rev=origin/main) lint and test/coverage checks reused both standalone and from fix-all.
  • say skill — a macOS audible nudge (say) for every human-attention exit path, plus the one positive "ready for final review" case.
  • New agentslint-fix, test-coverage-fix, merge-conflict-resolve, and coderabbit-review (existing, reused) do the actual fixing/resolving work each skill delegates to.
  • atmos fix custom 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 @osterman (#2715)

what

  • Add atmos vendor update — checks every Git-backed source in vendor.yaml (and standalone component.yaml/component.yml manifests) for a newer version, honoring each source's constraints (semver range, excluded versions, no_prereleases), and writes the new version in place using the format-preserving pkg/yaml engine. 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) for vendor update's output.
  • Fix vendor.base_path/--chdir resolution so vendor update/diff/get/set no longer fail to find vendor.yaml when atmos.yaml configures a non-default vendor.base_path.
  • Supporting changes: pkg/io/streams.go masking, cmd/version/formatters.go, pkg/toolchain/info.go, errors/errors.go, plus new pkg/vendoring/resolve.go and pkg/vendoring/component_files.go to unify component resolution across vendor.yaml and component.yaml sources.
  • 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 version field in vendor.yaml while risking a stripped comment or mangled YAML anchor.
  • These commands close that loop using the existing format-preserving pkg/yaml engine (the same one behind atmos config|stack|vendor get|set|delete), and extend it to repos that only use per-component component.yaml manifests.

references

  • Blog: website/blog/2026-07-09-vendor-diff-and-update.mdx
chore(security): resolve all open Dependabot and CodeQL alerts @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 Actions (folds in #2699):

  • actions/setup-python v6.3.0, chrnorm/deployment-status v2.0.4, cloudposse/github-action-pre-commit v4.1.0, github/codeql-action v4.36.2, hashicorp/setup-packer pin 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.overrides block pinning every alerted transitive dependency out of its vulnerable range (shell-quote critical, 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-icons 5.6 removed SiAmazonwebservices (Simple Icons trademark cleanup); switched to Font Awesome's FaAws

CodeQL (11 alerts):

  • Fixes all 8 go/allocation-size-overflow alerts by sizing allocations with a single len() term instead of a len(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-hashing alerts 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 main analysis

references

  • Supersedes and closes: #2690, #2699, #2605, #2580
  • Verified: go build ./..., targeted package tests, atmos lint changed, pre-commit hooks, and cd website && pnpm run build all pass; every Dependabot alert range was re-checked against the regenerated go.mod/pnpm-lock.yaml and 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 @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:

  1. Non-TTY default fallback (pkg/runner/step) — a shared BaseHandler.resolveInteractive decides prompt vs. default vs. error, and BaseHandler.ResolveDefault centralizes default resolution. The now-unused CheckTTY is removed. Because workflows, custom commands, hooks, and the task runner share the step handler registry, one change covers them all.
  2. !env / !exec in workflow step fields (internal/exec) — workflow step default/prompt/options/placeholder evaluate the context-free !env and !exec YAML 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/!exec field 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 !env defaults, feed values into a shell step, 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 @osterman (#2493)

what

  • Refresh the Atmos agent skills to use current stack naming, dependency, provisioning, auth, and toolchain guidance.
  • Replace the old atmos-gitops skill with atmos-ci, centered on Native CI, containerized GitHub Actions, OIDC profiles, checks, comments, summaries, affected/all matrices, and direct Atmos commands.
  • Add an atmos-ai skill 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, and settings.depends_on only 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 @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's default_tags via the providers section, 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 !emulator are 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 @osterman (#2701)

what

  • Point TMPDIR and GOLANGCI_LINT_CACHE at repo-local dirs in scripts/run-custom-golangci-lint.sh so each worktree gets its own golangci-lint lock and cache (GOCACHE stays shared for warm typecheck data); opt out with ATMOS_LINT_SHARED_CACHE=1.
  • Skip rebuilding the custom-gcl binary in atmos lint custom-gcl when it is already up to date (staleness guard mirroring lint lintroller/lint gomodcheck).
  • Drop the redundant standalone lint lintroller pass from atmos lint changed (it already runs as a plugin inside custom-gcl).

why

  • golangci-lint's single-instance lock is a fixed machine-global path (os.TempDir()/golangci-lint.lock), not inside GOLANGCI_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-worktree TMPDIR lets them run in parallel and contains an orphan to its own worktree.
  • Rebuilding custom-gcl (clone + compile) on every lint changed was the single biggest per-run cost during a rebase storm when the binary is identical across worktrees.
  • Verified empirically: an isolated-TMPDIR lint 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 @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 @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|list interface and shorthand aliases for the common case:
    • atmos config get|set|delete|format|list <path> — edits the active atmos.yaml. (No separate config sub-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. list only exists under config: a flat atmos stack list would collide with the existing atmos 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 its spec.sources[N].version path and delegates to the same engine; the canonical form addresses any path in the manifest.
  • atmos vendor update / atmos vendor diff on the same engine (check Git sources for newer versions, honoring semver constraints; diff two versions without a local checkout).
  • --type flag 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|render commands.
  • 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 --check acts 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%), and pkg/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 (TestRelativePathForStackDisplay used synthetic paths that aren't recognized as absolute by Go's Windows filepath.IsAbs).
  • Fixed a CodeQL go/allocation-size-overflow finding in internal/exec/utils.go and 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.yaml and .atmos.d/dev.yaml used the new !repo-root YAML tag, which doesn't exist in the pinned older atmos bootstrap 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|set refactored from a separate yq-expression implementation into a literal thin wrapper over atmos vendor config get|set (matching how atmos stack get|set|delete|format already alias atmos stack config), and documented the previously-undocumented atmos 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/yq strips 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 shared pkg/yaml engine generalizes (and supersedes) the hardcoded sources[].version writer from the older feat/vendor-diff-and-update branch.
  • 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-update branch (the broader vendor diff/vendor update feature port is tracked as follow-up).
  • Known limitation: blank lines between entries are not preserved (inherent to the gopkg.in/yaml.v3 node model that yqlib builds on).
Add casts core recording and rendering support @osterman (#2692)

what

  • Add casts-core support with atmos cast play, atmos cast render, and global --cast recording 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, and workdir, 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-cast branch 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.md
  • docs/fixes/2026-07-06-custom-command-include-env.md
  • docs/fixes/2026-07-06-custom-command-env-map-form.md
  • docs/fixes/2026-07-06-custom-command-step-execution.md
  • docs/fixes/2026-07-06-terminal-color-and-force-tty.md
  • docs/fixes/2026-07-06-help-rendering-without-config.md
  • docs/fixes/2026-07-06-mask-shell-and-env-output.md
  • docs/fixes/2026-07-06-chdir-updates-pwd.md
  • docs/fixes/2026-07-06-local-vendor-source-paths.md
  • docs/fixes/2026-07-06-step-output-labels.md

why

  • Split the reviewable casts-core implementation out of ascii-cast before 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=1
  • COLUMNS=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=1
  • go test ./cmd/...
  • go test ./pkg/asciicast ./pkg/io ./pkg/terminal/... ./pkg/runner/step ./pkg/process ./pkg/schema ./pkg/config
  • go test ./cmd/cast -count=1
  • git diff --check -- docs/fixes
Add topic-specific CLI help @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 @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.mode avoids 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

feat: add validation for unsupported YAML tags @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

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 acceptance mode with --cover.
    • Refreshed related docs/blog examples to match the new command surface.
feat: YAML key delimiter for dot notation in stack files @osterman (#2139)

what

  • Implement configurable settings.yaml.key_delimiter that 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: network

Atmos 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: network

Quoted mapping keys remain literal, so existing dotted keys can be preserved:

components:
  terraform:
    app:
      vars:
        "output.json": true
        'config.file': /etc/app.conf

why

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.
  • 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 @osterman (#2677)

what

  • Moves build, lint, test, screengrab, check, format, and cache tasks from Make into grouped .atmos.d Atmos commands, with atmos build as 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-versions installs explicit to atmos toolchain install, adds atmos shell, and adds a blog post embedding live .atmos.d examples.
  • Fixes custom-command dogfooding gaps for default subcommands, .atmos.d command-list merging across files with YAML tags, .cwd template 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 commit hooks, ./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 short still reports unrelated workspace failures in internal/tui/utils and pkg/utils.
Add CEL-backed when conditions @osterman (#2689)

what

  • Add pkg/condition to own when parsing, predicate handling, CEL compilation/evaluation, runtime context, and tests.
  • Keep pkg/schema compatible through aliases while wiring CEL-aware when evaluation into workflow steps, custom command steps, and hooks.
  • Update YAML tag normalization, JSON schemas, validation tests, and docs so bare CEL strings and explicit !cel conditions are supported.

why

  • This lets users write expressive, sandboxed boolean when expressions 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` @osterman (#2661)

what

  • atmos git clone (Atmos's native actions/checkout replacement) now refuses by default to clone untrusted fork content under GitHub's elevated pull_request_target and workflow_run events.
  • 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 whose owner/repo differs from the base GITHUB_REPOSITORY). Safe no-arg base checkouts and low-privilege pull_request/push/merge_group events are unaffected.
  • Adds a grep-able opt-in: --allow-unsafe-fork flag, ATMOS_ALLOW_UNSAFE_FORK_EXECUTION env var, and ci.allow_unsafe_fork_execution config key.
  • Adds the provider-agnostic gate core (pkg/ci.EvaluateForkCheckout), a Context.ElevatedEvent trust signal set by the GitHub provider, the ErrUnsafeForkCheckout sentinel, and unit tests (incl. negative paths).
  • Documents it: new PRD docs/prd/native-ci/framework/fork-pr-trust-gate.md, atmos git clone CLI docs, CI configuration reference, and a changelog blog post + roadmap milestone.

why

  • Because atmos git clone fills the same role as actions/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/checkout v7 hardening (fail-closed by default, explicit grep-able opt-out), bringing Atmos's checkout replacement to parity.

references

fix(algolia): surface page Intro as search-result subtext @osterman (#2429)

what

  • Reorder Algolia customRanking to [desc(weight.pageRank), asc(weight.position), desc(weight.level)] so the first content record on a page wins the per-URL distinct dedup.
  • Broaden the content selector to lead with article .intro so 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 top atmos auth hit has a non-empty _snippetResult.content.

why

  • Queries matching page titles (e.g. atmos auth) were resolving to lvl1 records, 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 of asc(weight.position), heading records always outranked content records inside distinct, 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 prior article p, … selector missed the Intro text entirely. Adding article .intro indexes 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 as initialIndexSettings is pushed.
Add native container image CI summaries @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/push component commands behind existing ci.enabled and ci.summary.enabled controls.
  • 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, and list RunE handlers to:
      • bind flags to Viper at execution time, and
      • use effective resolved values for downstream backend operations.
  • Shared flag-resolution helper

    • Added cmd/terraform/backend/flag_values.go with reusable helpers to read changed string/bool flags from local/inherited command flags, enabling consistent behavior across backend subcommands.
  • Regression coverage

    • Added a focused backend command regression test ensuring subcommands no longer fail with missing-stack errors when --stack is provided and that the resolved value flows into backend config initialization.
stack := getCommandFlagString(cmd, "stack")
if stack == "" {
    stack = v.GetString("stack")
}

Summary by CodeRabbit

  • New Features
    • Added configurable --error-mode handling for list and describe commands, supporting strict, warn, and silent.
    • 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.
  • 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) @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[].src entrypoints (ignoring LICENSE/README/docs), Atmos now preserves the complete archive tree under <versionDir>/.pkg and 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 pinned nodejs/node@24.18.0 resolves to the published v24.18.0 archive 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 only aws + aws_completer, dropped the bundled Python runtime (~8,600 files incl. libpython). The install reported success, but aws --version crashed with Failed to load Python shared library.
  • nodejs/node (#2744): {{.Version}} rendered without the leading v so the archive directory never matched, and npm/npx/corepack symlink 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.15aws --version prints aws-cli/2.35.15 Python/3.14.5 ... (direct and via toolchain env PATH).
  • nodejs/node (darwin/arm64): atmos toolchain install nodejs/node@24.18.0node, npm, npx all 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 .pkg and use a sidecar manifest to resolve real entrypoints.
    • PATH construction includes all distinct onedir entrypoint directories.
    • Added GetBinaryPaths to list all installed onedir entrypoint paths for a version.
  • 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 .exe behavior.
  • 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 @osterman (#2736)

what

  • Component hooks: (the user-defined hooks.RunAll engine) now fire for atmos terraform apply/plan/deploy under --all, --affected, --components, and --query — previously they silently never ran in these bulk modes, only for single-component invocations.
  • Adds a schema.ComponentNodeHooks interface (Before/After) wired into the scheduler's TerraformDispatcher.Dispatch — the one choke point shared by every bulk dispatch path — via a new pkg/hooks.RunPerComponentHooks helper.
  • 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|ignore setting rather than introducing new failure semantics.
  • Helmfile gets the same ComponentNodeHooks wiring, 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.go files, and orphaned helpers in terraform_utils.go/terraform_affected.go) that predated the scheduler-based dispatch path and had no live (non-test) callers left.

why

  • A before.terraform.apply hook (e.g. zipping a Lambda handler before apply) worked correctly under atmos terraform apply <component> -s <stack> but silently never ran under atmos 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 @osterman (#2698)

what

  • atmos version track update, lock, status, and diff now default to a human-readable, TTY-aware table (matching atmos version track list), instead of raw YAML.
  • --format=yaml / --format=json remain available as explicit opt-ins and continue to return the original full-fidelity struct (e.g. digests, ecosystem/datasource detail) unchanged.
  • --format=csv / --format=tsv are now also supported for these four verbs, reusing the same pkg/list/renderer pipeline already used by version track list and secret list.
  • lock's default table is scoped to the resolved track's entries (not the entire multi-track lock file); --format=yaml/json still dump the full versions.lock.yaml contents.
  • Updated website/docs/cli/commands/version/track/{update,status,lock,diff}.mdx and website/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.go for 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 list already got this right; update/lock/status/diff shared a writeFormatted() helper that had no table option at all.

references

  • Base branch is osterman/atmos-yaml-schema-split (not main) because this is a follow-up fix stacked on the still-open #2664, which already contains the merged Atmos Version Tracker feature (#2688).
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:

  1. Try the canonical parser first.
  2. If it fails, fall back to the generic parser.
  3. 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 affected is 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 only net/url and regexp from 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.goGetRepoInfo (used by internal/exec/describe_affected_helpers.go and internal/exec/pro.go)
    • internal/exec/describe_affected.gouploadableQuery (the --upload path to Atmos Pro)
fix(io): migrate fmt.Fprintf/Println anti-patterns to pkg/ui, pkg/data @osterman (#2713)

what

  • Migrate every remaining raw fmt.Fprintf/fmt.Fprintln/fmt.Println call outside pkg/io/pkg/ui to the sanctioned ui.*/data.* API across ~40 files (AI permission prompts, devcontainer/kubeconfig debug diagnostics, CI annotations/log groups, the Cobra root command, pkg/manifest, pkg/runner/step cast recording, and more).
  • Delete the pure print-forwarding shims in pkg/utils (PrintMessage, PrintfMessageToTUI, PrintfMarkdown*) and migrate all ~50 call sites directly onto data.*/ui.*, consolidating onto the already-established ui.Markdown/ui.MarkdownMessage renderer 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 env emitting real credential values for shell eval).
  • Add ui.MarkdownNoWrap/MarkdownMessageNoWrap for 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 raw os.Stdout, and extract the oversized Cobra help/usage closures into named helpers while there.
  • Correct pkg/devcontainer's ShowConfig/List output 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.Println outside pkg/io/pkg/ui because it bypasses secret masking, TTY/color degradation, and --cast recording. 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/utils print helpers were pure forwarding shims duplicating pkg/ui/pkg/data, adding an unnecessary second code path for callers to reason about; deleting them and routing directly through the canonical API reduces pkg/utils's surface area per this repo's ongoing effort to empty that package out.
  • atmos auth env/atmos env need one legitimate, explicit way to emit unmasked output (the whole point of those commands is exporting real credentials for eval), so a dedicated WriteUnmasked API makes that intent explicit instead of ad hoc fmt.Print calls.

references

fix: run parallel/matrix, shell, and exec steps through the step registry @osterman (#2703)

what

  • Wire parallel and matrix control steps into the pkg/runner/step registry through a reverse-registered ControlRunner seam (mirroring the emulator seam), so they execute from custom commands and lifecycle hooks, not only atmos workflow. Interactive (TTY) child steps are rejected, since they cannot run concurrently.
  • Register the exec step type in the registry so type: exec works outside the legacy workflow executor too.
  • Run the registry shell handler and parallel/matrix shell children through the in-process mvdan/sh interpreter with secret masking, replacing a host sh -c invocation; thread a context so fail-fast cancellation reaches running scripts.
  • Delete the dormant, duplicate workflow Executor engine and its adapters/mocks (zero production callers), preserving the still-live helpers that were co-located in those files (BuildConditionContext, CheckAndGenerateWorkflowStepNames).

why

  • parallel, matrix, and exec were recognized/validated step types whose handlers only ever worked inside atmos 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 same mvdan/sh interpreter 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.Executor had 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 @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 existing pkg/http.GitHubAuthenticatedTransport (already used by the Terraform registry cache and toolchain downloaders).
  • Added (*http.DefaultClient).HTTPClient() so callers needing the concrete *http.Client (like go-getter's HttpGetter.Client field) 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 !include YAML function's HTTP(S) downloads went out fully unauthenticated via go-getter's stock HttpGetter, subject to GitHub's much lower anonymous per-IP rate limit on raw.githubusercontent.com, regardless of whether a token was available.
  • pkg/downloader/file_downloader.go already 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: 429 failures in CI for tests exercising !include against 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 @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-tracker to examples/version-tracker and 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.* }}, using atmos version track set/get instead 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 manual PS1 override from the interactive-workflows cast.
  • Make quick-start-advanced's !terraform.state catalog references tolerate not-yet-deployed sibling components (// "pending" fallback), and turn its list-instances cast 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 heavier EmbedExample bundle to the same lightweight pattern for consistency.
  • Codify in the atmos-asciicast and pull-request skills 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 * and quick-start-advanced fixes 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 @cmharden (#2674)

what

  • Add --log-order stream|grouped to atmos terraform apply, deploy, and destroy (previously plan-only).
  • Bring deploy to concurrency parity with apply: add --max-concurrency and --failure-mode, and add deploy
    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 (PlanLogOrderLogOrder,
    TerraformPlanLogOrderTerraformLogOrder, consts drop Plan).
  • 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

  • plan could order concurrent per-component logs, but apply/deploy/destroy could not — atmos terraform apply --all --log-order grouped failed with "unknown flag". Grouped output (contiguous per-component blocks) is what
    makes concurrent --all runs 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-order flag name, stream/grouped values, and ATMOS_TERRAFORM_PLAN_LOG_ORDER
    are unchanged; the renamed field is internal/untagged.
  • --log-order grouped engages only under --max-concurrency > 1 (matches plan).
  • 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 @osterman (#2712)

what

  • Fixes internal/exec/terraform_nested_auth_helper.go so the nested-auth cache (nestedAuthManagerCache, added in #2652/#2656) never caches a result resolved through authContextWrapper — its GetChain() 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's AuthManager.
  • Fixes internal/exec/template_funcs_component.go (atmos.Component()) so it resolves a nested target's own auth: section (via resolveAuthManagerForNestedComponent) instead of always reusing the enclosing component's credentials verbatim — bringing it in line with how !terraform.state/!terraform.output already behave.
  • Adds regression tests for both (TestBuildComponentAuthCacheKey_AuthContextWrapperNeverCaches, TestResolveComponentFuncAuthManager) and a docs/fixes write-up.

why

  • A user reported atmos list instances regressing from v1.221.0 to v1.222.0 under GitHub Actions/OIDC auth (no ambient AWS credentials): a Go template calling atmos.Component(...) to fetch a nested component's terraform output failed with no 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 in docs/fixes/2026-07-09-atmos-component-nested-auth-cache-collision.md.

references

fix(workflows): resolve step vars in inline shell/atmos/exec steps @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

  • ExecuteWorkflow configures 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.ResolveWith applies a per-call environment overlay without mutating the shared executor env, so a step's environment is visible as {{ .env.* }} without leaking across steps.
  • prepareStepEnvironment stays 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), and env: resolution.
  • End-to-end (built binary): a workflow feeding a captured value into a shell step's command, env:, and a Sprig function all resolve correctly.
  • go build ./..., pkg/runner/step + internal/exec suites, and atmos lint changed are 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) @sgtoj (#2710)

what

  • Bring the published atmos-manifest JSON Schema (served at atmos.tools and mirrored to SchemaStore) back in sync with the workflow features shipped in 1.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 existing dependencies definition via $ref).
  • workflow_step: add the 75 missing step fields with descriptions, spanning every step-type family (options/default/interactive/tty/script, plus http, cast, container, style, log, say, env, exit, emulator, junit, require, workdir, table, control/parallel). Set additionalProperties: true and make level a plain string (see why).
  • Add three shared sub-definitions: workflow_viewport, workflow_show, workflow_container (the latter also accepts false to run on the host).
  • Add a reflection-based ratchet test (schema_workflow_coverage_test.go) that fails the build if a WorkflowStep/WorkflowDefinition field 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 with additionalProperties: 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, so additionalProperties: true (plus fully enumerated known fields for autocomplete + type checks) matches real behavior and eliminates the whole false-positive class — present and future — mirroring how providers/templates are modeled. level is 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 stacks is 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) use command: 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) @osterman (#2705)

what

  • ci.cache (the CI build cache) now excludes aws-sso, azure-device-code, aws-webflow, and auth — the subdirectories under ~/.cache/atmos where Atmos persists session credentials — unconditionally, with no opt-out, regardless of ci.cache.paths.
  • Applies in both Atmos's own cache backend (pkg/ci/cache/archive.go) and the atmos ci cache paths output used with the native actions/cache GitHub Action (rendered as !-prefixed glob exclusions, working around a known actions/toolkit glob-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 when ci.cache.paths is 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.cache was 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 on ci.cache.paths conventions.
  • 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 @osterman (#2702)

What

  • Fix the broken announcement-bar link: /blog/native-ci-integration/changelog/native-ci-integration.
  • Add a createRedirects rule to plugin-client-redirects so any other stale /blog/<slug> URL (old bookmarks, indexed search results) forwards to its current /changelog/<slug> page, matching the existing bare /blog/changelog redirect.

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 **.md and explicitly excludes website/**.
  • Docusaurus's onBrokenLinks: 'throw' only analyzes links it discovers via rendered MDX/<Link> content — the announcement bar renders its content string through dangerouslySetInnerHTML, 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 build succeeds.
  • Confirmed build/blog/native-ci-integration/index.html contains 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-integration returns 200 (the corrected target).
fix: deep merge drops keys under unquoted-integer YAML map keys @osterman (#2700)

what

  • Fixes normalizeMapReflect in pkg/merge/merge.go so a map[interface{}]interface{} (the shape yaml.v3 produces for a YAML mapping with an unquoted non-string key, e.g. 1:) is stringified into map[string]any instead 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 real yaml.v3 unmarshaling 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 forces map[interface{}]interface{} for that mapping node. normalizeMapReflect lumped this together with genuinely-typed non-string-key Go maps (e.g. map[int]schema.Provider) and preserved both, so deepMergeNative's map[string]any fast 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

Migrate devcontainer commands to flag handler @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 exec to prefer exec <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 for devcontainer exec.

references

[codex] Fix CI and native component compatibility regressions @osterman (#2685)

what

  • Fix Terraform native CI planfile upload for workdir-provisioned components by resolving the effective .workdir artifact path.
  • Fix native Helm plugin binary resolution to use the helm dependency scope, and expose --ci on 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 (or ATMOS_TERRAFORM_VERIFY_PLAN=true) is set but planfile storage is not configured under components.terraform.planfiles — previously the flag silently no-op'd and deploy applied an unverified fresh plan. A config-set verify: mode without storage now logs a warning. Docs updated everywhere the flag/prerequisite is mentioned, and the missing planfiles section 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/saml Browser-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/... and go test ./tests -count=1.
Support --profile flag for custom commands with autocomplete @osterman (#2190)

what

  • Added early parsing of --profile flag (before Cobra parses) to ensure profiles are applied during initial config load, so custom commands receive a properly profiled atmosConfig
  • Extended StringSliceFlag to support custom completion functions
  • Implemented profile autocomplete that lists available profiles from the profile manager
  • Updated FlagRegistry.SetCompletionFunc() to support both StringFlag and StringSliceFlag types

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 @osterman (#2268)

what

  • Replaced all log.Info and log.Error calls in the vendor non-TTY code path with proper ui.* methods (ui.Success, ui.Error, ui.Info)
  • Per-package status lines now use ui.Successf/ui.Errorf which provide consistent icons and styling
  • Summary and dry-run messages use ui.Successf/ui.Info instead of structured log key-value pairs
  • Extracted repeated format string into a pkgStatusFmt constant

why

  • The vendoring non-TTY path was abusing log.Info for user-facing status messages, which are UI output not diagnostic logs
  • log.Info renders 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 used
  • docs/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 @osterman (#2687)

what

  • Redesign the Atmos CI comment badge served from the existing atmos-ci-gradient image 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-scheme responsive 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', and pnpm build.
  • Commit hooks were retried after building custom-gcl; golangci-lint --new-from-rev=origin/main still reports existing unrelated lint findings outside this change set.
Split help command sections and redesign error formatting @osterman (#2678)

what

  • Split help output into BUILT-IN COMMANDS and CUSTOM COMMANDS, while keeping config aliases in the dedicated ALIASES section.
  • 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, full TestCLICommands snapshot regeneration, and pre-commit hooks.
Fix GitHub artifact signed URL download @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 Authorization header.

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

Fix native CI dogfood regressions @osterman (#2681)

what

  • Fix native CI bootstrap so atmos git clone can 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 path state reads, remote source-provisioned lock persistence, Docker python3, 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 host wrapper.

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

Don't miss a new atmos release

NewReleases is sending notifications on new releases.