Skip to content

Internal InitCliConfig(schema.ConfigAndStacksInfo{}) re-invocations discard --config, breaking atmos terraform test/plan #2868

Description

Internal InitCliConfig(schema.ConfigAndStacksInfo{}) re-invocations discard --config, breaking atmos terraform test/plan

Describe the Bug

atmos --config <file> terraform plan/test <component> -s <stack> fails with
Error: failed to find import, even with a single, complete, valid atmos.yaml — no
merging, no multiple files, nothing exotic. The exact same --config invocation works
fine for atmos --config <file> list stacks, so --config clearly is parsed and honored
initially. Something specific to terraform subcommands loses it partway through.

Confirmed via debug logs: the first config load correctly merges the --config-specified
file. A later, separate internal re-invocation of config loading — specific to
terraform-command processing — then runs plain auto-discovery from scratch (system dir, home
dir, parent dirs, CWD) with no trace of the original --config selection, finds nothing (since
this reproduction's only valid config lives at the --config-specified path, not anywhere
auto-discovery looks), and that's what actually fails.

Root cause, traced to source: Atmos's root command derives config selection from os.Args
correctly, once, at startup:

// cmd/root.go
atmosConfig, initErr = cfg.InitCliConfig(cfg.EarlyConfigAndStacksInfoFromArgs(os.Args[1:]), false)

But a large number of other call sites across the codebase call InitCliConfig with a bare,
empty struct literal instead of re-deriving or re-threading the original selection:

atmosConfig, err := cfg.InitCliConfig(schema.ConfigAndStacksInfo{}, false)

An empty schema.ConfigAndStacksInfo{} means AtmosConfigFilesFromArg, AtmosConfigDirsFromArg,
and AtmosBasePath are all zero-value — so that specific InitCliConfig call has no idea
--config was ever passed, and falls straight through to plain auto-discovery. This exact
anti-pattern appears in (non-exhaustive — found via a single code search, more likely exist):

  • pkg/vendoring/resolve.go (two call sites)
  • cmd/cmd_utils.go
  • pkg/ai/tools/atmos/stack_config.go
  • cmd/aws/eks/update_kubeconfig_sdk.go (two call sites)
  • pkg/devcontainer/config_interface.go (two call sites)
  • pkg/telemetry/utils.go (two call sites)
  • cmd/profile/show.go (two call sites)

This is why ATMOS_CLI_CONFIG_PATH (env var) does not have this problem: it's read directly
via os.Getenv() inside readEnvAmosConfigPath()/readSystemConfig() on every call, with no
struct field needing to be threaded through. --config's parsed value, by contrast, only
survives if whoever calls InitCliConfig() explicitly passes it along — and evidently, for
whatever specific internal step re-processes config during terraform plan/test, nobody does.

Expected Behavior

Once --config/--config-path/--base-path is specified for an Atmos invocation, every
internal reload of the CLI config during that same invocation should honor the same selection
— either by reusing the already-loaded AtmosConfiguration, or by re-deriving it via
EarlyConfigAndStacksInfoFromArgs(os.Args[1:]) (the same mechanism cmd/root.go already uses
correctly), rather than constructing a fresh, empty ConfigAndStacksInfo{}.

Steps to Reproduce

Minimal, self-contained repro. Creates a throwaway project in a temp dir with atmos.yaml
living in its own subdirectory (never auto-discoverable on its own), then compares list stacks against terraform plan for the identical --config invocation, plus two follow-up
cases isolating why. Copy-paste the whole block into a terminal:

REPRO_DIR="$(mktemp -d)"
echo "Reproducing in: $REPRO_DIR"

# Install atmos if not already on PATH (kept outside REPRO_DIR so atmos's own
# config-file discovery doesn't try to parse the binary itself as YAML)
if ! command -v atmos >/dev/null 2>&1; then
  ATMOS_VERSION=1.225.0-rc.7
  ATMOS_BIN_DIR="$(mktemp -d)"
  curl -fsSL "https://github.com/cloudposse/atmos/releases/download/v${ATMOS_VERSION}/atmos_${ATMOS_VERSION}_linux_amd64" -o "$ATMOS_BIN_DIR/atmos"
  chmod +x "$ATMOS_BIN_DIR/atmos"
  ATMOS="$ATMOS_BIN_DIR/atmos"
else
  ATMOS=atmos
fi
$ATMOS version

cd "$REPRO_DIR"
git init -q
mkdir -p config stacks/deploy components/terraform/my-component

cat > config/atmos.yaml <<'EOF'
base_path: ''
components:
  terraform:
    base_path: components/terraform
stacks:
  base_path: stacks
  included_paths:
    - deploy/**/*
  name_pattern: '{stage}'
EOF

cat > stacks/deploy/dev.yaml <<'EOF'
vars:
  stage: dev
components:
  terraform:
    my-component:
      vars: {}
EOF

cat > components/terraform/my-component/main.tf <<'EOF'
variable "stage" {
  type = string
}

output "stage" {
  value = var.stage
}
EOF

echo "CASE 1: --config, single complete file, list stacks (works - --config is honored)"
$ATMOS --config config/atmos.yaml list stacks

echo "CASE 2: identical --config, same single file, terraform plan (fails)"
$ATMOS --config config/atmos.yaml terraform plan my-component -s dev || true

echo "CASE 3: same file also placed as bare ./atmos.yaml in CWD - terraform plan now works,"
echo "        because the internal re-invocation's auto-discovery happens to find THIS copy"
cp config/atmos.yaml ./atmos.yaml
$ATMOS --config config/atmos.yaml terraform plan my-component -s dev
rm ./atmos.yaml

echo "CASE 4: same file selected via ATMOS_CLI_CONFIG_PATH instead of --config - works,"
echo "        because env vars are read fresh by every re-invocation, unlike the CLI flag"
ATMOS_CLI_CONFIG_PATH="$REPRO_DIR/config" $ATMOS terraform plan my-component -s dev

Case 1 prints dev. Case 2 fails with Error: failed to find import. Cases 3 and 4 both
succeed with a normal terraform plan output (+ stage = "dev").

Screenshots

No screenshots — CLI output only, included in Steps to Reproduce above. The exact failure
for Case 2:

Error: failed to find import

💡 Verify that base_path and stacks.base_path in atmos.yaml are correct
💡 If using ATMOS_BASE_PATH, ensure the path is correct relative to the working directory
💡 Verify stacks.base_path in atmos.yaml points to the correct directory
💡 Check that the stacks directory exists and contains stack configuration files

Environment

Additional Context

Suggested fix direction: audit every call site matching
InitCliConfig(schema.ConfigAndStacksInfo{}, ...) (or equivalent bare/empty construction) and
replace it with either the already-loaded AtmosConfiguration/ConfigAndStacksInfo from
earlier in the same command's execution, or a fresh call to
EarlyConfigAndStacksInfoFromArgs(os.Args[1:]) — the same pattern cmd/root.go already uses
correctly at startup. This is likely not limited to terraform/helmfile/packer — the same
anti-pattern was found in vendoring, AI tools, EKS kubeconfig, devcontainer, telemetry, and
profile-command code, so any of those may exhibit the same loss of --config context mid-command
under the right conditions.

Relationship to existing issues: distinct from #2863 (fixed by #2864base_path never
resolving to git root within a single LoadConfig() call for --config) and from #2867
(array-typed keys not merging correctly across multiple --config files). This issue
reproduces with exactly one --config file and no merging of any kind — the failure is a
downstream re-invocation losing the --config selection entirely, not a resolution or merge
defect within a single load.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions