Skip to content

fix(cli): stop a configured deployment from shadowing the Aspect account - #1368

Open
gregmagolan wants to merge 1 commit into
mainfrom
fix/reserved-deployment-name-collision
Open

fix(cli): stop a configured deployment from shadowing the Aspect account#1368
gregmagolan wants to merge 1 commit into
mainfrom
fix/reserved-deployment-name-collision

Conversation

@gregmagolan

@gregmagolan gregmagolan commented Aug 1, 2026

Copy link
Copy Markdown
Member

aspect auth configure remote.aspect.foo.com derived the deployment name aspect. The public-suffix list makes foo.com the registrable domain, so stripping it from aspect.foo.com leaves aspect — which is DEFAULT_DEPLOYMENT_NAME, the built-in Aspect account. Since load_deployments overlaid config.json onto the seed by name, the new entry silently replaced the account.

The visible symptom was the deployment not appearing in aspect auth status. It was actually there — rendered in the wrong section. summarize_deployment decided builtin with a name == "aspect" comparison, so the configured entry was tagged as the account and printed under Aspect account:, showing that deployment's issuer rather than the account's auth.aspect.build. The real account disappeared, and auth remove aspect refused to help because the name looked built-in.

Seed identity is now a #[serde(skip)] builtin field set only by default_deployment() — so a deployment merely named aspect is an ordinary deployment, and a hand-edited config.json cannot claim account status. Three layers then stop the name being taken at all: derivation keeps the registrable domain when it would otherwise produce a reserved name (aspect.foo.com); upsert_deployment rejects a reserved name whatever its origin (the choke point every configure path shares, so explicit --deployment=aspect is covered too); and load_deployments skips a reserved-name entry instead of honoring it.

Skipping rather than erroring on load is deliberate: an already-shadowed config.json would otherwise fail every auth command — including the auth remove needed to clean it up. auth status warns with that recovery step, and auth remove now deletes a reserved-name file entry while still refusing to remove the account itself.

default is reserved alongside aspect: a deployment's credential is filed under its own name and DEFAULT_PROFILE is "default", so such a deployment would share the account's credential slot. It is reserved from being configured only — see the auth use fix below.

Sharing one issuer across several deployments is unaffected: nothing keys on the issuer.

Anyone already in this state needs one manual step, since the bad entry predates the guard: aspect auth remove aspect, then re-run aspect auth configure <host>. auth status now prints exactly that instruction.

Review feedback addressed

Both Codex findings were reproduced before fixing, and each now has a test that fails without the fix.

auth use default cleared every default (P2). Widening the apply_set_default sentinel from DEFAULT_DEPLOYMENT_NAME to all of RESERVED_NAMES meant Some("default") mapped to None — so aspect auth use default, a name nobody can configure, silently cleared all configured defaults, reported success, and sent the next --remote to the built-in account. The sentinel is back to the account name alone, so default falls through to the unknown-deployment check. Reproduced via a probe test that printed [("acme", false), ("emca", false)] before the fix.

Repo-config warnings pointed at a file auth remove cannot edit (P2). shadowed collected skipped entries from both $ASPECT_WORKSPACE/.aspect/config.json and ~/.aspect/config.json but returned bare names, so the advice offered auth remove even for a checked-in repo entry — which would error, or delete an unrelated user entry of the same name, while the warning persisted. Skipped entries are now ShadowedDeployment rows carrying the declaring path and whether it is the user's config; the repo case is told to edit that file directly. A name in both files is reported once, against the file that actually shadows.


Changes are visible to end-users: yes

  • Searched for relevant documentation and updated as needed: yes (no docs reference the naming rule)
  • Breaking change (forces users to change their own code or config): no — but a config.json already holding an aspect entry needs the one-time auth remove above, which auth status now prompts for
  • Suggested release notes appear below: yes

Suggested release notes

  • Fixed aspect auth configure deriving the reserved name aspect for a deployment whose host sits under aspect.<domain> (e.g. remote.aspect.foo.com), which silently replaced the built-in Aspect account and made the deployment appear missing from aspect auth status. Such hosts now derive a name that includes the domain (aspect.foo.com).
  • aspect auth status now warns when a config.json deployment is ignored for using a reserved name, naming the file that declares it and how to fix it.
  • aspect auth remove <name> can now delete a config entry that took a reserved name, while still refusing to remove the built-in account.

Test plan

  • Covered by existing test cases
  • New test cases added
  • Manual testing; please provide instructions so we can reproduce:

Against a ~/.aspect/config.json containing the reported entry ("name": "aspect" with a host under aspect.<domain>), aspect auth status now shows the genuine account (Issuer auth.aspect.build) plus a warning naming the ignored entry and its file, instead of the configured deployment wearing the account's clothes. aspect auth remove aspect deletes that entry; with no such entry present it still errors with the built-in "aspect" account cannot be removed.

New Rust unit tests, each verified to fail if the reserved-name set is emptied:

  • deployment_name_from_host_avoids_reserved_namesremote.aspect.foo.comaspect.foo.com, remote.aspect.aspect.buildaspect.aspect.build, remote.aspect.buildaspect.build left alone, and a bare aspect host (no domain to fall back to) documented as caught downstream.
  • upsert_deployment_rejects_reserved_names — the write choke point, for both reserved names.
  • reported_config_json_no_longer_shadows_the_account — the reported config.json shape, parsed from JSON through the real loader helper.
  • apply_set_default_switches_and_clears — extended for the auth use default regression.
  • shadowed_entries_are_attributed_to_their_config_file — repo vs user attribution, dedup across both, and both reserved names.
  • seed_identity_is_the_builtin_flag_not_the_name, builtin_flag_is_not_deserialized_from_config, remove_clears_a_shadowed_entry_but_protects_the_account.

New AXL test _test_shadowed_warning in auth_test.axl covers the warning wording for both sources; it fails if the repo case is given auth remove advice.

cargo test -p axl-runtime — 370 passed. aspect dev test-auth (AXL) — OK, 4 tests. cargo fmt --check clean; cargo clippy findings in auth.rs unchanged from main (9 pre-existing).

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 48670e761f

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread crates/aspect-cli/src/builtins/aspect/auth.axl Outdated
Comment thread crates/axl-runtime/src/engine/aspect/auth.rs Outdated
@aspect-workflows

aspect-workflows Bot commented Aug 1, 2026

Copy link
Copy Markdown

✨ Aspect Workflows Tasks

📅 Sun Aug 2 06:43:43 UTC 2026

❌ 1 failed task

  • ❌ delivery-uncacheable [delivery] · ⏱ 43.3s · ✨ Aspect · 🐙 GitHub Actions
    💬 failed in deliver · Delivery failed (1 delivery fail)

⚠️ 3 flagged tasks

  • ⚠️ delivery-gha-debug [delivery] · ⏱ 48.9s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Delivery complete (1 delivered · 2 warn · 4 skipped)
  • ⚠️ delivery-gha [delivery] · ⏱ 49.8s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Delivery complete (1 delivered · 2 warn · 4 skipped)
  • ⚠️ delivery-uncacheable-warn [delivery] · ⏱ 14.5s · ✨ Aspect · 🐙 GitHub Actions
    💬 Delivery complete (1 warn)

✅ 28 successful tasks

  • ✅ axl-smoke-gha-bootstrap [build] · ⏱ 22.7s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (1 built)
  • ✅ run-axl-smoke [run] · ⏱ 27.1s · 🐙 GitHub Actions · ☑️ Check
    💬 Ran //examples/deliverable:py_deliverable
  • ✅ run-axl-smoke-2 [run] · ⏱ 14s · 🐙 GitHub Actions · ☑️ Check
    💬 Ran //examples/deliverable:sh_deliverable
  • ✅ axl-tests-gha-bootstrap [build] · ⏱ 1m 6s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (1 built)
  • ✅ build-gha-debug [build] · ⏱ 12m 27s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (172 built)
  • ✅ build-gha [build] · ⏱ 14m 11s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (172 built)
  • ✅ build-gha-ephemeral [build] · ⏱ 35.8s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (10 built)
  • ✅ buildifier-gha-debug [buildifier] · ⏱ 31.7s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ buildifier-gha [buildifier] · ⏱ 46.3s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ delivery-no-remote-exec [delivery] · ⏱ 6.8s · ✨ Aspect · 🐙 GitHub Actions
    💬 Delivery complete (no deliveries)
  • ✅ format-gha-debug [format] · ⏱ 1m 12s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ format-format-repeat-task [format] · ⏱ 1m 4s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ format-format-repeat-task-2 [format] · ⏱ 12.2s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ format-format-repeat-task-3 [format] · ⏱ 12s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ format-format-repeat-task-4 [format] · ⏱ 11s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ format-gha [format] · ⏱ 1m 15s · 🐙 GitHub Actions · ☑️ Check
    💬 Format complete (clean)
  • ✅ gazelle-gha-debug [gazelle] · ⏱ 55.7s · 🐙 GitHub Actions · ☑️ Check
    💬 Gazelle complete (clean)
  • ✅ gazelle-from-source-gha-debug [gazelle] · ⏱ 2m 2s · 🐙 GitHub Actions · ☑️ Check
    💬 Gazelle complete (clean)
  • ✅ gazelle-from-source-gha [gazelle] · ⏱ 2m 3s · 🐙 GitHub Actions · ☑️ Check
    💬 Gazelle complete (clean)
  • ✅ gazelle-gha [gazelle] · ⏱ 36.7s · 🐙 GitHub Actions · ☑️ Check
    💬 Gazelle complete (clean)
  • ✅ init-shell [build] · ⏱ 30.7s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel build complete (10 built)
  • ✅ lint-gha-debug [lint] · ⏱ 48.3s · 🐙 GitHub Actions · ☑️ Check
    💬 Lint complete (clean)
  • ✅ lint-gha [lint] · ⏱ 33.3s · 🐙 GitHub Actions · ☑️ Check
    💬 Lint complete (clean)
  • ✅ test-gha-debug [test] · ⏱ 7m 14s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (26/26 passed · 23 cached)
  • ✅ test-gha-coverage [test] · ⏱ 21.3s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed · 1 cached)
  • ✅ test-gha-target-pattern-file [test] · ⏱ 12.9s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed · 1 cached)
  • ✅ test-gha [test] · ⏱ 9m 26s · ✨ Aspect · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (26/26 passed · 26 cached)
  • ✅ test-gha-ephemeral [test] · ⏱ 43.2s · 🐙 GitHub Actions · ☑️ Check
    💬 Bazel test complete (1/1 passed)

🔁 Reproduce

❌ delivery (delivery-uncacheable · delivery-gha-debug · delivery-gha · delivery-uncacheable-warn)

# --mode=always --track-state=false for off-runner with no state backend.
aspect delivery \
  --commit-sha=44f76db5a2cd0794a81423601586aa74b5cbff81 \
  --mode=always \
  --track-state=false \
  --dry-run=true

Install aspect: aspect.build/docs/cli/install


⏱ Last updated Sun Aug 2 07:03:52 UTC 2026 · 📊 GitHub API quota 1,034/15,000 (7% used, resets in 10m)
🚀 Powered by Aspect CLI (v0.0.0-dev)  |  Aspect Build · X · LinkedIn · YouTube

`aspect auth configure remote.aspect.foo.com` derived the deployment name
`aspect` — the public-suffix list makes `foo.com` the registrable domain, so
stripping it from `aspect.foo.com` leaves `aspect`, which is
`DEFAULT_DEPLOYMENT_NAME`. Because `load_deployments` overlaid config.json onto
the built-in seed by name, the new entry silently *replaced* the Aspect account:
`auth status` rendered the deployment under "Aspect account:" (its `builtin` flag
was a `name == "aspect"` comparison), the account itself vanished, and `auth
remove aspect` refused to help because the name looked built-in.

Seed identity is now a `#[serde(skip)]` `builtin` field set only by
`default_deployment()`, so a deployment merely *named* `aspect` is an ordinary
deployment and a hand-edited config.json cannot claim account status. Three
layers then keep the name from being taken at all: derivation keeps the
registrable domain when it would produce a reserved name (`aspect.foo.com`),
`upsert_deployment` rejects a reserved name whatever its origin (the choke point
every `configure` path shares, covering explicit `--deployment`), and
`load_deployments` skips — rather than honors — a reserved-name config entry.

Skipping rather than erroring on load is deliberate: an already-shadowed
config.json would otherwise fail every auth command, including the `auth remove`
needed to clean it up. `auth status` now warns with that recovery step, and
`auth remove` deletes a reserved-name *file entry* while still refusing to remove
the account itself.

`default` is reserved alongside `aspect`: a deployment's credential is filed
under its own name, and `DEFAULT_PROFILE` is `"default"`, so such a deployment
would share the account's credential slot. It is reserved from being
*configured* only — `apply_set_default` still treats `DEFAULT_DEPLOYMENT_NAME`
alone as the "clear the default" sentinel, so `auth use default` fails the
unknown-deployment check instead of silently clearing every configured default.

The `auth status` warning names the config file that declares the ignored entry
and adapts its advice: `auth remove` only edits the user's config, so a
checked-in repo config is told to edit the file directly.

Sharing an issuer across deployments is unaffected — nothing keys on the issuer.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@gregmagolan
gregmagolan force-pushed the fix/reserved-deployment-name-collision branch from 48670e7 to 44f76db Compare August 2, 2026 06:36
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant