Flex Consumption: ARM accepts uncatalogued functionAppConfig.runtime.version (e.g. "10" instead of "10.0"); app then fails only at host startup with generic InternalServerError
- Dominant language
- PowerShell
- Stars
- 1.1k
- Forks
- 215
- Avg merge
- 4h 2m
- Merged PRs (30d)
- 1
Description
## Summary
Creating a Flex Consumption function app with a `functionAppConfig.runtime.version` value that is not in the regional runtime catalog (e.g. `"10"` where the catalog value is `"10.0"` for `dotnet-isolated`) **succeeds at ARM PUT time with no error or warning**. The resulting app is permanently broken in the most opaque way possible:
- every instance start fails; the app URL never responds (connection timeout),
- `POST .../host/default/listkeys` returns `400 BadRequest`: `"Encountered an error (InternalServerError) from host runtime."`,
- zip deployments upload successfully but end with `"Function triggers synchronization failed due to Response status code does not indicate success: 500 (Internal Server Error)"`,
- nothing anywhere: portal, ARM, deployment logs; points at the runtime version.
Diagnosing this took a full team-day, because every symptom points at deployment, storage, RBAC, or app code instead at the one-character config difference.
## Repro
Reproduces with any client that passes the raw string through to ARM (Bicep, ARM template, Terraform azurerm/azapi, `az resource create`). Note `az functionapp create` does NOT repro; the CLI validates and normalizes client-side (see "Evidence" below).
1. Create a Flex Consumption app (Bicep or `az resource`) with:
```json
"functionAppConfig": {
"runtime": { "name": "dotnet-isolated", "version": "10" }
}
```
in a region where the catalog value is `"10.0"` (e.g. West Europe; verify with
`az functionapp list-flexconsumption-runtimes --location westeurope --runtime dotnet-isolated`).
2. Observe: creation succeeds. Portal shows a healthy-looking app.
3. Deploy any valid dotnet-isolated package (zip deploy). Upload succeeds; deployment ends `status: 6`, `"Function triggers synchronization failed ... 500"`.
4. `az rest --method post --url ".../sites//host/default/listkeys?api-version=2023-12-01"` -> `400 BadRequest / "Encountered an error (InternalServerError) from host runtime."`
5. Patch the resource: `runtime.version` -> `"10.0"`. Redeploy. Everything works immediately. No other change.
## Expected behavior
Any one of:
1. **Reject at PUT** with a 400 naming the property and the valid values. The RP demonstrably already does this for *some* invalid versions: creating with python `"V2"` returns `"Site.FunctionAppConfig.Runtime.Version is invalid. The specified value of runtime version 'V2' for runtime name 'python' is not supported."` (quoted in hashicorp/terraform-provider-azurerm#31154). So a validation allow-list exists; bare-major dotnet values (`"10"`) fall through a hole in it.
2. **Normalize** bare-major versions to the catalog form (`"10"` → `"10.0"`), which is exactly what azure-cli does client-side (`_FlexFunctionAppStackRuntimeHelper` in `appservice/custom.py` fetches the regional `functionAppStacks?sku=FC1` catalog, matches version variants, and substitutes the canonical string into the request).
3. **Fail specifically at provision time**. If the value is accepted, the host/platform knows at instance start that it cannot resolve a worker image for `dotnet-isolated|10`; surfacing that (instead of a generic `InternalServerError from host runtime` through three different APIs) would make the failure self-explanatory.
## Additional evidence the current contract is a trap
- The Microsoft.Web swagger (`CommonDefinitions.json`, stable/2024-04-01 and 2024-11-01) types `FunctionsRuntime.version` as an unconstrained nullable string whose own description is **"Function app runtime version. Example: 8 (for dotnet-isolated)"**; i.e. the spec's own example uses the bare-major format that bricks the app (catalog value is `8.0`).
- The official Flex Bicep sample (`Azure-Samples/azure-functions-flex-consumption-samples`, `IaC/bicep/main.bicep`) constrains `runtime.name` with `@allowed(...)` but leaves the version a free string.
- Terraform `azurerm_function_app_flex_consumption` validates `runtime_name` (`StringInSlice`) but `runtime_version` only as non-empty, so IaC users pass the raw string to ARM.
- ARM/Bicep template reference documents `runtime.name` as an enum but `version` as a free string.
## Related (not duplicates)
- Azure/azure-functions-host#11379. Same generic error surface for Flex control-plane->host proxied operations (acknowledged by-design; this issue is about the missing write-time validation that makes that generic error the *only* signal).
- Azure/bicep-types-az#2521. Same accept-at-write / fail-later gap on `functionAppConfig.deployment.storage`; redirected to the App Service RP.
- Azure/azure-cli#32523. Analogous write-time gap on Linux Consumption (`linux-fx-version` accepts unsupported .NET 10).
- Azure/Azure-Functions#2638, #2644. User confusion updating Flex runtime versions; no validation feedback in either direction.
## Environment
- Region: West Europe; observed 2026-08-13.
- Runtime: `dotnet-isolated`; catalog value `"10.0"`, accepted invalid value `"10"`.
- Plan: Flex Consumption (`FC1`), Linux.
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.