github cloudposse/atmos v1.229.1-rc.1

pre-release2 hours ago
feat(ai): add opencode CLI provider @aknysh (#3189) ## what
  • Add opencode (https://opencode.ai) as a CLI AI provider (opencode), alongside claude-code, codex-cli, and copilot-cli. Atmos runs the opencode binary non-interactively (opencode run <prompt>), reusing the user's opencode auth and model-provider configuration — no API key in atmos.yaml.
  • Full MCP pass-through: when mcp.servers are configured, Atmos writes a temporary opencode config (auth-requiring servers wrapped with atmos auth exec -i <identity>, toolchain PATH injected) and points opencode at it via the OPENCODE_CONFIG env var. opencode deep-merges it, so the user's own opencode.json is never modified and nothing is left behind (temp file cleaned up per invocation).
  • Joins the CLI auto-detection chain (claude-codecodex-clicopilot-cliopencodegemini-cli) and the IsCLIProvider set. cmd/ai/init.go needs no change — its MCP handling is generic via IsCLIProvider.
  • Adds unit tests (config, buildArgs, ExtractResult, temp-config schema incl. auth-wrapping, and a cross-platform subprocess round-trip via a TestMain fake binary — 86.7% coverage), provider documentation, a changelog post, and a roadmap milestone.

why

  • Atmos AI already lets users reuse a locally installed coding-agent CLI (Claude Code, OpenAI Codex, GitHub Copilot) instead of an API key, but the popular open-source opencode agent wasn't supported. opencode users had to fall back to a raw API provider — a second credential to manage, giving up opencode's own model selection and MCP setup.
  • Adding opencode as a first-class CLI provider lets those users drive atmos ai through their existing opencode installation with zero extra credentials.

references

  • Part of #3179 (the API providers OpenRouter / DeepSeek / Z.AI shipped in #3188).
  • Docs: /cli/configuration/ai/providers
  • Changelog: website/blog/2026-09-18-opencode-cli-provider.mdx

Summary by CodeRabbit

  • New Features

    • Added OpenCode as an AI CLI provider for atmos ai, with automatic detection and optional model selection.
    • Supports conversation history, system prompts, explicit full-auto mode, and MCP server pass-through.
    • Reuses OpenCode authentication and configuration without modifying user files.
    • Applies MCP settings through a temporary configuration file and reports configuration or execution errors clearly.
  • Documentation

    • Added OpenCode setup, usage, troubleshooting, and roadmap guidance.
    • Documented OpenRouter, DeepSeek, and Z.AI providers.
  • Tests

    • Added coverage for provider detection, command execution, response handling, and MCP integration.
feat(scaffold): support glob patterns in spec.files[].path @jorrite (#3187) ## what
  • spec.files[].path can now be a glob pattern (doublestar syntax: *, ?, [...], ** for any depth, {a,b}), matched against every file the template discovers, instead of only a literal path.
  • A glob path: combined with when: gates or skips an entire directory recursively, in one entry, instead of one when:-gated entry per file.
  • A glob path: combined with matrix: and target: duplicates an entire directory's files once per matrix combination, the same way a single-file matrix entry already does. Two new template variables, .file.Path and .file.RelPath (the matched file's path with the entry's glob literal prefix stripped), are available in target: and content so a directory-level target: can differentiate which matched file an output came from.
  • When more than one spec.files[] entry's path: matches the same discovered file, the last declared entry wins — the same precedence convention .gitignore/CODEOWNERS use.
  • A directory-level matrix axis expression is now resolved once per spec.Path, not once per matched file, so a non-deterministic axis expression (e.g. Sprig's randAlphaNum) resolves the same value across every file one entry matches, instead of a different value per file.
  • A malformed glob pattern (unclosed [/{) now fails scaffold load and atmos scaffold validate immediately (ErrScaffoldFilePathPatternInvalid), instead of silently and permanently matching nothing.
  • A backslash in a path: pattern is now always normalized to a forward slash regardless of OS, instead of only working correctly on Windows.
  • A directory-level matrix target: that fails to reference .file.Path/.file.RelPath now fails deterministically before any file in the run is written (ErrScaffoldMatrixTargetMissingFileContext), instead of leaving a partial write on disk once a collision is discovered mid-run. This check parses target: with the scaffold's real delimiters and walks the resulting template AST for a genuine .file.Path/.file.RelPath field access, rather than a lexical substring match — so it can't be fooled by a literal .file. in unrelated text or an invalid .file.Unknown reference.
  • atmos scaffold generate --update can now recover a real 3-way merge base after a target: migration (e.g. adopting a glob + .file.RelPath-based target on an entry that previously rendered verbatim to its own path): the merge-base lookup falls back to the file's original discovered source path when git history has nothing at the new rendered path, instead of always treating it as user-added and silently freezing its content forever.
  • The atmos-scaffold AI agent skill (agent-skills/skills/atmos-scaffold/) now documents glob path:, directory-level matrix, and .file.Path/.file.RelPath, so AI agents helping a user author a scaffold.yaml know this capability exists.

why

spec.files[] matched files one at a time by exact literal path, so gating or duplicating an entire directory meant repeating the same when: or matrix:/target: on every file inside it, one entry per file, kept in sync by hand as the directory grew — a gap the original matrix: PRD explicitly called out as a non-goal at the time. Directory-level matrix duplication is a natural extension of the existing single-file matrix feature (e.g. one components/ tree instance per environment), and directory-wide skip is the same gap for when: alone (a legacy docs tree gated behind an opt-in answer, a cloud-provider-specific subtree).

Several bullets under "what" are bugs found either while field-testing the feature against a real build, or during CodeRabbit review — each reproduced live before and after the fix, with a new regression test.

references

  • Blog post: website/blog/2026-09-18-scaffold-directory-glob.mdx
  • PRD: docs/prd/atmos-scaffold.md ("Glob path: and Directory-Level Matrix")
  • Docs: website/docs/cli/commands/scaffold/generate.mdx ("Glob Paths and Directory-Level Matrix")
  • Example: examples/scaffolding-directory-matrix/
  • Agent skill: agent-skills/skills/atmos-scaffold/

Summary by CodeRabbit

  • New Features
    • Scaffold file paths support glob patterns, recursive directory matching, and normalized separators.
    • Apply conditions to directory trees and duplicate matched files across matrix values.
    • Use file path template variables to preserve relative locations and avoid output collisions.
    • Overlapping matches follow last-declared-entry precedence.
  • Bug Fixes
    • Invalid patterns and ambiguous matrix targets fail before files are written.
    • Updates recover merge history when rendered paths change and warn when unavailable.
    • Invalid file-context references are no longer misidentified as valid.
  • Documentation
    • Added usage guidance and a directory-matrix scaffold example.
feat(ai): add OpenRouter, DeepSeek, and Z.AI providers @aknysh (#3188) ## what
  • Add three first-class OpenAI-compatible API providers to Atmos AI, selectable by name under ai.providers:
    • openrouter — OpenRouter (https://openrouter.ai/api/v1, OPENROUTER_API_KEY). A router across hundreds of models; switch models by changing the provider-prefixed model slug (e.g. anthropic/claude-sonnet-4-5, openai/gpt-4o, deepseek/deepseek-chat). Default model: deepseek/deepseek-chat.
    • deepseek — DeepSeek (https://api.deepseek.com, DEEPSEEK_API_KEY). Default deepseek-chat; deepseek-reasoner for the reasoning model.
    • zai — Z.AI / Zhipu GLM (https://api.z.ai/api/paas/v4, ZAI_API_KEY). Default glm-5.3.
  • Each provider mirrors the existing grok provider using the shared base/openaicompat conversion layer, self-registers via init(), and works everywhere the other providers do (atmos ai ask/chat, --ai).
  • No schema change required — base_url, api_key, and model already exist on the AI provider config.
  • Adds unit tests (config extraction, client construction, getters, and an httptest round-trip exercising all five send methods), factory registration coverage, provider documentation, a changelog post, and a roadmap milestone.

why

  • The AI model landscape moves faster than any single vendor's roadmap, and a US-only provider list is a non-starter for many teams. Users wanted to route through OpenRouter (to test many models cheaply), or use DeepSeek / Z.AI (GLM) directly.
  • Before this change the only way to reach these was to point the generic openai provider at a hand-copied base URL and hope the defaults lined up. These providers make the common choices first-class: sensible default model, API-key env var, and endpoint out of the box.

references

  • Part of #3179 (the Opencode CLI provider is intentionally split into a separate follow-up PR).
  • Docs: /cli/configuration/ai/providers
  • Changelog: website/blog/2026-09-18-more-ai-providers.mdx

Summary by CodeRabbit

  • New Features

    • Added support for OpenRouter, DeepSeek, and Z.AI providers across Atmos AI commands.
    • Added configurable models, endpoints, token limits, request timeouts, conversation history, system prompts, and tool usage.
    • Added secure base URL validation when API keys are configured, while allowing local loopback endpoints.
  • Documentation

    • Added provider configuration guidance and updated the roadmap with the new integrations.
  • Bug Fixes

    • Improved handling of API failures, empty responses, and insecure endpoint configurations.
fix(ci): allow Go toolchain download in website preview deploy @aknysh (#3191) ## what
  • Add release-assets.githubusercontent.com:443 to the harden-runner allowed-endpoints in .github/workflows/website-preview-deploy.yml.

why

  • The Website Preview Deploy job has been failing on main and every recent PR for days — its Set up Go step dies with connect ECONNREFUSED …:443 while downloading the Go toolchain.
  • The deploy job genuinely needs Go: the s3-deploy composite action runs go tool mage s3:deploy. So removing the step isn't an option — the toolchain download must be allowed.
  • actions/setup-go fetches Go from GitHub's release-asset CDN (release-assets.githubusercontent.com), but that host was missing from the deploy job's egress allowlist while harden-runner runs with egress-policy: block. The build job (website-preview-build.yml) already allows this host, which is why the site build succeeds but the deploy fails.
  • This only affects the preview-deploy Deployment (the website-deploy-preview build check already passes); it is not a merge gate. Because workflow_run workflows execute the copy on the default branch, the fix has to land on main to take effect.

references

  • Surfaced while reviewing CI on #3188 and #3189.
  • Matches the allowlist in .github/workflows/website-preview-build.yml.

Summary by CodeRabbit

  • Chores
    • Website preview deployments can now access required release assets over HTTPS.
feat(scaffold): --update-strategy=tracked|rendered for atmos init/scaffold generate @jorrite (#3119) ## what
  • Add --update-strategy=tracked|rendered to atmos init --update and atmos scaffold generate --update, controlling where the three-way merge base comes from, independently of --merge-strategy (conflict resolution) and --merge-driver (merge algorithm).
    • tracked (default): unchanged behavior — base read from the target's own git history at --base-ref.
    • rendered: base is a pristine re-render of the template at the ref that produced what's currently on disk, using that generation's own recorded .atmos/scaffold.yaml answers. No dependency on the target being a git repository at all.
  • New pkg/generator/engine.UpdateStrategy type + ParseUpdateStrategy, a baseContentLoader interface abstraction on Processor (satisfied by both the existing storage.GitBaseStorage and the new storage.RenderedBaseStorage), and pkg/generator/source.ResolveRenderedBase (loads the target's project record and fetches the old ref before the current run overwrites it).
  • rendered requires the template to carry a scaffold.yaml (so its answers are recoverable) — plain --set-only templates aren't supported yet and get a clear error pointing at tracked.
  • --base-ref + --update-strategy=rendered is a mutually-exclusive-flags error, since rendered's ref comes from .atmos/scaffold.yaml, not --base-ref.
  • rendered works for OCI-sourced templates too: the resolved manifest digest is pinned the same way a git commit SHA is, so a later re-render always resolves the exact original content instead of whatever a mutable tag currently points to.
  • Docs: <dt>/<dd> flag entries and usage examples on init.mdx/scaffold/generate.mdx/scaffold/usage.mdx, updates to the atmos-scaffold.md/atmos-init.md PRDs, a changelog post, and a roadmap milestone.

hardening

A /field-test pass and two rounds of CodeRabbit review against the shipped feature surfaced several real gaps, all fixed in this branch:

  • Silent data loss: an unconditional base-ref resolution used to run for any --update regardless of strategy and wrote a resolved value into spec.baseRef even under rendered — whose whole point is to have no git-history dependency at all. Introduced spec.renderedRef, a separate project-record field recording the actual resolved ref (git commit SHA or OCI manifest digest) at generation time, and gated every place that previously wrote spec.baseRef unconditionally on --update-strategy — including two retry-only code paths (the "confirm update instead" offer, and the interactive-declined-then-retried flow) that a first fix pass missed.
  • Crash: the "confirm update instead" retry path could reach rendered mode's base-render step with no base source ever resolved, panicking on a nil pointer. Fixed with both a functional retry-path fix and a defensive nil-check that turns any future recurrence into a clear error instead of a panic.
  • Switch detection: a project's recorded baseRef/renderedRef now doubles as a record of which strategy last managed it; switching strategies against an existing project fails loudly instead of silently misinterpreting the other strategy's provenance.
  • OCI subdir sources: go-getter's git fetch for a //subdir source (the shape atmos init aws/app itself uses) clones the full repo into its own internal temp location and copies only the subdir out, so the destination directory never has a usable .git to inspect — spec.renderedRef silently stayed empty for any subdir-sourced template. Fixed with a best-effort fallback re-fetch scoped to resolving just the commit.
  • Enforced the existing WithValidValues flag-validation framework for --update-strategy/--merge-driver/--merge-strategy (previously registered but never actually checked), fixed a misleading error message that assumed git under rendered mode, and closed several direct-unit test-coverage gaps left by cross-package-only test exercise.

why

--update's merge base has always been read from the target's own git history at a pinned commit. That has two real costs:

  1. It requires the target to be an intact git repository — a squashed/rewritten history, or a target that was never a git repo at all, makes --update fail outright.
  2. The pinned base ref never advances (by design, see #2989), so the gap between base and the current template only grows across successive updates.

--update-strategy=rendered sidesteps both: no git dependency, and the base always reflects exactly one update cycle's worth of drift, since it's re-rendered fresh from the recorded prior generation each time.

references

  • #2989 (base-ref pinning this builds on)
  • #3047 (conflict-marker correctness fixes this branch depends on, already merged)

Summary by CodeRabbit

  • New Features

    • Added --update-strategy to initialization and scaffold updates.
    • The default tracked strategy uses target Git history.
    • The rendered strategy reconstructs prior template state from recorded configuration, supporting updates without target Git history.
    • Rendered updates reuse the exact template revision and support dry-run previews.
  • Bug Fixes

    • Added validation for invalid strategies, incompatible --base-ref combinations, missing configuration, and strategy switches.
  • Documentation

    • Updated CLI guides, product documentation, roadmap, and blog content.

🚀 Enhancements

fix(vendor): add `vendor clean --prune-lock` to forget lock entries @aknysh (#3201) ## what
  • Add an opt-in --prune-lock flag to atmos vendor clean that also removes the cleaned components' entries from vendor.lock.yaml (in addition to removing their files).
  • It forgets only the entries the same --component/--tags/--stack/--labels selectors matched — never entries the caller didn't target. Preservation stays the default.
  • Adds lockfile.CleanOptions{Force,DryRun,PruneLock}, pruneSelectedLockEntries, CleanReport.Forgotten, unit + cmd tests, and CLI docs.

why

  • Since v1.229.0 (#3169, "vendor clean now preserves lockfile entries for future reinstalls"), there was no supported way to permanently remove a vendored source using the native lock: after deleting it from vendor.yaml, the orphan lock entry keeps its recorded files, and vendor verify then reports them as missing.
  • The issue (#3196) offered three candidate fixes. Auto-pruning orphans on vendor pull --refresh-lock was rejected: vendor.yaml and legacy component.yaml vendoring share one vendor.lock.yaml (both call lockfile.Record), so a full vendor pull blindly pruning non-declared artifacts could delete legitimate component.yaml lock entries. The surgical, zero-over-pruning fix is an explicit flag on vendor clean that forgets only what the selectors matched.

Usage

# Permanently remove a component: delete its files AND forget its lock entry,
# then remove its source from vendor.yaml (no orphan left for `vendor verify`).
atmos vendor clean --component vpc --prune-lock

references

  • Closes #3196
  • Docs: /cli/commands/vendor/vendor-clean
  • Fix record: docs/fixes/2026-09-22-vendor-clean-prune-lock.md

Summary by CodeRabbit

  • New Features

    • Added a --prune-lock option to atmos vendor clean for permanently removing selected vendored components and their lock-file entries.
    • Added dry-run support to preview files and lock entries that would be removed.
    • Lock-file entries remain preserved by default when cleaning.
  • Documentation

    • Updated command documentation with usage examples, selector behavior, pruning details, and dry-run guidance.
    • Added troubleshooting guidance for permanently removing vendored components.
fix(terraform): provision local-component workdir before backend/varfile generation @aknysh (#3198) ## what
  • For a local Terraform component (no JIT source:) with provision.workdir.enabled: true, provision the isolated workdir up front in ProvisionAndResolveComponentPath — before backend/varfile generation — instead of only at the before.terraform.init hook.
  • The generated backend.tf.json and *.tfvars.json now land in the per-run workdir instead of the shared source component directory.
  • Adds a regression test (TestProvisionAndResolveComponentPath_LocalComponentWorkdirProvisionedEarly) and a fix record under docs/fixes/.

why

  • Fixes the race in #3192. The Terraform working dir is resolved from info.ComponentSection[WorkdirPathKey]; when that key is unset, generation falls back to the source component dir.
  • For local components, the workdir copy (which sets WorkdirPathKey) ran at before.terraform.initafter runPreExecutionSteps had already written backend.tf.json/varfile. So those files were written into components/terraform/<component>/, polluting the source tree, and under atmos terraform plan --all --max-concurrency N parallel runs read/wrote the same source backend.tf.json concurrently, producing Error: ... The JSON data ends prematurely HCL parse failures.
  • JIT-source: components were unaffected because their workdir is set early (during source download).
  • The fix moves the (self-gating, idempotent) workdir provisioner ahead of generation. The later before.terraform.init run of the same provisioner becomes a safe no-op (it early-returns when WorkdirPathKey is already set), and the -reconfigure signal it records still persists on the shared ComponentSection.

references

  • Closes #3192
  • Follow-up (deferred): the documented global default settings.provision.workdir.enabled is still ignored (only component-level is honored) — tracked separately in #3197.
  • Fix record: docs/fixes/2026-09-21-workdir-backend-race-source-dir.md

Summary by CodeRabbit

  • Bug Fixes

    • Fixed concurrent Terraform runs for local components using isolated work directories.
    • Generated backend configuration and variable files are now placed in the appropriate per-run directory, preventing source-tree changes and read/write errors during parallel planning.
    • Ensured local component files are available in the isolated work directory before Terraform execution.
    • Preserved existing source-directory behavior for local non-Terraform components.
  • Documentation

    • Added documentation covering the work-directory race fix and a remaining limitation with the global work-directory setting.
fix(schema): allow ':' in auth/secret provider and identity names @aknysh (#3186) ## what
  • Relax the stack-manifest JSON Schema so auth.identities, auth.providers, and secrets.providers keys may contain a colon (:), enabling namespaced names such as example/prod:terraform_applier.
  • Change the auth_identities, auth_providers, and secret_providers patternProperties key pattern from ^[a-zA-Z0-9/_-]+$ to ^[a-zA-Z0-9/_:-]+$ in both the embedded schema (pkg/datafetcher/schema/atmos/manifest/1.0.json) and the hand-synced test fixture (tests/fixtures/schemas/atmos/atmos-manifest/1.0/atmos-manifest.json).
  • Restore drifted auth_identity / auth_provider definition parity in the test fixture (it still required kind and was missing the required/tags fields).
  • Add regression tests: schema-level validation for colon-containing identity, provider, and secret-provider names (plus a negative test that a name with a space is still rejected), runtime identity resolution with colon names, and runtime SOPS provider resolution with a colon name.

why

  • Component-level auth.identities rejected globally configured identity names containing :, even though the atmos.yaml config schema and the runtime identity model already accept them (identity/provider names are opaque map keys, resolved case-insensitively with no character validation).
  • Because additionalProperties was false, a namespaced identity like example/prod:terraform_applier failed stack validation before identity resolution and could not be selected as a component default.
  • secrets.providers shared the identical inconsistency (config schema accepts any key; manifest schema rejected :; provider names are opaque map keys at runtime), so it was relaxed in the same pass for consistency.
  • Terraform required_providers local names (^[a-zA-Z0-9-_]+$, no colons per Terraform's own rule) and component-instance-name patterns were reviewed and intentionally left unchanged.

references

  • Closes #3185
  • Fix record: docs/fixes/2026-09-17-auth-identity-name-colon-schema.md

Summary by CodeRabbit

  • New Features

    • Manifest names for authentication identities, authentication providers, and secret providers can now include colons (:), including namespaced formats.
    • Authentication identities and providers support optional tags for categorization and filtering.
    • Authentication identities can be marked as required for automatic authentication.
    • Authentication identity definitions no longer require a kind value.
  • Documentation

    • Added guidance covering the updated manifest schema and validation behavior.

Don't miss a new atmos release

NewReleases is sending notifications on new releases.