Skip to content

az containerapp up silently re-creates an existing container app when its second existence check fails, wiping identity, ingress, secrets and scale #34193

Open

Description

@RagnarRoos

Describe the bug

az containerapp up checks twice whether the target app exists, and both checks swallow every exception:

  1. ContainerApp.create() in _up_utils.py calls get_container_app_if_exists(), which wraps ContainerAppClient.show in a bare except: pass (_utils.py L617-L623). Its result only chooses the Updating … / Creating … log line (_up_utils.py L384-L391).
  2. containerapp_up_logic() then calls ContainerAppClient.show again, also inside a bare except: pass, and if that returns nothing it calls create_containerapp(...) (custom.py L3747-L3757).

So when the second GET fails for any reason other than the app not existing — a throttled or transient error, a network or token hiccup — up treats an existing app as missing and sends a full create PUT built only from its own arguments. That PUT replaces the app's configuration: everything not passed to up is reset.

We hit this in a CI deploy of an existing app. The log shows both branches in one run:

WARNING: Updating Containerapp <app> in resource group <rg>
...
Container app created. To access it over HTTPS, enable ingress: az containerapp ingress enable -n <app> -g <rg> --type external --target-port <port> --transport auto
Your container app <app> has been created and deployed! Congrats!

Container app created. To access it over HTTPS, enable ingress is printed only by ContainerAppCreateDecorator.post_process (containerapp_decorator.py L519), so the first check found the app and the second did not. Azure Resource Graph change history for that write shows:

  • identity.type → None — the system-assigned identity was deleted, so its principal (and every role assignment and database user granted to it) is gone for good
  • ingress removed
  • every secret except the registry credential up rebuilds removed, including the Easy Auth client secret; the authConfigs child resource survived, so Easy Auth was left enabled pointing at a secret that no longer existed and the app could not start
  • scale settings reset to defaults

The command exited 0. The same command with the same CLI version against another existing app logged Updating Containerapp … followed by Browse to your container app at …, took the update path and left its configuration intact; the failing run took several times longer, consistent with a create.

The exception from the second GET is swallowed, so we cannot tell what it was.

Related command

az containerapp up

Errors

None — the command succeeds. That is the problem: a failed read becomes a destructive create with exit code 0.

Issue script & Debug output

Not reproducible on demand, since it needs the second show to fail. Command as run in CI:

az config set extension.use_dynamic_install=yes_without_prompt
az containerapp up \
  --name <app> \
  --resource-group <rg> \
  --image <registry>.azurecr.io/<image>:<tag> \
  --environment /subscriptions/<sub>/resourceGroups/<env-rg>/providers/Microsoft.App/managedEnvironments/<env> \
  --location <location> \
  --env-vars KEY1="value" KEY2="value"

It can be reproduced deterministically by making the second ContainerAppClient.show call in containerapp_up_logic raise (for example by patching it in a test) against an existing app: up then calls create_containerapp.

Expected behavior

  • up must not create when it cannot tell whether the app exists. Only a definite not-found (ResourceNotFound / HTTP 404) should lead to create; any other error from the existence check should be raised.
  • The existence check should happen once, and the same answer should decide both the log line and the create/update branch.
  • Ideally, up on an existing app should never send a full PUT built from its own arguments, since that silently discards identity, ingress, secrets, scale and revision settings that up has no flags for.

Environment Summary

azure-cli 2.90.0 (GitHub-hosted ubuntu-24.04 runner)
containerapp extension: not installed
Python (Linux)

Additional context

Activity

  1. yonzhan commented on Oct 8, 2026

    @yonzhan
    Collaborator

    Thank you for opening this issue, we will look into it.

  2. x-engineering-agent commented on Oct 14, 2026

    @x-engineering-agent
    Contributor

    X Engineering Agent Bug Analysis

    Confidence: Low (0.40)

    Summary

    A failed existence read during az containerapp up can select a create PUT for an existing app, replacing configuration instead of failing safely.

    Likely root cause

    Two independent lookups conflate every exception with absence; the second result controls create/update and can contradict the first log message.

    Affected code
    • src/azure-cli/azure/cli/command_modules/containerapp/_up_utils.py › ContainerApp.create (line 382)
    • src/azure-cli/azure/cli/command_modules/containerapp/_utils.py › get_container_app_if_exists (line 617)
    • src/azure-cli/azure/cli/command_modules/containerapp/custom.py › containerapp_up_logic (line 3747)
    • src/azure-cli/azure/cli/command_modules/containerapp/containerapp_decorator.py › ContainerAppCreateDecorator.construct_payload (line 289)
    • src/azure-cli/azure/cli/command_modules/containerapp/_clients.py › ContainerAppClient.create_or_update (line 122)
    Ruled out
    • Configuration: The same command can update successfully; existing settings are omitted from up arguments, not instructions to delete them. Configuration does not justify treating a read error as absence.
    • Environment: The exact transient error is unavailable, but any non-not-found exception enters the same shared branch; the runner platform is not required to explain dispatch.
    • Intended behavior: The built-in command is create-or-update and its normal update path fails closed on lookup errors; recreating after an unknown read result is not required behavior.
    Proposed change

    Treat only verified resource absence as permission to create and propagate every other lookup failure before writes. Reuse the existing HTTP-error handling convention; share one existence result for logging and branch selection, preserving direct callers and the normal partial-update path.

    Workaround

    Use az containerapp update for known existing apps; permit a separate create only after a confirmed not-found and fail on other lookup errors.

    Technical details

    Evidence

    • commands.py registers the built-in up command with containerapp_up. ContainerApp.create calls get_container_app_if_exists only to choose its log, then calls containerapp_up_logic without forwarding the result.
    • get_container_app_if_exists at _utils.py:617-623 and containerapp_up_logic at custom.py:3747-3757 both catch all exceptions and leave the result None; the latter then calls create_containerapp.
    • ContainerAppCreateDecorator starts with a fresh model, defaults identity.type to None at line 395, constructs optional ingress/secrets/scale from arguments, and sends it through ContainerAppClient.create_or_update, which issues PUT.
    • update_containerapp_logic already uses handle_non_404_status_code_exception and rejects a missing app. Existing test_containerapp_up.py covers live success paths, not injected lookup errors.

    Test plan

    Add mocked regressions alongside tests/latest/test_containerapp_up.py and test_containerapp_utils.py: found selects update, confirmed not-found selects create, 401/403/429/5xx and transport errors propagate with neither create nor update writes. Regress the first-found/second-error case and assert consistent logging/existence reuse. Preserve identity, ingress, secrets and scale on ordinary updates. Mocked fault injection needs no recordings; service configuration preservation needs a focused live scenario.

    Generated code: Handwritten code. The failing built-in command logic is in containerapp/custom.py, _up_utils.py and _utils.py, outside generated AAZ output; the reporter has no containerapp extension installed.

    Out of scope: No service-side fault or exact transient error is established; avoid unrelated extension changes and a general rewrite of create/update semantics.

    Live reproduction (BAMI sandbox) — Inconclusive

    Verdict: Inconclusive: the sandbox could not run the reproduction to the end.
    Reason: the problem cannot be expressed as allowed sandbox commands.
    The analysis above is based on reading the code only, so its confidence is capped at low.

    Commands ran in an isolated Azure sandbox subscription from a validated ReproSpec; output is redacted.

    Posted by x-engineering-agent (Fixer)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Auto-AssignAuto assign by botConfigureaz configure/configContainerAppRequest X Engineering AgentAsk the agent to triage this issue or review this PRService AttentionThis issue is responsible by Azure service team.act-codegen-extensibility-squadact-observability-squadcustomer-reportedIssues that are reported by GitHub users external to the Azure organization.questionThe issue doesn't require a change to the product in order to be resolved. Most issues start as that

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions