From 1f19a92957a66be038fc6ef359cd9b55f7c9827c Mon Sep 17 00:00:00 2001 From: Anders Huss <5502349+andhus@users.noreply.github.com> Date: Fri, 8 May 2026 11:10:58 +0200 Subject: [PATCH 1/2] Fix misleading slug display in stardag modal CLI (v0.7.3) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The workspace/environment slug printed by `stardag modal deploy` and `stardag modal stardag-api-key create` was read from the active CLI profile's TOML, so when env vars or a custom `config_provider` override the resolved IDs the slug could refer to a different env than the resolved UUID — e.g. hypothetically `Environment: (env-b-slug)` where the UUID resolves to env A but the slug is read from a profile pointing at env B. Replace `_get_profile_slugs` with `_resolve_display_slugs` which reverse-looks up the slug from the resolved UUID via the id-cache, falling back to omitting the slug when there is no cache hit. Co-Authored-By: Claude Opus 4.7 (1M context) --- CHANGELOG.md | 22 ++++++++++ RELEASE_NOTES.md | 22 ++++++++++ lib/stardag/src/stardag/_cli/modal.py | 55 ++++++++++++------------- lib/stardag/src/stardag/config/cache.py | 21 ++++++++++ 4 files changed, 92 insertions(+), 28 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b9a8f2c0..e6796652 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,28 @@ For detailed SDK migration guides, see [RELEASE_NOTES.md](RELEASE_NOTES.md). ## [Unreleased] +## [0.7.3] — 2026-05-08 + +`stardag modal deploy` and `stardag modal stardag-api-key create` now +display the slug that actually corresponds to the resolved +workspace/environment UUID. Previously the slug was read from the active +CLI profile's TOML, which could be unrelated to the resolved UUID when +env vars or a custom `config_provider` override the IDs — producing +misleading lines pairing the resolved UUID with a slug from an unrelated +profile. No client-code changes — `pip install -U stardag` is +sufficient. See +[RELEASE_NOTES.md](RELEASE_NOTES.md#v073--correct-slug-display-in-stardag-modal-cli) +for details. + +### SDK + +- **`stardag/_cli/modal.py`**: replaced `_get_profile_slugs` with + `_resolve_display_slugs`, which reverse-looks up the slug from the + resolved UUID via the id-cache. Slug is omitted when no cache hit + rather than guessing from the active profile. +- **`stardag/config/cache.py`**: added `get_cached_workspace_slug` and + `get_cached_environment_slug` (UUID → slug reverse lookups). + ## [0.7.2] — 2026-05-08 `sd.build(resume_build_id=...)` now fires a `BUILD_RESUMED` event so diff --git a/RELEASE_NOTES.md b/RELEASE_NOTES.md index a80efc9d..8edf00e0 100644 --- a/RELEASE_NOTES.md +++ b/RELEASE_NOTES.md @@ -6,6 +6,28 @@ For changes to the Registry API, UI, and other components, see [CHANGELOG.md](CH --- +## v0.7.3 — Correct slug display in `stardag modal` CLI + +Fixes a misleading display in `stardag modal deploy` and +`stardag modal stardag-api-key create` where the workspace/environment +slug printed alongside the resolved UUID could come from a different +profile than the one whose IDs were actually used. The CLI now +reverse-looks up the slug from the resolved UUID via the id-cache, or +omits it when no cache entry exists. + +This typically affected setups that override `STARDAG_WORKSPACE_ID` / +`STARDAG_ENVIRONMENT_ID` via env vars or a custom `config_provider` +default factory while leaving an unrelated profile active — for example +hypothetical output like +`Environment: (env-b-slug)`, where the UUID resolves to +environment A but the slug is read from a profile pointing at +environment B. The deployed bytes were already correct; only the +printed slug was wrong. + +**No client-code changes** — `pip install -U stardag` is sufficient. + +--- + ## v0.7.2 — Build resume status fix and SKIPPED UI polish Fixes a UX bug where `sd.build(resume_build_id=...)` silently reused the diff --git a/lib/stardag/src/stardag/_cli/modal.py b/lib/stardag/src/stardag/_cli/modal.py index 2bca9e46..7ab8d0c6 100644 --- a/lib/stardag/src/stardag/_cli/modal.py +++ b/lib/stardag/src/stardag/_cli/modal.py @@ -30,11 +30,13 @@ from stardag._cli._helpers import get_authenticated_client from stardag._cli.credentials import ( - get_active_profile, - list_profiles, resolve_environment_slug_to_id, resolve_workspace_slug_to_id, ) +from stardag.config.cache import ( + get_cached_environment_slug, + get_cached_workspace_slug, +) from stardag.config.loader import clear_config_cache, get_config from stardag.integration.modal import StardagApp @@ -61,37 +63,34 @@ app.add_typer(stardag_api_key_app, name="stardag-api-key") -def _get_profile_slugs( - profile_name: str | None, - workspace_id: str | None = None, - environment_id: str | None = None, +def _resolve_display_slugs( + workspace_id: str | None, + environment_id: str | None, ) -> tuple[str | None, str | None]: - """Get workspace and environment slugs from a profile. + """Reverse-lookup workspace/environment slugs for the given UUIDs. - Returns (workspace_slug, environment_slug) where slug is None if - the profile value matches the resolved ID (i.e. it's already a UUID). + Why: the resolved UUIDs may come from env vars or a custom config_provider + override and have no relation to the active profile's TOML slugs. Reading + `prof["environment"]` could display a slug for a totally different env + than the resolved UUID. """ - if not profile_name: - profile_name_resolved, _ = get_active_profile() - profile_name = profile_name_resolved - - if not profile_name: - return None, None - - profiles = list_profiles() - prof = profiles.get(profile_name) - if not prof: + config = get_config() + registry_name = config.context.registry_name + if not registry_name: return None, None - ws_slug = prof["workspace"] if prof["workspace"] != workspace_id else None - env_slug = prof["environment"] if prof["environment"] != environment_id else None + ws_slug = ( + get_cached_workspace_slug(registry_name, workspace_id) if workspace_id else None + ) + env_slug = ( + get_cached_environment_slug(registry_name, workspace_id, environment_id) + if workspace_id and environment_id + else None + ) return ws_slug, env_slug -def _print_stardag_context( - env_vars: dict[str, str], - profile_name: str | None = None, -) -> None: +def _print_stardag_context(env_vars: dict[str, str]) -> None: """Print stardag context info (registry, workspace, environment, target roots).""" import json @@ -99,7 +98,7 @@ def _print_stardag_context( ws_id = env_vars.get("STARDAG_WORKSPACE_ID", "N/A") env_id = env_vars.get("STARDAG_ENVIRONMENT_ID", "N/A") - ws_slug, env_slug = _get_profile_slugs(profile_name, ws_id, env_id) + ws_slug, env_slug = _resolve_display_slugs(ws_id, env_id) console.print(f"[dim] Registry: {registry}[/dim]") @@ -297,7 +296,7 @@ def stardag_api_key_create( if api_key_name is None: api_key_name = f"modal-{modal_env or 'default'}" - ws_slug, env_slug = _get_profile_slugs(stardag_profile, ws_id, env_id) + ws_slug, env_slug = _resolve_display_slugs(ws_id, env_id) ws_display = f"{ws_id} ({ws_slug})" if ws_slug else ws_id env_display = f"{env_id} ({env_slug})" if env_slug else env_id @@ -561,7 +560,7 @@ def deploy( console.print(f"[cyan]Using stardag profile: {profile}[/cyan]") env_vars = get_profile_env_vars(profile) if env_vars: - _print_stardag_context(env_vars, profile) + _print_stardag_context(env_vars) extra_secrets.append( modal.Secret.from_dict(dict(env_vars)) # type: ignore[arg-type] ) diff --git a/lib/stardag/src/stardag/config/cache.py b/lib/stardag/src/stardag/config/cache.py index 3912c3ef..5e9e8eff 100644 --- a/lib/stardag/src/stardag/config/cache.py +++ b/lib/stardag/src/stardag/config/cache.py @@ -188,6 +188,27 @@ def cache_environment_id( save_id_cache(cache) +def get_cached_workspace_slug(registry: str, workspace_id: str) -> str | None: + """Reverse lookup: cached workspace slug for a given UUID.""" + cache = load_id_cache() + for slug, ws_id in cache.workspaces.get(registry, {}).items(): + if ws_id == workspace_id: + return slug + return None + + +def get_cached_environment_slug( + registry: str, workspace_id: str, environment_id: str +) -> str | None: + """Reverse lookup: cached environment slug for a given UUID.""" + cache = load_id_cache() + envs = cache.environments.get(registry, {}).get(workspace_id, {}) + for slug, env_id in envs.items(): + if env_id == environment_id: + return slug + return None + + def _looks_like_uuid(value: str) -> bool: """Check if a string looks like a UUID.""" uuid_pattern = r"^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$" From df7d933ef9c0ed2dd544ed44a88f393c722e8867 Mon Sep 17 00:00:00 2001 From: Anders Huss <5502349+andhus@users.noreply.github.com> Date: Fri, 8 May 2026 12:32:14 +0200 Subject: [PATCH 2/2] Pin id-cache lookup to the resolved registry URL, single-load MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Address review feedback on PR #143: 1. `_resolve_display_slugs` previously read `registry_name` from `get_config()`, but both call sites resolve IDs under a temporarily overridden `STARDAG_PROFILE` and restore the env before printing — so `get_config()` could point at a different registry than the IDs came from, producing reverse-lookup misses on multi-registry setups. Derive `registry_name` from the actual `STARDAG_API_URL` instead. 2. Collapse `get_cached_workspace_slug` + `get_cached_environment_slug` into a single `get_cached_slugs` helper so the id-cache file is loaded once per CLI invocation instead of twice. Co-Authored-By: Claude Opus 4.7 (1M context) --- lib/stardag/src/stardag/_cli/modal.py | 53 +++++++++++++++---------- lib/stardag/src/stardag/config/cache.py | 41 ++++++++++--------- 2 files changed, 56 insertions(+), 38 deletions(-) diff --git a/lib/stardag/src/stardag/_cli/modal.py b/lib/stardag/src/stardag/_cli/modal.py index 7ab8d0c6..e51b0f35 100644 --- a/lib/stardag/src/stardag/_cli/modal.py +++ b/lib/stardag/src/stardag/_cli/modal.py @@ -30,13 +30,11 @@ from stardag._cli._helpers import get_authenticated_client from stardag._cli.credentials import ( + list_registries, resolve_environment_slug_to_id, resolve_workspace_slug_to_id, ) -from stardag.config.cache import ( - get_cached_environment_slug, - get_cached_workspace_slug, -) +from stardag.config.cache import get_cached_slugs from stardag.config.loader import clear_config_cache, get_config from stardag.integration.modal import StardagApp @@ -63,31 +61,44 @@ app.add_typer(stardag_api_key_app, name="stardag-api-key") +def _registry_name_for_url(api_url: str | None) -> str | None: + """Look up the configured registry name for a given URL. + + The id-cache is keyed by registry *name* (the TOML key under ``[registry.]``) + rather than URL, so we reverse-lookup the name from the URL the resolved IDs were + fetched against. Returns ``None`` if no registry with a matching URL is configured + (e.g. STARDAG_API_URL is set without a corresponding TOML entry). + """ + if not api_url: + return None + target = api_url.rstrip("/") + for name, url in list_registries().items(): + if url.rstrip("/") == target: + return name + return None + + def _resolve_display_slugs( + api_url: str | None, workspace_id: str | None, environment_id: str | None, ) -> tuple[str | None, str | None]: - """Reverse-lookup workspace/environment slugs for the given UUIDs. + """Reverse-lookup workspace/environment slugs for the resolved UUIDs. Why: the resolved UUIDs may come from env vars or a custom config_provider override and have no relation to the active profile's TOML slugs. Reading `prof["environment"]` could display a slug for a totally different env - than the resolved UUID. + than the resolved UUID. We also can't trust ``get_config().context.registry_name`` + because callers like ``deploy(--profile foo)`` and ``stardag_api_key_create`` + resolve IDs under a temporarily-overridden profile, then restore the env + before this function runs — leaving ``get_config()`` pointing at the + *active* profile, which may use a different registry. Pin the registry to + the URL the IDs were actually resolved against. """ - config = get_config() - registry_name = config.context.registry_name + registry_name = _registry_name_for_url(api_url) if not registry_name: return None, None - - ws_slug = ( - get_cached_workspace_slug(registry_name, workspace_id) if workspace_id else None - ) - env_slug = ( - get_cached_environment_slug(registry_name, workspace_id, environment_id) - if workspace_id and environment_id - else None - ) - return ws_slug, env_slug + return get_cached_slugs(registry_name, workspace_id, environment_id) def _print_stardag_context(env_vars: dict[str, str]) -> None: @@ -98,7 +109,9 @@ def _print_stardag_context(env_vars: dict[str, str]) -> None: ws_id = env_vars.get("STARDAG_WORKSPACE_ID", "N/A") env_id = env_vars.get("STARDAG_ENVIRONMENT_ID", "N/A") - ws_slug, env_slug = _resolve_display_slugs(ws_id, env_id) + ws_slug, env_slug = _resolve_display_slugs( + env_vars.get("STARDAG_API_URL"), ws_id, env_id + ) console.print(f"[dim] Registry: {registry}[/dim]") @@ -296,7 +309,7 @@ def stardag_api_key_create( if api_key_name is None: api_key_name = f"modal-{modal_env or 'default'}" - ws_slug, env_slug = _resolve_display_slugs(ws_id, env_id) + ws_slug, env_slug = _resolve_display_slugs(api_url, ws_id, env_id) ws_display = f"{ws_id} ({ws_slug})" if ws_slug else ws_id env_display = f"{env_id} ({env_slug})" if env_slug else env_id diff --git a/lib/stardag/src/stardag/config/cache.py b/lib/stardag/src/stardag/config/cache.py index 5e9e8eff..e019be4b 100644 --- a/lib/stardag/src/stardag/config/cache.py +++ b/lib/stardag/src/stardag/config/cache.py @@ -188,25 +188,30 @@ def cache_environment_id( save_id_cache(cache) -def get_cached_workspace_slug(registry: str, workspace_id: str) -> str | None: - """Reverse lookup: cached workspace slug for a given UUID.""" - cache = load_id_cache() - for slug, ws_id in cache.workspaces.get(registry, {}).items(): - if ws_id == workspace_id: - return slug - return None - - -def get_cached_environment_slug( - registry: str, workspace_id: str, environment_id: str -) -> str | None: - """Reverse lookup: cached environment slug for a given UUID.""" +def get_cached_slugs( + registry: str, + workspace_id: str | None, + environment_id: str | None, +) -> tuple[str | None, str | None]: + """Reverse lookup: cached (workspace_slug, environment_slug) for the given UUIDs. + + Loads the id-cache once. Returns ``(None, None)`` for inputs without a cache hit. + """ cache = load_id_cache() - envs = cache.environments.get(registry, {}).get(workspace_id, {}) - for slug, env_id in envs.items(): - if env_id == environment_id: - return slug - return None + ws_slug: str | None = None + if workspace_id: + for slug, ws_id in cache.workspaces.get(registry, {}).items(): + if ws_id == workspace_id: + ws_slug = slug + break + env_slug: str | None = None + if workspace_id and environment_id: + envs = cache.environments.get(registry, {}).get(workspace_id, {}) + for slug, env_id in envs.items(): + if env_id == environment_id: + env_slug = slug + break + return ws_slug, env_slug def _looks_like_uuid(value: str) -> bool: