Skip to content

feat: command-path aliases with default-flag injection - #2572

Open
Erik Osterman (Cloud Posse) (osterman) wants to merge 20 commits into
mainfrom
osterman/profile-flag-defaults
Open

Erik Osterman (Cloud Posse) (osterman) wants to merge 20 commits into
mainfrom
osterman/profile-flag-defaults

Conversation

@osterman

@osterman Erik Osterman (Cloud Posse) (osterman) commented Jun 5, 2026 •

Copy link
Copy Markdown
Member

what

  • Command aliases in atmos.yaml now support multi-word command-path keys (e.g.
    "terraform apply", "describe component") in addition to single-word shortcuts, so an alias can
    target a subcommand, not just a top-level command.
  • Same-name aliases inject default flags — terraform: terraform --identity=false rewrites
    atmos terraform ... to atmos terraform --identity=false ... before Cobra parses, so any command
    can get default flags without per-command wiring.
  • Injected default flags are suppressed when the user (or an ATMOS_* env var) already sets that
    flag
    , so precedence is CLI > ENV > alias-injected default > built-in.
  • Implemented as a single argv preprocessor (cmd/internal/alias_expander.go:
    ExpandCommandAliases/AliasExpander.Expand) with longest-prefix matching, cycle detection, and a
    max expansion depth; no changes to flag parsing itself.

why

  • Every flag's default was hard-coded — there was no config-file way to disable identity/auth or
    YAML-function processing by default, per command, or to vary it per environment.
  • We initially built this as a separate args:/<command-path>.args: config section
    (flags.InjectDefaultArgs), but that introduced a second config surface doing almost the same job
    as aliases:. Folding default-flag injection into the existing alias mechanism (same-name
    command-path aliases) gives one mental model for shortcuts and defaults, instead of two.

references

  • PRD: docs/prd/command-path-aliases.md
  • Docs: /cli/configuration/aliases
  • Changelog: command-path-aliases
  • Roadmap: website/src/data/roadmap.js (pr 2572, changelog command-path-aliases)

Summary by CodeRabbit

  • New Features

    • Inject configurable default CLI arguments per command (global + per-command) into the argv stream before parsing; defaults are spliced after the command and suppressed when overridden by CLI flags or environment variables. Merged configuration (including profiles) is captured for lookup.
  • Documentation

    • Added docs and a blog post describing configuration, precedence, examples, and usage.
  • Tests

    • Added unit tests covering default-arg collection, injection, suppression, and helper behaviors.

Add default arguments configurable per command in atmos.yaml via a
path-derived `args:` list (top-level `args:` for global, `<command>.args`
per command, most-specific wins). Default args are raw pass-through
tokens spliced onto the command line before parsing — uniform across all
commands, supporting any flag, repeated flag, or positional, with no
per-command wiring.

Precedence: CLI > ENV > command args > global args > built-in. A default
is skipped when its flag is already on the CLI or its env var is set.
Overridable by Atmos profiles, which merge into the same config.

Implemented as a single argv preprocessor (flags.InjectDefaultArgs) run
before Cobra parses, plus a captured merged-settings map
(AtmosConfiguration.RawConfig) populated during config load.

Includes PRD, changelog blog post, roadmap milestone, and docs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@atmos-pro

atmos-pro Bot commented Jun 5, 2026 •

Copy link
Copy Markdown
Contributor

Tip

Atmos Pro  

No affected stacks workflow was detected for this pull request.
If this is expected, no action is needed.
Learn More. Ask AI.

@osterman Erik Osterman (Cloud Posse) (osterman) added the minor New features that do not break anything label Jun 5, 2026
@github-actions github-actions Bot added the size/m Medium size PR label Jun 5, 2026
@github-actions

github-actions Bot commented Jun 5, 2026 •

Copy link
Copy Markdown

Dependency Review

✅ No vulnerabilities or license issues found.

Scanned Files

None

@coderabbitai

coderabbitai Bot commented Jun 5, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Stores merged atmos.yaml as RawConfig, injects config-specified default CLI tokens into argv before Cobra parses, filters them when overridden by CLI or env, splices remaining defaults after the command path, and adds tests, PRD/docs/blog, and a roadmap entry.

Changes

Per-command default args from atmos.yaml

Layer / File(s) Summary
Config schema and raw settings capture
pkg/schema/schema.go, pkg/config/load.go
AtmosConfiguration.RawConfig stores merged atmos.yaml as a generic map after profile application, enabling path-derived lookups for per-command defaults.
Config-layer testing for merged settings and profiles
pkg/config/arg_defaults_test.go
Tests verify that merged global and per-command args are visible in config, and that configuration profiles override base values.
Argv injection and core helpers
pkg/flags/arg_defaults.go
InjectDefaultArgs locates the target command, collects defaults from merged RawConfig along the command path, filters defaults suppressed by CLI flags or environment variables, and splices remaining defaults into argv after command-path tokens.
Flag parsing, presence detection, and env checks
pkg/flags/arg_defaults.go
Helpers extract flag names from tokens, detect flag presence (respecting --), check conventional and registered env vars, and normalize config args values into []string.
Unit tests for injection logic
pkg/flags/arg_defaults_test.go
Comprehensive tests cover list normalization, flag extraction, presence detection, cross-scope default collection, suppression by CLI/env, splice placement, help no-op, and nil-config behavior.
CLI preprocessor integration
cmd/root.go
InjectDefaultArgs is called in preprocessArgs() as step 0 before existing flag preprocessing. SetArgs logic preserves injected defaults by comparing processed args to original os.Args[1:].
Documentation, PRD, blog, and roadmap
docs/prd/per-command-default-args.md, website/docs/cli/configuration/default-args.mdx, website/blog/2026-06-05-per-command-default-args.mdx, website/src/data/roadmap.js
PRD documents architecture and goals; docs and blog provide usage and precedence examples; roadmap updated with shipped milestone.

Sequence Diagram

sequenceDiagram
  participant CLI as User CLI
  participant Injector as InjectDefaultArgs
  participant Cobra as Cobra (rootCmd)
  participant Raw as RawConfig (merged settings)
  participant Filter as filterDefaultArgs
  participant Root as RootCmd

  CLI->>Injector: args, rootCmd, rawConfig
  Injector->>Cobra: rootCmd.Find(command path)
  Injector->>Raw: collectDefaultArgs(command path)
  Raw-->>Injector: default tokens
  Injector->>Filter: filterDefaultArgs(defaults, remainingArgs)
  Filter-->>Injector: filtered defaults
  Injector->>Root: spliceDefaultArgs -> SetArgs(processedArgs)
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • aknysh
  • nitrocode
  • kevcube
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title points to the core default-flag injection feature, though "command-path aliases" is not actually part of the changes.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch osterman/profile-flag-defaults

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@pkg/flags/arg_defaults_test.go`:
- Around line 72-119: Add a regression test in TestInjectDefaultArgs to cover
split-token defaults: create a raw config where describe.args is
[]any{"--identity","default"} and add a t.Run that calls InjectDefaultArgs(root,
raw, []string{"describe","component","vpc","--identity","me"}) and asserts the
result equals the input (both tokens removed/not injected). Also add a subcase
that sets the corresponding env var (e.g., t.Setenv("ATMOS_IDENTITY","me")) and
asserts InjectDefaultArgs(root, raw, []string{"describe","component","vpc"})
returns the input unchanged; this ensures InjectDefaultArgs correctly detects
and suppresses split-token defaults for both CLI and env wins.

In `@pkg/flags/arg_defaults.go`:
- Around line 119-127: filterDefaultArgs currently iterates defaults and, when
suppressing a flag (using flagNameFromArg/flagPresentInArgs/flagEnvIsSet), only
skips the flag token which leaves any following separate value token (e.g.
"--identity", "default") orphaned; change the loop in filterDefaultArgs to
iterate by index so when you decide to continue for a suppressed flag you also
check the next token and, if the current default is a standalone flag token (no
"=" form) and the next token exists and does not start with "-", increment the
index to skip that value token as well; use flagNameFromArg, flagPresentInArgs
and flagEnvIsSet to detect suppression and guard skipping the next token only
when appropriate.

In `@website/src/data/roadmap.js`:
- Line 230: The roadmap milestone object with label 'Per-command default args in
atmos.yaml' in website/src/data/roadmap.js is missing the required pr field; add
a pr: <number> property to that milestone object (the same object that has
status: 'shipped', quarter: 'q2-2026', changelog: 'per-command-default-args',
docs: '/cli/configuration/default-args', description: ..., benefits: ...) and
ensure you include the trailing comma/formatting consistent with other entries.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 9ef715e3-6d8e-457a-b130-6ffec48898d3

📥 Commits

Reviewing files that changed from the base of the PR and between 3c0b3e5 and 15944d1.

📒 Files selected for processing (10)
  • cmd/root.go
  • docs/prd/per-command-default-args.md
  • pkg/config/arg_defaults_test.go
  • pkg/config/load.go
  • pkg/flags/arg_defaults.go
  • pkg/flags/arg_defaults_test.go
  • pkg/schema/schema.go
  • website/blog/2026-06-05-per-command-default-args.mdx
  • website/docs/cli/configuration/default-args.mdx
  • website/src/data/roadmap.js

Comment thread pkg/flags/arg_defaults_test.go Outdated
Comment thread pkg/flags/arg_defaults.go Outdated
Comment thread website/src/data/roadmap.js Outdated
@codecov

codecov Bot commented Jun 5, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 80.12422% with 32 lines in your changes missing coverage. Please review.
✅ Project coverage is 81.34%. Comparing base (86edc83) to head (c5fe3d2).
⚠️ Report is 19 commits behind head on main.

Files with missing lines Patch % Lines
cmd/internal/alias_expander.go 87.21% 9 Missing and 8 partials ⚠️
cmd/root.go 42.10% 7 Missing and 4 partials ⚠️
cmd/cmd_utils.go 33.33% 3 Missing and 1 partial ⚠️

❌ Your patch check has failed because the patch coverage (80.12%) is below the target coverage (85.00%). You can increase the patch coverage or adjust the target coverage.

Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main    #2572      +/-   ##
==========================================
+ Coverage   81.32%   81.34%   +0.02%     
==========================================
  Files        1641     1642       +1     
  Lines      154729   154883     +154     
==========================================
+ Hits       125830   125987     +157     
+ Misses      21974    21962      -12     
- Partials     6925     6934       +9     
Flag Coverage Δ
unittests 81.34% <80.12%> (+0.02%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
cmd/internal/registry.go 89.25% <100.00%> (+0.27%) ⬆️
cmd/cmd_utils.go 68.94% <33.33%> (-0.14%) ⬇️
cmd/root.go 70.88% <42.10%> (-0.61%) ⬇️
cmd/internal/alias_expander.go 87.21% <87.21%> (ø)

... and 9 files with indirect coverage changes

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

…fault args

filterDefaultArgs suppressed a default when the same flag was already set on
the CLI or via env, but dropped only the flag token. For split-token defaults
like `args: ["--identity", "default"]`, the bare "default" token was left behind
and spliced onto the command line as a positional argument, silently corrupting
the command.

filterDefaultArgs now also drops the flag's paired space-separated value token.
The pairing mirrors the existing NoOptDefVal preprocessor (a bare flag whose
next token is a non-flag value), so it correctly handles NoOptDefVal flags such
as `--identity` — unlike a NoOptDefVal-based check, which would misclassify them.

Also adds regression tests for split-token suppression (CLI/ENV/not-suppressed),
unit tests for the new helpers, and `pr: 2572` on the roadmap milestone.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ide and positionals

Remove the "Why Args Instead of a Typed Map" section from the blog post — it is
internal design rationale (already covered in the PRD), not end-user content.

Clarify two points that were unclear in the configuration reference:
- Overriding a broader default at a deeper command path (most-specific wins via
  last-value-wins), with a concrete global-vs-subcommand example.
- Flags vs. positional arguments: both are passed through as raw tokens, but
  positionals are never suppressed and are injected before typed args, so a
  defaulted positional adds to (rather than replaces) what you type.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 6, 2026
The original feature request was about setting a list-valued flag. Add a
dedicated "Repeated flags (list values)" section showing how to default a
list by repeating the flag (e.g. `--skip`, Terraform `-var`), with the
equivalent command line.

Document the replace-not-extend behavior: passing an Atmos flag once on the
command line suppresses all of its configured defaults. Tool-native flags
(single-dash, e.g. `-var`) are not Atmos-suppressed — they pass straight
through and the tool's own parsing decides how values combine.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
coderabbitai[bot]
coderabbitai Bot previously approved these changes Jun 10, 2026
Show defaulting list-command flags: `list.args: [--format=json]` applies across
the whole `list` subtree (list stacks, list components, list values, …), with a
subcommand-specific `list.components.args: [--max-columns=5]`, overridable on the
command line.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…lags

Verified the example end-to-end against examples/demo-stacks: `list.args:
[--format=json]` injects across the whole list subtree (list stacks/components/
values), and CLI flags override it.

The prior draft used `--max-columns` under `list.components`, which is invalid —
that flag belongs to `list instances`/`list values`, and an injected flag the
target subcommand does not define errors out. Switched the subcommand example to
`--type=all` (a real `list components` flag) and added a note that injected flags
must be valid for the targeted subcommand.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@github-actions

github-actions Bot commented Jul 4, 2026 •

Copy link
Copy Markdown

Note

Release Documentation Complete ✅

  • Changelog: website/blog/2026-07-04-command-path-aliases.mdx
  • Roadmap: website/src/data/roadmap.js

Thank you!

@mergify

mergify Bot commented Jul 6, 2026

Copy link
Copy Markdown
Contributor

💥 This pull request now has conflicts. Could you fix it Erik Osterman (Cloud Posse) (@osterman)? 🙏

@mergify mergify Bot added the conflict This PR has conflicts label Jul 6, 2026
@mergify mergify Bot removed the conflict This PR has conflicts label Jul 6, 2026
@github-actions

github-actions Bot commented Jul 6, 2026 •

Copy link
Copy Markdown

Resource Changes Found for bucket in test

Atmos CI

create

Plan: 4 to add, 0 to change, 0 to destroy.
To reproduce this locally, run:

atmos terraform plan bucket -s test

Create

+ aws_s3_bucket.checkov_target
+ aws_s3_bucket.this
+ aws_s3_bucket.trivy_target
+ aws_s3_bucket_public_access_block.trivy_target
Terraform Plan Summary
  # aws_s3_bucket.checkov_target will be created
  + resource "aws_s3_bucket" "checkov_target" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "atmos-native-ci-e2e-checkov-test"
      + bucket_domain_name          = (known after apply)
      + bucket_prefix               = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + tags_all                    = (known after apply)
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)

      + cors_rule (known after apply)

      + grant (known after apply)

      + lifecycle_rule (known after apply)

      + logging (known after apply)

      + object_lock_configuration (known after apply)

      + replication_configuration (known after apply)

      + server_side_encryption_configuration (known after apply)

      + versioning (known after apply)

      + website (known after apply)
    }

  # aws_s3_bucket.this will be created
  + resource "aws_s3_bucket" "this" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "atmos-native-ci-e2e-test"
      + bucket_domain_name          = (known after apply)
      + bucket_prefix               = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + tags                        = {
          + "AtmosFixture" = "native-ci-e2e"
          + "Stage"        = "test"
        }
      + tags_all                    = {
          + "AtmosFixture" = "native-ci-e2e"
          + "Stage"        = "test"
        }
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)

      + cors_rule (known after apply)

      + grant (known after apply)

      + lifecycle_rule (known after apply)

      + logging (known after apply)

      + object_lock_configuration (known after apply)

      + replication_configuration (known after apply)

      + server_side_encryption_configuration (known after apply)

      + versioning (known after apply)

      + website (known after apply)
    }

  # aws_s3_bucket.trivy_target will be created
  + resource "aws_s3_bucket" "trivy_target" {
      + acceleration_status         = (known after apply)
      + acl                         = (known after apply)
      + arn                         = (known after apply)
      + bucket                      = "atmos-native-ci-e2e-trivy-test"
      + bucket_domain_name          = (known after apply)
      + bucket_prefix               = (known after apply)
      + bucket_regional_domain_name = (known after apply)
      + force_destroy               = false
      + hosted_zone_id              = (known after apply)
      + id                          = (known after apply)
      + object_lock_enabled         = (known after apply)
      + policy                      = (known after apply)
      + region                      = (known after apply)
      + request_payer               = (known after apply)
      + tags_all                    = (known after apply)
      + website_domain              = (known after apply)
      + website_endpoint            = (known after apply)

      + cors_rule (known after apply)

      + grant (known after apply)

      + lifecycle_rule (known after apply)

      + logging (known after apply)

      + object_lock_configuration (known after apply)

      + replication_configuration (known after apply)

      + server_side_encryption_configuration (known after apply)

      + versioning (known after apply)

      + website (known after apply)
    }

  # aws_s3_bucket_public_access_block.trivy_target will be created
  + resource "aws_s3_bucket_public_access_block" "trivy_target" {
      + block_public_acls       = true
      + block_public_policy     = true
      + bucket                  = (known after apply)
      + id                      = (known after apply)
      + ignore_public_acls      = true
      + restrict_public_buckets = true
    }

Plan: 4 to add, 0 to change, 0 to destroy.

Changes to Outputs:
  + bucket_name = "atmos-native-ci-e2e-test"

@osterman Erik Osterman (Cloud Posse) (osterman) changed the title feat: per-command default args in atmos.yaml feat: command-path aliases with default-flag injection Jul 7, 2026
…efault-args PRD

The original per-command-default-args.md PRD was deleted wholesale when the
implementation pivoted from a standalone args: config section to folding
default-flag injection into the existing aliases: mechanism, leaving the
concluded design undocumented. Documents the alias_expander.go mechanics and
lists the abandoned args:-based approach as an alternative considered.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@mergify

mergify Bot commented Jul 8, 2026

Copy link
Copy Markdown
Contributor

💥 This pull request now has conflicts. Could you fix it Erik Osterman (Cloud Posse) (@osterman)? 🙏

@mergify mergify Bot added the conflict This PR has conflicts label Jul 8, 2026
…-defaults

# Conflicts:
#	website/src/data/roadmap.js
@mergify

mergify Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

💥 This pull request now has conflicts. Could you fix it Erik Osterman (Cloud Posse) (@osterman)? 🙏

@mergify mergify Bot added the conflict This PR has conflicts label Jul 12, 2026
…-defaults

# Conflicts:
#	website/src/data/roadmap.js
@mergify

mergify Bot commented Jul 16, 2026

Copy link
Copy Markdown
Contributor

💥 This pull request now has conflicts. Could you fix it Erik Osterman (Cloud Posse) (@osterman)? 🙏

@mergify mergify Bot added the conflict This PR has conflicts label Jul 16, 2026

This branch was successfully deployed

1 active deployment
preview — c5fe3d20 Deployed Jul 12, 2026 by github-actions[bot]
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

conflict This PR has conflicts minor New features that do not break anything size/m Medium size PR

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant