dotnet / dotnet/aspnetcore

Custom BeforeBuild target's generated wwwroot file missing from staticwebassets.endpoints.json depending on build environment

Open
#68,641 13 comments 0 reactions 0 assignees View on GitHub
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

### Summary

A custom MSBuild target that generates a file into `wwwroot` and is hooked at `BeforeTargets="BeforeBuild"` produces the file correctly on disk during `dotnet publish`, but the file's route is sometimes **entirely missing** from the generated `{Project}.staticwebassets.endpoints.json`, so `MapStaticAssets()` never registers an endpoint for it — every request 404s, even though the file exists in the published output at the correct size/content.

This isn't just "wrong extensibility point" (see #68108, which recommends `BeforeTargets="AssignTargetPaths"` for this exact scenario). In our case, moving the hook there (and to `AssignTargetPaths;Publish`), and separately splitting `dotnet build` + `dotnet publish --no-build` into two invocations, **both still reproduced the bug**. The only thing that reliably avoided it was generating the file in a fully separate process *before* invoking `dotnet build`/`publish` at all.

More surprisingly, **the bug is highly environment-sensitive in a way that doesn't correlate with CPU/memory**:
- Building and running the exact same commit **locally** (Docker Desktop on macOS, both native arm64 and via QEMU amd64 emulation, and via the classic non-BuildKit builder under `--memory`/`--cpu-quota` cgroup limits from 8 GB down to ~768 MB / 1 CPU before the build gets OOM-killed outright) **always** produces a correct manifest.
- Deploying the **same commit** to Render.com (their documented free-tier "Starter" build machine: 2 CPU / 8 GB RAM) **always** produces a manifest missing the route. Confirmed via a temporary diagnostic endpoint that the file was present on disk at the correct size; only the manifest entry was missing.
- Running the exact same Dockerfile in a minimal repro repo on **GitHub Actions `ubuntu-latest`** (4 CPU / 15 GB RAM, plain Docker Engine, no unusual sandboxing) **also always** reproduces it.

So it's not about being resource-starved — GitHub Actions' runner is more generously specced than several local configurations that worked fine. Whatever the actual trigger is, it's consistent *within* an environment and differs *between* environments in a way we couldn't pin down (our best untested guess: something about running inside a genuine Linux VM on a Linux hypervisor vs. Docker Desktop's macOS-hosted virtualization — but we didn't verify this).

### Minimal repro

https://github.com/Popl7/essessay-tailwind-manifest-repro

- `Essessay/Essessay.csproj` defines:
```xml




```
generating `wwwroot/css/site.css`, referenced from a Razor view and served via `app.MapStaticAssets()` (`Program.cs`), .NET 10, `Microsoft.NET.Sdk.Web`.
- `Essessay/Dockerfile` builds it with a single `RUN dotnet publish Essessay/Essessay.csproj -c Release -o /app --no-restore`.
- `.github/workflows/check-tailwind-manifest.yml` builds the image with `--no-cache` on `ubuntu-latest` and greps the published `Essessay.staticwebassets.endpoints.json` for a `css/site.css` route, failing the job if it's missing.

Two runs, both failed the same way:
- https://github.com/Popl7/essessay-tailwind-manifest-repro/actions/runs/32241988885
- https://github.com/Popl7/essessay-tailwind-manifest-repro/actions/runs/32241993765

Relevant excerpt from the job log — the file exists, correctly sized, but only the unrelated Identity Razor Class Library's own `css/site.css` shows up:
```
--- disk ---
-rw-r--r-- 1 root root 38287 Aug 19 10:18 site.css
--- manifest routes for site.css ---
"Route":"Identity/css/site.css"
"Route":"Identity/css/site.css"
"Route":"Identity/css/site.css"
"Route":"Identity/css/site.css.br"
"Route":"Identity/css/site.css.gz"
FAIL: css/site.css route is MISSING from the manifest (bug reproduced)
```

You can re-run the workflow yourself via `workflow_dispatch` on that repo, or fork it — it's a full, working ASP.NET Core MVC app, `--no-cache` build, no other dependencies.

### Expected

`wwwroot/css/site.css`, generated during `BeforeBuild`, gets a route in the published `{Project}.staticwebassets.endpoints.json`, deterministically, regardless of the machine/environment the build runs on.

### Actual

The route is silently missing depending on the build environment (see above), despite the file itself being generated correctly every time.

### What we tried

1. **Baseline** (`BeforeTargets="BeforeBuild"`, single `dotnet publish`) — fails on GitHub Actions / Render, works locally.
2. **Split `dotnet build` (with the target) + `dotnet publish --no-build`** — still fails, even with the target's `Exec` running in a separate MSBuild invocation from the one that finalizes the publish manifest.
3. **Re-hook at `BeforeTargets="AssignTargetPaths;Publish"`** per #68108 (still one `dotnet publish` invocation) — still fails.
4. **Workaround that holds**: run the target as its own, fully separate process before `dotnet build`/`publish` starts at all (`dotnet msbuild Essessay.csproj -t:BuildTailwindCss`, then `dotnet publish ... -p:SkipTailwindBuild=true` to skip it the second time). This is the only approach that has served correctly across multiple Render deploys and would presumably also fix the GitHub Actions repro (untested there, since the repro repo intentionally keeps the original buggy Dockerfile).

### Related, but not (as far as we could tell) duplicates

- #68108 — recommends `AssignTargetPaths` as the hook point; insufficient in our case.
- #56755, #46156 — both about the manifest going stale/missing relative to actual `wwwroot` content, but in different scenarios (files removed rather than added, and a hard failure rather than a silently incomplete manifest).

Happy to add more diagnostics to the repro repo if it helps narrow this down — e.g. `dotnet --info`, MSBuild binlog, or testing the `AssignTargetPaths;Publish` hook on GitHub Actions specifically.

Contributor guide

Open the contributing guide

Research direction

Start with the minimal repro's Essessay/Essessay.csproj, Dockerfile, and .github/workflows/check-tailwind-manifest.yml, then rerun the workflow that checks the published Essessay.staticwebassets.endpoints.json. Compare the generated manifest with the wwwroot/css/site.css file across the reported environments. Done means the css/site.css route is present deterministically in the manifest and the workflow check passes.

Written by the indexing model from the issue text.

Assessment

Tech stack
csharp, docker, github-actions
Domain
build-system
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
48/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.