Skip to content

fix: make func pack gracefully handle missing local.settings.json (issue #4783)#4829

Open
MO2k4 wants to merge 9 commits intoAzure:mainfrom
MO2k4:fix/pack-missing-local-settings
Open

fix: make func pack gracefully handle missing local.settings.json (issue #4783)#4829
MO2k4 wants to merge 9 commits intoAzure:mainfrom
MO2k4:fix/pack-missing-local-settings

Conversation

@MO2k4
Copy link

@MO2k4 MO2k4 commented Mar 16, 2026

Summary

Fixes #4783

When func pack is run where FUNCTIONS_WORKER_RUNTIME is not set (e.g. a CI/CD pipeline where local.settings.json is gitignored), the command throws "Unsupported runtime: None" with no actionable guidance.

This PR improves the error handling in PackAction.RunAsync():

  • --no-build mode: preserves the existing .dll-based fallback, but scoped to top-level files only (SearchOption.TopDirectoryOnly) to avoid false positives from node_modules/, Python venvs, etc. Emits a deprecation warning directing users to set FUNCTIONS_WORKER_RUNTIME explicitly.
  • All other cases: throws a descriptive CliException with explicit guidance if the runtime cannot be determined.

Runtime detection order

  1. FUNCTIONS_WORKER_RUNTIME environment variable (unchanged)
  2. local.settings.json via SecretsManager (unchanged)
  3. --no-build only: top-level .dll fallback with deprecation warning (scoped from previous AllDirectories to TopDirectoryOnly)
  4. Fail: CliException with message:

    Unable to determine the worker runtime for this project. Set the 'FUNCTIONS_WORKER_RUNTIME' value in 'local.settings.json' or as an environment variable. Valid values: dotnet-isolated, dotnet, node, python, powershell, custom.

Files changed

File Change
src/Cli/func/Actions/LocalActions/PackAction/PackAction.cs Scope .dll fallback to top-level + add deprecation warning; add CliException for unresolvable runtime
test/Cli/Func.UnitTests/ActionsTests/PackAction/PackActionRuntimeDetectionTests.cs 4 unit tests covering: no signals → error, env var set → no error, --no-build + dlls → no error, --no-build + no dlls → error

Test plan

  • PackActionRuntimeDetectionTests — 4 tests covering all detection paths
  • Note: local builds require Azure DevOps feed access (private NuGet packages). The unit test project validates behavior against the compiled source.

Release notes

func pack now emits a clear, actionable error when FUNCTIONS_WORKER_RUNTIME cannot be determined, instead of the cryptic Unsupported runtime: None. The existing .dll-based fallback for --no-build is preserved but scoped to top-level files only, with a deprecation warning.

@MO2k4 MO2k4 requested a review from a team as a code owner March 16, 2026 19:18
@liliankasem
Copy link
Member

@MO2k4 - thanks for the contribution. I do have concerns about this approach. I think for now it might be better to drop the runtime inference logic (InferWorkerRuntimeFromProjectFiles) and focus on clear, actionable error messages instead.

Reliably detecting the worker runtime from project files is not easily feasible for Azure Functions. In my opinion, silent misdetection is worse than a clear error. If we guess wrong, the user gets a confusing failure much later in the pack/deploy pipeline — harder to diagnose than being told upfront what to do.


Missing runtimes entirely

Java and Custom handler projects are not covered

Incomplete coverage of existing runtimes

f# projects are not covered

Fragile heuristics

  1. .csproj isolated detection is brittle, it checks if the file content contains `"Microsoft.Azure.Functions.Worker" which is not ideal. Potential issues with this:

    • Projects using Central Package Management (Directory.Packages.props) may not have the package reference in the .csproj at all — it's in a props file. This is the default setup for new projects in this very repo.
    • The substring match "Microsoft.Azure.Functions.Worker" also matches "Microsoft.Azure.Functions.Worker.Sdk", "Microsoft.Azure.Functions.Worker.Extensions.Http", etc. — which happen to correlate with isolated, so this is lucky rather than intentional.
  2. recursive .dll search is dangerous Directory.GetFiles(directory, "*.dll", SearchOption.AllDirectories) will:

    • Match native .dll files in Python virtual environments (venv/Lib/site-packages/...)
    • Match native addons in node_modules/
    • Be very slow on large directories
    • Produce false positives — any project with any .dll anywhere in its subtree would be classified as Dotnet

Priority / ordering problems

  1. package.json could exist in a non-Node project. Python projects often have a package.json if they use npm-based frontend tooling. If such a project also has requirements.txt, it would correctly return Python (Python is checked first). But if it only has function_app.py and package.json without requirements.txt, the Python check passes, so this specific case is fine. The ordering is fragile though; it relies on the coincidence that Python signals are checked before Node.

  2. functions.metadataDotnetIsolated assumption - This file can appear in in-proc .NET build outputs too. Hardcoding it to DotnetIsolated could misroute in-proc projects.

The --no-build scenario is poorly served

The original issue was specifically about --no-build in CI. In published/built output directories, the source files (.csproj, package.json, requirements.txt) are typically not present — only build artifacts exist. So, the inference would skip all the source-file checks and land on the blunt .dll fallback, always returning Dotnet regardless of whether the project is actually isolated.

@MO2k4
Copy link
Author

MO2k4 commented Mar 16, 2026

@liliankasem thanks for the feedback

Dropped InferWorkerRuntimeFromProjectFiles entirely.

RunAsync now throws directly when the runtime can't be determined:

Unable to determine the worker runtime for this project. Set the 'FUNCTIONS_WORKER_RUNTIME' value in 'local.settings.json' or as an environment variable. Valid values: ...

Also removed PackActionInferRuntimeTests.cs. The remaining PackActionRuntimeDetectionTests covers the error path and the env-var bypass case.

Copy link
Member

@liliankasem liliankasem left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Have you tested this by build the CLI locally and running it? Can you share screenshots of what these error messages look like and what use cases you have tested?

@MO2k4
Copy link
Author

MO2k4 commented Mar 17, 2026

Local builds are not possible in this environment — the repo's NuGet.config pulls from private Azure DevOps feeds that require internal credentials, so dotnet build fails for external contributors.

What I can show is the exact error message produced by the code path (from PackAction.cs lines 109-113):

Unable to determine the worker runtime for this project. Set the 'FUNCTIONS_WORKER_RUNTIME' value in 'local.settings.json' or as an environment variable. Valid values: dotnet-isolated, dotnet, node, python, powershell, custom.

And for the --no-build + .dll path (lines 98-103), the deprecation warning:

Warning: No 'FUNCTIONS_WORKER_RUNTIME' setting found. Defaulting to 'dotnet' based on .dll files. This fallback is deprecated and will be removed in a future release. Set 'FUNCTIONS_WORKER_RUNTIME' in 'local.settings.json' or as an environment variable.

The unit tests in PackActionRuntimeDetectionTests validate these four paths against the actual RunAsync() implementation:

  • No runtime signals → CliException with the message above ✓
  • FUNCTIONS_WORKER_RUNTIME env var set → no runtime error ✓
  • --no-build + top-level .dll present → no runtime error (deprecation warning path) ✓
  • --no-build + no .dll files → CliException with actionable message ✓

MO2k4 added 5 commits March 17, 2026 07:29
Adds PackAction.InferWorkerRuntimeFromProjectFiles() to detect worker
runtime from .csproj, requirements.txt, package.json, profile.ps1, and
build-output signals when local.settings.json is absent.

Fixes Azure#4783
When FUNCTIONS_WORKER_RUNTIME is not set and local.settings.json is
absent (the default CI/CD state), func pack now warns and falls back to
project-file inference instead of throwing "Unsupported runtime: None".
If inference also fails, a clear CliException with actionable guidance is
thrown. Removes the flawed NoBuild-only .dll fallback.

Fixes Azure#4783
Per reviewer feedback, project-file inference is fragile and unreliable:
- fragile heuristics (Central Package Management breaks .csproj detection)
- recursive .dll scan is slow and produces false positives in Python/Node
- source files absent in --no-build/CI output dirs anyway

Replace with a clear, actionable error directing users to set
FUNCTIONS_WORKER_RUNTIME in local.settings.json or as an env var.

Remove InferWorkerRuntimeFromProjectFiles and its test suite.
Per reviewer feedback: removing the '&& NoBuild' guard was a regression
for users relying on the .dll-based runtime detection in CI/CD pipelines.

Restore the fallback but scope it properly:
- Only active when --no-build is passed
- Searches top-level only (SearchOption.TopDirectoryOnly) to avoid false
  positives from .dll files in node_modules/, Python venvs, etc.
- Emits a deprecation warning directing users to set FUNCTIONS_WORKER_RUNTIME
- Falls through to the actionable CliException if no .dll files found

Also adds two new unit tests covering the NoBuild + dll and NoBuild + no-dll
detection paths.
@MO2k4 MO2k4 force-pushed the fix/pack-missing-local-settings branch from 104d683 to 5695c8c Compare March 17, 2026 06:29
@liliankasem
Copy link
Member

Local builds are not possible in this environment — the repo's NuGet.config pulls from private Azure DevOps feeds that require internal credentials, so dotnet build fails for external contributors.

What I can show is the exact error message produced by the code path (from PackAction.cs lines 109-113):

Unable to determine the worker runtime for this project. Set the 'FUNCTIONS_WORKER_RUNTIME' value in 'local.settings.json' or as an environment variable. Valid values: dotnet-isolated, dotnet, node, python, powershell, custom.

And for the --no-build + .dll path (lines 98-103), the deprecation warning:

Warning: No 'FUNCTIONS_WORKER_RUNTIME' setting found. Defaulting to 'dotnet' based on .dll files. This fallback is deprecated and will be removed in a future release. Set 'FUNCTIONS_WORKER_RUNTIME' in 'local.settings.json' or as an environment variable.

The unit tests in PackActionRuntimeDetectionTests validate these four paths against the actual RunAsync() implementation:

  • No runtime signals → CliException with the message above ✓
  • FUNCTIONS_WORKER_RUNTIME env var set → no runtime error ✓
  • --no-build + top-level .dll present → no runtime error (deprecation warning path) ✓
  • --no-build + no .dll files → CliException with actionable message ✓

You should still be able to test locally. The private feed shouldn't stop you from that; I was able to publish the CLI locally on my personal machine without being logged into my corp account. Please build and test the CLI and provide screenshots.

@liliankasem
Copy link
Member

Just a quick note on process: AI assistance is fine for the code, but I'd appreciate it if review discussions and feedback responses came from you directly. If AI is handling that side of the conversation too, there's no meaningful difference between your contribution and me assigning the issue to Copilot. Part of what makes open source collaboration valuable is the human exchange, and I'd like your engagement here to genuinely be yours. I'll help get this merged, but please keep this in mind for future contributions.

@MO2k4
Copy link
Author

MO2k4 commented Mar 17, 2026

@liliankasem Ok got it.

Could you elaborate how to get this running locally without running a dotnet restore? I've tried to build, pack and publish but i receive the following error

Unable to find package Microsoft.Azure.DurableTask.AzureStorage.Internal

@MO2k4
Copy link
Author

MO2k4 commented Mar 18, 2026

@liliankasem here you can find the testing validation

image

I've executed the following test cases:

  1. func pack — no runtime set → actionable error
  2. func pack --no-build — .dll present → deprecation warning, proceeds
  3. func pack --no-build — no .dll → actionable error
  4. FUNCTIONS_WORKER_RUNTIME=node func pack — env var bypasses detection
scenario1-no-runtime scenario2-nobuild-dll scenario3-nobuild-nodll scenario4-envvar

Is there any testcase missing?

@liliankasem
Copy link
Member

@liliankasem here you can find the testing validation

image I've executed the following test cases:
  1. func pack — no runtime set → actionable error
  2. func pack --no-build — .dll present → deprecation warning, proceeds
  3. func pack --no-build — no .dll → actionable error
  4. FUNCTIONS_WORKER_RUNTIME=node func pack — env var bypasses detection

scenario1-no-runtime scenario2-nobuild-dll scenario3-nobuild-nodll scenario4-envvar
Is there any testcase missing?

Great, thanks! I think the only one I can think of is validating that local.settings.json with FUNCTIONS_WORKER_RUNTIME still works as expected then lgtm

@MO2k4
Copy link
Author

MO2k4 commented Mar 18, 2026

@liliankasem I added another testcase and here is the validation screenshot, i've created a minimal http func to validate it

local-settings-full-pack

@liliankasem
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

@liliankasem
Copy link
Member

/azp run

@azure-pipelines
Copy link

Azure Pipelines successfully started running 1 pipeline(s).

Environment.SetEnvironmentVariable(Constants.FunctionsWorkerRuntime, null);
}

public void Dispose()
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you fix this

public void Dispose()
{
    try
    {
        Directory.Delete(_tempDir, recursive: true);
    }
    catch (IOException)
    {
        // Best-effort cleanup; files may still be locked by the test host.
    }
}

Alternatively, ensure all IDisposable resources are fully disposed before Dispose() runs.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I've checked and there should no disposable objects currently in the test. Code wise it is adjusted with the reset mechanism for the current environment variable usage

@liliankasem
Copy link
Member

@MO2k4 I'm fixing the zip helper test issue in another pr #4831

@MO2k4
Copy link
Author

MO2k4 commented Mar 19, 2026

@liliankasem I got some push back on another pr for using Environment and checked this here too, should i adjust this here as well since this was already pre existing or do this properly in a new pr?

microsoft/aspire#15388 (comment)

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

func pack requirs local.settings.json

3 participants