Conversation
…ar error azd's Aspire integration is C#-only (MSBuild IsAspireHost + `dotnet run --publisher manifest`). A polyglot AppHost (TypeScript/Python) is never recognized as Aspire, so azd falls through to a generic Oryx/buildpacks source build that produces confusing Docker/buildpack failures (issue #9335). Enhance the .NET AppHost detector to recognize polyglot (non-C#) Aspire AppHosts during app detection (init/up) and return an actionable ErrorWithSuggestion referencing #7138 instead of falling through. Detection prefers the explicit signal from aspire.config.json (appHost.language/path), and otherwise trusts well-known AppHost file names conservatively to avoid false positives (TypeScript apphost.mts/apphost.ts on their own; apphost.py only with a companion marker; go/java/rust only via aspire.config.json). Add telemetry (event aspire.apphost.unsupported + field aspire.apphost.language) to count how often users hit this, so we can size demand for polyglot support before implementing it. Update telemetry docs and schema/matrix/privacy-review specs accordingly. Refs #9335, #7138 Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> Copilot-Session: 4ff538e7-5f90-4c60-a339-1a02257f6c5b
|
Azure Pipelines: Successfully started running 1 pipeline(s). 21 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Detects unsupported Aspire polyglot AppHosts early, replacing misleading build failures with actionable guidance and telemetry.
Changes:
- Adds conservative polyglot AppHost detection and error handling.
- Emits language telemetry for unsupported AppHosts.
- Adds tests, telemetry documentation, and spelling configuration.
Reviewed changes
Copilot reviewed 12 out of 12 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
docs/specs/metrics-audit/telemetry-schema.md |
Documents the event and language field. |
docs/specs/metrics-audit/privacy-review-checklist.md |
Adds the language enum example. |
docs/specs/metrics-audit/feature-telemetry-matrix.md |
Maps app detection telemetry. |
docs/reference/telemetry-data.md |
Adds public telemetry references. |
cli/azd/internal/tracing/fields/fields.go |
Defines the language attribute. |
cli/azd/internal/tracing/events/events.go |
Defines the unsupported-AppHost event. |
cli/azd/internal/appdetect/dotnet_apphost.go |
Returns guidance and emits telemetry. |
cli/azd/internal/appdetect/aspire_polyglot.go |
Implements polyglot detection. |
cli/azd/internal/appdetect/aspire_polyglot_test.go |
Tests detection and error behavior. |
cli/azd/cmd/telemetry_test.go |
Checks the telemetry field constant. |
cli/azd/.vscode/cspell.yaml |
Adds Go spelling vocabulary. |
.vscode/cspell.misc.yaml |
Adds documentation vocabulary. |
- aspire_polyglot.go: preserve the relative apphost path from aspire.config.json (e.g. src/apphost.mts) instead of collapsing to filepath.Base; only case-resolve immediate children against the listing. - appdetect tests: assert the full relative path and add a subdirectory case; add an in-memory span-recorder test verifying the aspire.apphost.unsupported span and aspire.apphost.language attribute. - docs/reference/telemetry-data.md: add an App Detection row to the Feature -> Telemetry Mapping table documenting init/fresh-up-only scope. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.
Comments suppressed due to low confidence (1)
cli/azd/internal/appdetect/aspire_polyglot.go:85
- azd-code-reviewer: This only recognizes the new
aspire.config.jsonformat. The current Aspire CLI still loads and migrates valid legacy.aspire/settings.jsonconfigurations (whereappHostPathis relative to the.aspiredirectory), so a legacy Python AppHost withapphost.pyplus that explicit settings file—but nopylock.apphost.tomlorapphost_requirements.txt—still falls through to a generic source build; legacy Go/Java/Rust hosts are also missed. Read the legacy settings signal as a fallback and cover its path rebasing in tests.
// Strongest signal: aspire.config.json explicitly declares the AppHost language/path.
if configName, has := present[aspirePolyglotConfigFile]; has {
if lang, file := languageFromAspireConfig(filepath.Join(dir, configName), present); lang != "" {
return lang, filepath.Join(dir, file), true
}
}
CI cspell-lint flagged "buildpack" and "upvote" in the polyglot AppHost error message. Add file-scoped cspell overrides so the Go spell check passes. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
📋 Prioritization NoteThanks for the contribution! The linked issue isn't in the current milestone yet. |
|
Azure Pipelines: Successfully started running 1 pipeline(s). 21 pipeline(s) were filtered out due to trigger conditions. There may be pipelines that require an authorized user to comment /azp run to run. |
There was a problem hiding this comment.
Pull request overview
Copilot reviewed 12 out of 12 changed files in this pull request and generated no new comments.
Suppressed comments (3)
cli/azd/internal/appdetect/aspire_polyglot.go:167
- azd-code-reviewer: Treating any
aspire.config.jsonas Python corroboration creates a false positive. A supported C# config such as theCSharpConfigIsNotPolyglotcase, combined with an unrelated root-levelapphost.py, falls through config parsing and is then reported as unsupported Python. A valid Python config already returns earlier, so only Python-specific companion files should count here.
if _, has := present[aspirePolyglotConfigFile]; has {
return true
}
cli/azd/internal/appdetect/aspire_polyglot.go:85
- azd-code-reviewer: This reads only the unified config, but the Aspire CLI still supports and migrates legacy
.aspire/settings.jsonfiles containingappHostPathandlanguage(dotnet/aspire,AspireConfigFile.LoadOrCreate). Those projects are also explicitly in #7138's detection signals. TypeScript happens to hit the filename fallback, but legacy Go/Java/Rust AppHosts always fall through to generic source detection because their filenames are intentionally excluded below. Read the legacy config when the unified file is absent, resolvingappHostPathrelative to the.aspiredirectory, and add coverage for that format.
// Strongest signal: aspire.config.json explicitly declares the AppHost language/path.
if configName, has := present[aspirePolyglotConfigFile]; has {
if lang, file := languageFromAspireConfig(filepath.Join(dir, configName), present); lang != "" {
return lang, filepath.Join(dir, file), true
}
}
docs/specs/metrics-audit/telemetry-schema.md:165
- azd-code-reviewer: The authoritative schema records the enum and hashing decision but omits the required measurement status.
cli/azd/AGENTS.md:277-279requires every new field row to state whether it is a measurement; mark this string enum explicitly as not a measurement.
| AppHost language | `aspire.apphost.language` | SystemMetadata | FeatureInsight | Fixed enum (`typescript`/`python`/`go`/`java`/`rust`); not hashed. Emitted on `aspire.apphost.unsupported`. |
Azure Dev CLI Install InstructionsInstall scriptsMacOS/Linux
bash: pwsh: WindowsPowerShell install MSI install Standalone Binary
MSI
Documentationlearn.microsoft.com documentationtitle: Azure Developer CLI reference
|
jongio
left a comment
There was a problem hiding this comment.
Ran this locally against the PR branch. CI is green and the four earlier review comments are all genuinely addressed. Four things I hit while poking at the detector, one of which blocks a supported scenario.
hasPythonAppHostCompaniontreats anyaspire.config.jsonas Python corroboration, so a C# project plus a strayapphost.pyfailsazd init. Details inline.aspire.apphost.unsupportedfires twice perazd initwhen the AppHost sits undersrc/, which skews the very counts this event exists to produce.- The schema row is missing the measurement status that
cli/azd/AGENTS.md:277-279asks for. - JavaScript AppHosts get reported as
typescript.
One more that has no changed line to hang it on: javaDetector sits ahead of dotNetAppHostDetector in allDetectors (cli/azd/internal/appdetect/appdetect.go:186), and it claims any directory containing a pom.xml. An Aspire Java AppHost with a pom.xml gets picked up as a plain Java project before the polyglot check ever runs, so it still falls through to a source build. Worth confirming before you count on the java enum value showing up in telemetry.
| func hasPythonAppHostCompanion(present map[string]string) bool { | ||
| if _, has := present[aspirePolyglotConfigFile]; has { | ||
| return true | ||
| } |
There was a problem hiding this comment.
This returns true for any aspire.config.json, including one that explicitly declares a C# AppHost, so it can block a supported project.
Repro: aspire.config.json containing {"appHost":{"path":"AppHost/AppHost.csproj"}} at the repo root, plus a root-level apphost.py. languageFromAspireConfig correctly returns an empty language for the C# path, the filename fallback then finds apphost.py, this helper returns true purely because the config file exists, and detection reports python. Running it on your branch:
ok=true lang="python" file=".../apphost.py"
That's azd init and azd up hard-failing on a fully supported C# Aspire layout, with no flag to get past it. Note the root directory has no .csproj in that layout, so neither of the earlier dotnet checks catches it first.
A real Python config already returns early from languageFromAspireConfig, so this clause isn't buying any coverage. Dropping it fixes the repro and every existing case in TestDetectAspirePolyglotAppHost still passes, PythonWithConfig included.
| func hasPythonAppHostCompanion(present map[string]string) bool { | |
| if _, has := present[aspirePolyglotConfigFile]; has { | |
| return true | |
| } | |
| func hasPythonAppHostCompanion(present map[string]string) bool { |
Same class of problem for TypeScript: an apphost.ts next to a C# aspire.config.json also reports typescript. If the config resolves to a C# AppHost, the filename fallback arguably shouldn't run at all.
| // through to a generic source build (which produces confusing Docker/buildpack failures). | ||
| // See https://github.com/Azure/azure-dev/issues/7138. | ||
| if language, appHostFile, ok := detectAspirePolyglotAppHost(path, entries); ok { | ||
| _, span := tracing.Start( |
There was a problem hiding this comment.
This fires twice for a single azd init when the AppHost lives under src/.
internal/repository/app_init.go:52 scans ./src first and discards the error (if err == nil && len(prj) > 0), then line 59 rescans the working directory and walks back into src/. Both walks reach this detector. Confirmed with an in-memory span recorder on your branch:
src scan err: scanning directories: detecting dotnet-apphost project: detected an Aspire polyglot (typescript) AppHost at ".../src/apphost.mts"
wd scan err: scanning directories: detecting dotnet-apphost project: detected an Aspire polyglot (typescript) AppHost at ".../src/apphost.mts"
aspire.apphost.unsupported spans emitted: 2
src/ is the layout azd's own templates use, so raw event counts will run 2x for a real chunk of the population. Since sizing demand is the whole point of the event, either guard the emit with a sync.Once per process, or call out the double count in telemetry-data.md so whoever writes the query counts distinct sessions rather than events.
|
|
||
| | Field | OTel Key | Classification | Purpose | Notes | | ||
| |-------|----------|----------------|---------|-------| | ||
| | AppHost language | `aspire.apphost.language` | SystemMetadata | FeatureInsight | Fixed enum (`typescript`/`python`/`go`/`java`/`rust`); not hashed. Emitted on `aspire.apphost.unsupported`. | |
There was a problem hiding this comment.
cli/azd/AGENTS.md:277-279 asks every new schema row to state whether the field is a measurement. This row covers classification, purpose, hashing and the enum, but not that.
| | AppHost language | `aspire.apphost.language` | SystemMetadata | FeatureInsight | Fixed enum (`typescript`/`python`/`go`/`java`/`rust`); not hashed. Emitted on `aspire.apphost.unsupported`. | | |
| | AppHost language | `aspire.apphost.language` | SystemMetadata | FeatureInsight | Fixed enum (`typescript`/`python`/`go`/`java`/`rust`); not hashed; not a measurement. Emitted on `aspire.apphost.unsupported`. | |
| // AppHosts are detected and supported through the regular .NET AppHost path. | ||
| func normalizeAspireLanguage(language string) string { | ||
| switch strings.ToLower(strings.TrimSpace(language)) { | ||
| case "typescript/nodejs", "typescript", "ts", "javascript/nodejs", "javascript", "js": |
There was a problem hiding this comment.
javascript, js and javascript/nodejs all normalize to typescript. Two knock-on effects: a JavaScript AppHost gets told it has a polyglot typescript AppHost, and the telemetry can't separate JS demand from TS demand. Since the event exists to decide what to build first, is the folding deliberate, or is a separate javascript value worth it?
There was a problem hiding this comment.
@vhvb1989, I'm not 100% sure, but I think this is the list of what strings are valid:
https://github.com/microsoft/aspire/blob/main/src/Aspire.Cli/Projects/KnownLanguageId.cs
So two things:
- I don't think we could distinguish JS anyways, Aspire doesn't seem to (it's all TypeScript or TypeScript/NodeJS). @jongio (bot) - confirm?
- Can you add a link to the source file here, so we know what value you're trying to sync against, in case (in the future) we need to add more?
Summary
Fixes the confusing failure in #9335, where
azd upon an Aspire TypeScript AppHost fails with obscure Docker/buildpack errors.Root cause: azd's Aspire integration is C#-only (it relies on MSBuild's
IsAspireHostanddotnet run --publisher manifest). A polyglot (non-C#) AppHost is never recognized as Aspire, so azd falls through to a generic Oryx/buildpacks source build — which then fails (e.g. on Docker Engine 29). The real problem isn't Docker or buildpacks; azd is simply ignoring the non-C# AppHost.This PR implements step 1 of the plan: detect polyglot AppHosts early and surface an actionable error, plus emit telemetry so we can size demand before investing in full polyglot support (#7138).
What changed
internal/appdetect/aspire_polyglot.go): the .NET AppHost detector now recognizes Aspire polyglot (non-C#) AppHosts during app detection (covers bothazd initandazd upfor fresh projects). It returns anErrorWithSuggestionreferencing Support Aspire polyglot (non-C#) AppHost projects in azd #7138 instead of falling through to a source build.aspire.config.json(appHost.language/appHost.path), mirroring the Aspire CLI's language discovery.apphost.mts/apphost.ts) is trusted on its own;apphost.pyrequires a companion marker (aspire.config.json,pylock.apphost.toml, orapphost_requirements.txt); go/java/rust are only detected viaaspire.config.json. A C# AppHost (.csprojpath, no language) is never misreported as polyglot.aspire.apphost.unsupportedwith fieldaspire.apphost.language(fixed enum, not hashed). Lets us count how many users hit this and which languages they want.telemetry-data.md,telemetry-schema.md,feature-telemetry-matrix.md, andprivacy-review-checklist.mdper AGENTS.md.ErrorWithSuggestion, and a telemetry field-constant subtest.Notes / scope
azure.yamlwith a scaffolded containerapp service) won't re-run app detection onazd up, so this primarily helps freshinit/up. That's sufficient to close [Issue] azd up fails with Docker Desktop / Docker Engine 29 when packaging Aspire TypeScript AppHost using Oryx/buildpacks #9335.Refs #9335, #7138
Telemetry Change Checklist
This PR adds one telemetry event (
aspire.apphost.unsupported) and one field(
aspire.apphost.language). Perdocs/specs/metrics-audit/privacy-review-checklist.md, aprivacy review is required for any new field/event.
New Fields
fields/fields.gowith correct classification and purpose (SystemMetadata/FeatureInsight)docs/specs/metrics-audit/telemetry-schema.mdtypescript/python/go/java/rust), not user-derivedNew Events
events/events.go(AspireUnsupportedAppHostEvent)docs/specs/metrics-audit/telemetry-schema.mdaspire.apphost.unsupported=prefix.noun.verb)Privacy
SystemMetadata)CustomerContentemitted in telemetryTesting
TestDotNetAppHostDetector_EmitsUnsupportedTelemetryuses an in-memory span recorder)TestTelemetryFieldConstants→AspireFieldssubtest)TestDetectAspirePolyglotAppHost,TestDotNetAppHostDetector_PolyglotReturnsSuggestionError)Downstream
Documentation
feature-telemetry-matrix.md— App detection cross-cutting subsystem)telemetry-schema.md— Aspire field + event rows)docs/reference/telemetry-data.md— event, field, and Feature → Telemetry Mapping row with command scope)privacy-review-checklist.md)