Skip to content

Isolated payload under FUNCTIONS_WORKER_RUNTIME=dotnet does not surface AZFD0013 before failing with an assembly load error #12032

Description

@fzankl

This is about the host's own startup validation path, the one that emits AZFD0013, not about deployment or slot mechanics. The state it reproduces from is a single configuration mismatch: FUNCTIONS_WORKER_RUNTIME set to dotnet while the deployed payload is an isolated worker payload. The steps below are only how the app arrived in that state.

Investigative information

  • Timestamp:
    August 25 and 26, 2026. The swap quoted below ran from 18:09:21 to 18:10:38
  • Function App version:
    FUNCTIONS_EXTENSION_VERSION ~4. Two host extensions were loaded across the swap, SiteExtensions\Functions\4.1053.200 before and SiteExtensions\FunctionsInProc\4.641.300 after
  • Function App name / Invocation ID:
    The app and its Application Insights resource have since been deleted, so there is nothing left to look up. The report rests on the host source and on the logs quoted below, which I kept. If a live repro with telemetry would help, I can stand the setup up again: two slots, one payload each, one swap.
  • Function name(s):
    One HTTP-triggered function plus a diagnostics endpoint that exists only in the isolated payload.
  • Region:
    West Europe
  • OS:
    Windows

Repro steps

  1. A function app with a production and a staging slot. Production holds an in-process .NET 8 payload with FUNCTIONS_WORKER_RUNTIME=dotnet and netFrameworkVersion v8.0. Staging holds an isolated .NET 10 payload on Microsoft.Azure.Functions.Worker 2.52.0 with dotnet-isolated and v10.0. Both slots return HTTP 200 for the same request.
  2. Mark FUNCTIONS_WORKER_RUNTIME as a deployment slot setting in both slots, which the migration guide warns against ("Don't mark FUNCTIONS_WORKER_RUNTIME as a slot setting").
  3. Swap staging into production. The swap reports success and takes about 77 seconds.
  4. Production now runs the isolated payload while FUNCTIONS_WORKER_RUNTIME stayed at dotnet. Everything else moved with the content: netFrameworkVersion, the 64-bit setting, WEBSITE_RUN_FROM_PACKAGE.

One swap produces both mismatch pairings at once, one per slot:

Deployed payload FUNCTIONS_WORKER_RUNTIME netFrameworkVersion Host extension loaded Result
in-process dotnet-isolated v8.0 Functions\4.1053.200 HTTP 200, app keeps serving
isolated dotnet v10.0 FunctionsInProc\4.641.300 HTTP 503, host restart loop

A swap back restored both slots to HTTP 200.

Expected behavior

The mismatch is reported the way AZFD0013 reports it in the opposite pairing: a diagnostic event that names FUNCTIONS_WORKER_RUNTIME and the detected payload, so the setting is the first thing the operator looks at.

Actual behavior

The AZFD0013 code path doesn't run for this pairing, as the next section shows. What happens instead: the in-process host fails during external startup, restarts every 5 to 20 seconds, and the app returns HTTP 503. What Application Insights recorded:

Microsoft.Azure.WebJobs.Script.ExternalStartupException
    Error configuring services in an external startup class.
 ---> System.IO.FileNotFoundException
    Could not load file or assembly 'System.ComponentModel, Version=8.0.0.0, ...'.

  Assembly: Microsoft.Azure.WebJobs.Extensions.Storage.Queues, Version=5.3.8.0

Nothing in it names FUNCTIONS_WORKER_RUNTIME, a slot or a payload type, so it reads like a broken package. LogFiles/Application/Functions/Host/ gets no new file, because the host never gets far enough to write its own log. The event log shows the switch of host extension within twelve seconds of the swap:

18:08:44  SiteExtensions\Functions\4.1053.200        started   (staging, dotnet-isolated)
18:09:25  <app>                                      shutdown  (swap)
18:09:37  SiteExtensions\FunctionsInProc\4.641.300   started   (with production settings, dotnet)

Known workarounds

Set FUNCTIONS_WORKER_RUNTIME in the affected slot to the value that matches the deployed payload, or swap back. Both bring the app up again within a restart. To avoid the state in the first place, don't mark FUNCTIONS_WORKER_RUNTIME as a deployment slot setting, as the migration guide says. None of this helps with finding the cause, which is the point of this report: the log gives you an assembly name, not a setting.

Why the host doesn't report it

ScriptHost.ValidateAndLogRuntimeMismatch compares the setting with the payload metadata and logs the diagnostic event under WorkerRuntimeDoesNotMatchWithFunctionMetadataErrorCode, which is AZFD0013. GetFunctionDescriptorsAsync calls it only when the setting is dotnet-isolated:

// this dotnet isolated specific logic is temporary to ensure in-proc payload compatibility with "dotnet-isolated" as the FUNCTIONS_WORKER_RUNTIME value.
if (!_environment.IsLogicApp() && string.Equals(workerRuntime, RpcWorkerConstants.DotNetIsolatedLanguageWorkerName, StringComparison.OrdinalIgnoreCase) && !_environment.IsPlaceholderModeEnabled())

The in-process host that the platform loaded here, v4.641.300, has the same guard. The warning text, "The application will continue to run, but may throw an exception in the future", is therefore accurate where it is emitted. When the hosting configuration enables WorkerRuntimeStrictValidationEnabled (WORKER_RUNTIME_STRICT_VALIDATION_ENABLED, default false), the same check logs an error and throws HostInitializationException instead, which is presumably what separates "Warning" from "Error" in the severity row of the AZFD0013 article.

For dotnet with an isolated payload the current code path never calls this check, so there is no later point at which AZFD0013 would be emitted for this pairing. In this reproduction, startup also ended before function metadata was processed at all: the exception above shows the in-process host failing while loading an extension that comes from the isolated payload (Storage.Queues 5.3.8, where the in-process app pinned 5.3.0). So in this reproduction, the one diagnostic that exists for this mismatch could not fire in the direction that takes the app down. I didn't query the production slot for AZFD0013 at the time, and the resources no longer exist, so this rests on the code path rather than on telemetry.

The same exception text is the subject of #10391, closed in March 2025 with FUNCTIONS_INPROC_NET8_ENABLED as the answer for in-process .NET 8 apps. A later comment there comes from someone migrating to the isolated model who still sees the error. Whether that was this pairing I can't tell from the thread, but it shows where people land when they search for the message.

Suggested change

When FUNCTIONS_WORKER_RUNTIME is dotnet and the payload is an isolated worker payload, report AZFD0013 as an error that names the setting and the detected payload, before external startup tries to load the payload's extensions. The payload may be identifiable before extension loading from artifacts already present in wwwroot, such as worker.config.json or the .azurefunctions/ folder. You'll know which signal is reliable at that point, and where the check belongs.

I realize the in-process model reaches end of support on 10 November 2026, and a change to the in-process host may not be worth making this late. If that's the case, two smaller changes would still help people migrating right now. One is to surface the configured FUNCTIONS_WORKER_RUNTIME value in the failure that reaches the user when an in-process host fails on an isolated payload, so that the setting appears somewhere in the log at all. The other is to name the direction in the existing warning ("an in-process payload under dotnet-isolated"), so that "will continue to run" isn't read as covering the reverse pairing. That leaves the behavior as it is and changes only the sentence people quote.

Once the in-process model is out of support, is FUNCTIONS_WORKER_RUNTIME=dotnet with an isolated payload still expected to reach the in-process host, or is that combination expected to be rejected earlier? If it's rejected, this fixes itself and only the documentation needs the correction.

The AZFD0013 article describes only the first direction as well. MicrosoftDocs/azure-docs doesn't take issues, so I can follow up with a docs PR there once it's clear how the host is meant to behave.

Related information

  • Programming language: C#. Production ran the in-process model on .NET 8, staging the isolated worker model on .NET 10 with Microsoft.Azure.Functions.Worker 2.52.0.
  • Bindings used: HTTP trigger and a queue output binding. The extension that fails to load is Microsoft.Azure.WebJobs.Extensions.Storage.Queues 5.3.8 from the isolated payload, against 5.3.0 pinned in the in-process app.
  • Links to source: ScriptHost.ValidateAndLogRuntimeMismatch and its call site in GetFunctionDescriptorsAsync, plus FunctionsHostingConfigOptions.WorkerRuntimeStrictValidationEnabled, all linked in the section above at dev commit 47c5606.
  • Docs: the AZFD0013 article and the slot section of the migration guide.
  • Related issue: #10391, same exception text, different cause.

Scope

One application, Windows, one region, one swap plus a swap back. The host extension was FunctionsInProc rather than FunctionsInProc8, because FUNCTIONS_INPROC_NET8_ENABLED was not a slot setting and moved with the swap. Not checked: FunctionsInProc8, Linux, Flex Consumption. The source reading refers to dev at 47c5606 (September 15, 2026) and the tag v4.641.300.

Activity

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions