Skip to content

Deploying to Flex Consumption plans only displays "WarmUp" in Portal, requires clicking "Refresh" to see my function names #373

Description

@jonsagara

Summary

When I deploy my Azure Functions app to a Flex Consumption plan using Azure/functions-action, Azure Portal only displays WarmUp in the Functions list on the overview blade. I have to click Refresh in order to see my actual functions.

Environment

  • Action version: Azure/functions-action@v1.5.6 (pinned to bc63708cc6539760eea18d8a7de4ce8ef5fdf593)
  • Plan: Azure Functions Flex Consumption
  • Runtime: .NET 10, isolated worker model, self-contained, linux-x64
  • Deploy method: zip package built via dotnet publish, uploaded via the action's package input (see workflow excerpt below)
  • CI: GitHub Actions (ubuntu-latest), authenticating via OIDC (azure/login)

Expected Behavior

After a successful deployment, in Azure Portal, I see a list of my functions, available and ready to run.

Actual Behavior

After a successful deployment, in Azure Portal, I see only WarmUp, which is not one of my functions. I have to click Refresh, after which I see my functions, and they're available and ready to run.

What I have tried

With Claude's help, I tried the following:

  1. Baseline: rely solely on Azure/functions-action's built-in deployment + Kudu sync. It almost always results in only WarmUp being display in Azure Portal.
  2. az functionapp restart immediately after deploy. Theory: force re-specialization of a worker so it picks up the new package. Did not reliably resolve it (and risks restarting mid-async-deployment-pipeline, since Kudu's pipeline can still be finishing up to ~60s after the "Finished deployment pipeline" log line).
  3. Call the ARM host/default/sync endpoint (the same one the Portal's Refresh button calls) after a fixed Start-Sleep -Seconds 90:
    az rest --method post --url "https://management.azure.com/subscriptions/<sub>/resourceGroups/<rg>/providers/Microsoft.Web/sites/<app>/host/default/sync?api-version=2018-11-01"
    This worked on the very next deployment after adding it, then reverted to showing WarmUp on a subsequent deployment with no other changes — suggesting the fixed 90s delay is a race rather than a real fix: Flex Consumption instance specialization time (cold start, loading the app + native dependencies) is variable, and a single fire-and-forget sync call issued too early has nothing real to report yet.
  4. Current workaround: replaced the single sync call with a poll loop — call host/default/sync, then GET .../functions and check for a known function name, retrying with backoff (up to ~10 attempts / ~7 minutes) before failing the CI job loudly if it never resolves. This makes the failure visible in CI instead of silently leaving a stale Portal, but it's a workaround for what still looks like a platform/action reliability gap, not a fix.

Relevant workflow excerpt with workaround

So far, this workaround has resulted in the expected behavior after every deployment:

      - name: Deploy ZIPped Function with the Azure Functions Action
        uses: Azure/functions-action@bc63708cc6539760eea18d8a7de4ce8ef5fdf593 # v1.5.6
        with:
          app-name: ${{env.FUNCTION_APP_NAME}}
          package: ${{env.PUBLISH_ZIP_FILE_NAME}}

      # Kudu's own SyncTriggerStep (visible in App Insights) calls hostruntime/admin/host/synctriggers,
      # which isn't reliably picked up by the Portal/runtime on Flex Consumption. The Portal's Refresh
      # button, which does work, calls the ARM-level host/default/sync endpoint instead - call that
      # same endpoint here.
      #
      # A single sync call on a fixed delay is a race: on Flex Consumption an instance has to cold-start
      # and specialize with the new package (including native Magick.NET/SkiaSharp deps) before sync has
      # anything real to report, and that takes a variable amount of time. Until it does, sync/Portal keep
      # showing the placeholder WarmUp function. So poll: call sync, then check whether CheckAlertsScheduled
      # is actually registered, and retry with backoff until it is (or we give up and fail the job loudly
      # instead of silently leaving WarmUp behind).
      - name: Sync triggers and verify the deployment took effect
        run: |
          $resourceGroup = az functionapp list --query "[?name=='${{env.FUNCTION_APP_NAME}}'].resourceGroup" -o tsv
          $syncUrl = "https://management.azure.com/subscriptions/${{vars.AZURE_SUBSCRIPTION_ID}}/resourceGroups/$resourceGroup/providers/Microsoft.Web/sites/${{env.FUNCTION_APP_NAME}}/host/default/sync?api-version=2018-11-01"
          $listUrl = "https://management.azure.com/subscriptions/${{vars.AZURE_SUBSCRIPTION_ID}}/resourceGroups/$resourceGroup/providers/Microsoft.Web/sites/${{env.FUNCTION_APP_NAME}}/functions?api-version=2023-12-01"

          $maxAttempts = 10
          $delaySeconds = 30
          $verified = $false

          for ($attempt = 1; $attempt -le $maxAttempts; $attempt++) {
            Write-Output "Attempt $attempt/$maxAttempts - waiting ${delaySeconds}s before syncing triggers..."
            Start-Sleep -Seconds $delaySeconds

            az rest --method post --url $syncUrl | Out-Null

            # Give the sync call a moment to propagate before checking what it reported.
            Start-Sleep -Seconds 10

            $functionNames = az rest --method get --url $listUrl --query "value[].name" -o tsv
            Write-Output "Functions currently known to the runtime:`n$functionNames"

            if ($functionNames -match 'CheckAlertsScheduled') {
              Write-Output "Verified CheckAlertsScheduled is registered - deployment took effect."
              $verified = $true
              break
            }
          }

          if (-not $verified) {
            Write-Error "Trigger sync did not pick up CheckAlertsScheduled after $maxAttempts attempts (~$($maxAttempts * ($delaySeconds + 10))s). The Function App may still be serving the WarmUp placeholder - check the Portal and consider a manual restart."
            exit 1
          }

Related issue

It looks like this was fixed for Consumption plans in #135 ("triggers not synced after deployment," classic Consumption plan, closed August 2022) by pull request #148.

Question

  • Is it possible for this action to handle this polling and checking internally?
  • Claude suggests that perhaps the fix from #135 for Consumption plans can also be applied for One Deploy deployments to Flex Consumption plans. Is that possible?

Note

This also happens with the stock Azure DevOps Pipelines AzureFunctionsApp@v2: microsoft/azure-pipelines-tasks#22354

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