Skip to content

Fix .NET runtime winget ingestion after upstream Scope flip - #51022

Merged
allenhouchins merged 1 commit into
mainfrom
allenhouchins/fix-dotnet-winget-scope
Aug 12, 2026
Merged

Fix .NET runtime winget ingestion after upstream Scope flip#51022
allenhouchins merged 1 commit into
mainfrom
allenhouchins/fix-dotnet-winget-scope

Conversation

@allenhouchins

@allenhouchins allenhouchins commented Aug 12, 2026

Copy link
Copy Markdown
Member

Related issue: N/A — fixes a failing scheduled workflow (Ingest maintained apps)

What's happening

The Ingest maintained apps workflow is failing on every run:

panic: ingesting winget app: failed to find installer for app

Because a single unmatched app aborts the whole run, no FMA update PR is being generated at all. This cron runs every 4 hours, so it has been failing repeatedly since the upstream change landed — not just once.

Root cause

Microsoft republished the Microsoft.DotNet.* winget manifests on manifest schema 1.12.0 and added a top-level Scope: machine that the previous 1.9.0 manifests did not carry:

  PackageVersion: 10.0.11
+ Scope: machine
...
- ManifestVersion: 1.9.0
+ ManifestVersion: 1.12.0

The ingester reads the manifest-level scope first (scope := m.Scope, ingester.go:312). Previously that was empty, so it fell through to the per-installer scope (also empty) and then to the installer-type switch — which has no exe case, leaving scope as "" and matching the input's "installer_scope": "".

Now it resolves to "machine", so the match at ingester.go:338:

matches := installer.Architecture == input.InstallerArch &&
    scope == input.InstallerScope &&
    ...

fails for every installer in the manifest, selectedInstaller stays nil, and the ingester returns failed to find installer for app.

The package was not removed from winget — a removal would surface as a 404 from GetContents, not this error.

The fix

Set "installer_scope": "machine" on the three affected inputs and regenerate their outputs.

exe is present in fileTypes, so the machine-scope→MSI coercion at ingester.go:326 does not apply and installer_type: "exe" still matches. Worth confirming on review — without that, the scope fix alone would have moved the failure rather than resolved it.

All three .NET inputs were affected, not just the one named in the log. The run aborted at the first failure and filename ordering put microsoft-dotnet-desktop-runtime-10 ahead of the other two, so they were never reached:

Package Version Was
Microsoft.DotNet.DesktopRuntime.10 10.0.11 10.0.10
Microsoft.DotNet.Runtime.10 10.0.11 10.0.10
Microsoft.DotNet.Runtime.8 8.0.30 8.0.29

Microsoft also moved the download host from builds.dotnet.microsoft.com to download.microsoft.com, so the installer URLs and SHA256s in the regenerated outputs change too.

Checklist for submitter

  • Input data is properly validated, SELECT * is avoided, SQL injection is prevented (using placeholders for values in statements), JS inline code is prevented especially for url redirects, and untrusted data interpolated into shell scripts/commands is validated against shell metacharacters.

No changes file: this is FMA manifest data, consistent with previous FMA data fixes (e.g. #50370).

Testing

  • QA'd all new/changed functionality manually

Ran the ingester locally against live upstream manifests for each affected slug — all three ingest cleanly where they previously panicked:

go run cmd/maintained-apps/main.go -slug microsoft-dotnet-desktop-runtime-10/windows
go run cmd/maintained-apps/main.go -slug microsoft-dotnet-runtime-10/windows
go run cmd/maintained-apps/main.go -slug microsoft-dotnet-runtime-8/windows

The regenerated outputs carry real upstream SHA256s and the new download URLs.

Because the ingester aborts on the first failure, fixing only these three would move the panic rather than resolve it if any other manifest got the same treatment. So every winget input was ingested individually against live upstream manifests:

SWEPT 434 apps. failures: 0

No other package is affected by the Scope flip.

Unrelated fragility noticed while verifying

A full-run attempt died on a different app with:

panic: ingesting winget app: getting winget manifest locale file contents: Get
"https://api.github.com/.../Notion.Notion.locale.en-US.yaml": read: connection reset by peer

That was a transient TCP reset, but it is still fatal to the entire run: isTransientGitHubError only recognizes RateLimitError, AbuseRateLimitError, and ErrorResponse with 429/5xx, so a bare connection reset falls through to the fatal path and takes all 434 apps down with it. Out of scope for this PR, but worth a follow-up given this workflow runs every 4 hours.

Summary by CodeRabbit

  • New Features
    • .NET Desktop Runtime 10 and .NET Runtime 8/10 installations now use machine-wide scope.
  • Updates
    • Updated Windows .NET Desktop Runtime 10 to version 10.0.11.
    • Updated Windows .NET Runtime 10 to version 10.0.11.
    • Updated Windows .NET Runtime 8 to version 8.0.30.
    • Refreshed installer and uninstall details for each updated version.

Microsoft republished the Microsoft.DotNet.* winget manifests on schema
1.12.0 and added a top-level "Scope: machine" that the previous 1.9.0
manifests did not carry. The ingester reads the manifest-level scope first
(ingester.go:312), so scope became "machine" where it had resolved to "",
and the installer match on scope == input.InstallerScope failed for every
installer in the manifest. That returns "failed to find installer for app",
which is fatal for the whole run:

  panic: ingesting winget app: failed to find installer for app

Set installer_scope to "machine" on the three affected inputs and regenerate
their outputs. "exe" is in fileTypes, so the machine-scope MSI coercion at
ingester.go:326 does not apply and the type still matches.

Also picks up the versions the failed runs never ingested:
.NET Desktop Runtime 10 and .NET Runtime 10 to 10.0.11, .NET Runtime 8 to
8.0.30.
@github-actions

Copy link
Copy Markdown
Contributor

Script Diff Results

ee/maintained-apps/outputs/microsoft-dotnet-desktop-runtime-10/windows.json

=== Install Script (no changes) ===
=== Uninstall // 661ee74e -> a95e65db ===

--- /tmp/old.2XPGDG	2026-08-12 02:26:05.179388645 +0000
+++ /tmp/new.qQlljF	2026-08-12 02:26:05.179388645 +0000
@@ -8,7 +8,7 @@
 # We target the bundle by its ProductCode (injected by the ingester) and fall back
 # to the cached bootstrapper in the Package Cache.
 
-$productCode = '{866BECDA-F284-473A-9E84-0CCE816BF06F}'
+$productCode = '{96749152-C361-49E0-BAC6-818F491B9276}'
 
 function Invoke-Uninstaller {
     param([string]$exe, [string]$exeArgs)

ee/maintained-apps/outputs/microsoft-dotnet-runtime-10/windows.json

=== Install Script (no changes) ===
=== Uninstall // 9da7afd4 -> f4222761 ===

--- /tmp/old.4GTMzN	2026-08-12 02:26:05.235388187 +0000
+++ /tmp/new.bDfU2X	2026-08-12 02:26:05.236388179 +0000
@@ -8,7 +8,7 @@
 # We target the bundle by its ProductCode (injected by the ingester) and fall back
 # to the cached bootstrapper in the Package Cache.
 
-$productCode = '{086851AF-5BEF-47A0-95C5-A8EED09F3F36}'
+$productCode = '{9515CC72-C8E1-498C-9423-E0DA05D6B55B}'
 
 function Invoke-Uninstaller {
     param([string]$exe, [string]$exeArgs)

ee/maintained-apps/outputs/microsoft-dotnet-runtime-8/windows.json

=== Install Script (no changes) ===
=== Uninstall // d9aa0345 -> da06c0ef ===

--- /tmp/old.dIOcN5	2026-08-12 02:26:05.283387795 +0000
+++ /tmp/new.sGrR08	2026-08-12 02:26:05.284387787 +0000
@@ -8,7 +8,7 @@
 # We target the bundle by its ProductCode (injected by the ingester) and fall back
 # to the cached bootstrapper in the Package Cache.
 
-$productCode = '{0636c53d-0ac0-4636-9b38-7fe0a4f09a00}'
+$productCode = '{bba240a5-2d02-4aed-99b0-f1b79cb1c4c1}'
 
 function Invoke-Uninstaller {
     param([string]$exe, [string]$exeArgs)

@allenhouchins
allenhouchins marked this pull request as ready for review August 12, 2026 03:03
Copilot AI lite review requested due to automatic review settings August 12, 2026 03:03

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR updates Fleet-maintained app (FMA) winget input manifests for Microsoft .NET runtimes to match an upstream winget manifest change (Scope: machine), preventing the winget ingester from failing to select an installer and aborting the scheduled “Ingest maintained apps” workflow.

Changes:

  • Set installer_scope to "machine" for the three affected winget inputs (Microsoft.DotNet.*).
  • Regenerated the corresponding Windows output manifests to bump versions and update installer URLs/SHA256s (and related uninstall ProductCodes).

Reviewed changes

Copilot reviewed 6 out of 6 changed files in this pull request and generated no comments.

Show a summary per file
File Description
ee/maintained-apps/inputs/winget/microsoft-dotnet-runtime-8.json Sets installer_scope to machine to match upstream manifest scope.
ee/maintained-apps/inputs/winget/microsoft-dotnet-runtime-10.json Sets installer_scope to machine to match upstream manifest scope.
ee/maintained-apps/inputs/winget/microsoft-dotnet-desktop-runtime-10.json Sets installer_scope to machine to match upstream manifest scope.
ee/maintained-apps/outputs/microsoft-dotnet-runtime-8/windows.json Regenerates output (version bump, updated download URL/SHA, updated uninstall ProductCode).
ee/maintained-apps/outputs/microsoft-dotnet-runtime-10/windows.json Regenerates output (version bump, updated download URL/SHA, updated uninstall ProductCode).
ee/maintained-apps/outputs/microsoft-dotnet-desktop-runtime-10/windows.json Regenerates output (version bump, updated download URL/SHA, updated uninstall ProductCode).

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: 947cf393-23ea-4111-be10-85cce92b12e2

📥 Commits

Reviewing files that changed from the base of the PR and between a6e50e9 and 2a71a61.

📒 Files selected for processing (6)
  • ee/maintained-apps/inputs/winget/microsoft-dotnet-desktop-runtime-10.json
  • ee/maintained-apps/inputs/winget/microsoft-dotnet-runtime-10.json
  • ee/maintained-apps/inputs/winget/microsoft-dotnet-runtime-8.json
  • ee/maintained-apps/outputs/microsoft-dotnet-desktop-runtime-10/windows.json
  • ee/maintained-apps/outputs/microsoft-dotnet-runtime-10/windows.json
  • ee/maintained-apps/outputs/microsoft-dotnet-runtime-8/windows.json

Walkthrough

The Winget inputs now specify machine-wide installation for .NET Desktop Runtime 10, .NET Runtime 10, and .NET Runtime 8. The Windows outputs update .NET Desktop Runtime 10 to 10.0.11, .NET Runtime 10 to 10.0.11, and .NET Runtime 8 to 8.0.30. Each update includes new patch thresholds, installer URLs, checksums, uninstall references, and bundle product codes.

Possibly related PRs

  • fleetdm/fleet#50361: Introduced the .NET Desktop Runtime 10 definitions updated by this PR.
  • fleetdm/fleet#50352: Also changes Winget configuration for machine-wide .NET Desktop Runtime 10 installation.
  • fleetdm/fleet#49487: Updates maintained-app metadata with newer versions and installer details.
🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the fix for .NET Winget ingestion after the upstream scope change.
Description check ✅ Passed The description explains the failure, root cause, fix, affected packages, testing, and the decision to exclude unrelated changes.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch allenhouchins/fix-dotnet-winget-scope

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@allenhouchins
allenhouchins merged commit 531fa7d into main Aug 12, 2026
19 checks passed
@allenhouchins
allenhouchins deleted the allenhouchins/fix-dotnet-winget-scope branch August 12, 2026 13:54
allenhouchins added a commit that referenced this pull request Aug 12, 2026
…51040)

<!-- Add the related story/sub-task/bug number, like Resolves #123, or
remove if NA -->
**Related issue:** N/A — fixes a failing scheduled workflow ([Ingest
maintained
apps](https://github.com/fleetdm/fleet/actions/workflows/ingest-maintained-apps.yml))

## What's happening

The `Ingest maintained apps` workflow panics on every run:

```
{"level":"INFO","msg":"ingesting homebrew app","name":"Worksheet Crafter"}
panic: ingesting homebrew app: app not found in brew API
```

The homebrew ingester aborts on the first failing app, so no FMA update
PR is generated at all — the failure is not limited to Worksheet
Crafter's own data.

## Root cause

Homebrew renamed the cask from `worksheet-crafter` to
`worksheetcrafter`. The old token now 404s, which `fetchCask` treats
(correctly) as a non-transient, fatal error at
[ingester.go:533](https://github.com/fleetdm/fleet/blob/main/ee/maintained-apps/ingesters/homebrew/ingester.go#L533).

The upstream cask records the rename itself:

```json
{
  "token": "worksheetcrafter",
  "old_tokens": ["worksheet-crafter"],
  "name": ["WorksheetCrafter"],
  "version": "2026.2.5"
}
```

The app is not gone from Homebrew, and it is unrelated to the app being
`frozen` — `frozen: true` only gates the *output write* in
[main.go:106](https://github.com/fleetdm/fleet/blob/main/cmd/maintained-apps/main.go#L106);
the ingester still fetches the cask for every input, so a frozen app
with a dead token still takes the whole run down.

## The fix

Point the input at the new token:

```diff
-  "token": "worksheet-crafter",
+  "token": "worksheetcrafter",
```

Deliberately unchanged:

- **`slug` stays `worksheet-crafter/darwin`.** It is user-facing and
already published in `apps.json`, and the validator derives the input
file path from the slug (`isFrozen()` in
[app_commander.go:36](https://github.com/fleetdm/fleet/blob/main/cmd/maintained-apps/validate/app_commander.go#L36)
reads `inputs/homebrew/<slug-name>.json`), so the input file keeps its
current name too.
- **`name` stays `Worksheet Crafter`.** The output's `name` comes from
the input, not the cask, so adopting the cask's new `WorksheetCrafter`
spelling would only churn the display name for existing users.
- **`frozen: true` stays.** The freeze predates this (#49055) and is a
separate question; this PR is scoped to unbreaking the workflow. The
output stays at 2026.2.4 while upstream is at 2026.2.5.

## Checklist for submitter

- [x] Input data is properly validated, `SELECT *` is avoided, SQL
injection is prevented (using placeholders for values in statements), JS
inline code is prevented especially for url redirects, and untrusted
data interpolated into shell scripts/commands is validated against shell
metacharacters.

No changes file: this is FMA manifest data, consistent with previous FMA
data fixes (e.g. #51022).

## Testing

- [x] QA'd all new/changed functionality manually

The affected slug now ingests cleanly where it previously panicked, and
writes nothing (it is frozen and its output already exists):

```
go run ./cmd/maintained-apps -slug worksheet-crafter/darwin
```

Because the run aborts on the first failure, a fix for one app can just
move the panic to the next one. Two checks against that:

1. Every homebrew input token was cross-checked against the full brew
cask index (`https://formulae.brew.sh/api/cask.json`, 7691 casks).
`worksheet-crafter` was the only token missing — no other input is at
risk of a 404.
2. The whole homebrew leg was run end to end (`-slug /darwin` matches
all 962 homebrew inputs and no winget inputs). It completed with exit 0.
`outputs/worksheet-crafter/darwin.json` was untouched, as expected for a
frozen app; the other output changes were ordinary upstream version
bumps and were reverted rather than included here, since they belong to
the automated update PR.

### Unrelated issue noticed while verifying

The full run logged, without failing:

```
Error enriching app net.whatsapp.WhatsApp: Expected WhatsApp version to start with '2.' but found '26.32.15'
```

This is pre-existing, not something this PR touches. WhatsApp has moved
to a `26.x` version scheme, so `whats_app_version_shortener.go`'s
`2.`-prefix assumption no longer holds. `EnrichManifest` only prints the
error and moves on
([main.go:118](https://github.com/fleetdm/fleet/blob/main/ee/maintained-apps/ingesters/homebrew/external_refs/main.go#L118)),
so the shortener is skipped and the unshortened version ships —
`outputs/whatsapp/darwin.json` is already on `26.32.12`. Worth a
follow-up.


<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->

## Summary by CodeRabbit

* **Bug Fixes**
* Updated the Homebrew app token to use the correct identifier for
Worksheet Crafter.

<!-- end of auto-generated comment: release notes by coderabbit.ai -->
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.

3 participants