Why
Today every Fallout-using repo carries `build.cmd` (7 lines), `build.sh` (~70 lines), and `build.ps1` (~70 lines). They do two jobs:
- Provision the .NET SDK locally (`dotnet-install.sh/ps1` → `.fallout/temp/`) when it's missing.
- Compile + run `build/_build.csproj` via `dotnet build` + `dotnet run --project --no-build`.
The `fallout` global tool exists but, for target execution, just shells out to `bash ./build.sh` — it's a launcher, not a runner. We ship a .NET build tool whose canonical entry point is a shell script. That's the thing we want to fix.
Goal: one logical entry point (`fallout`), one C# code path for target execution, no `.cmd`, and thin shell shims only for the "I just cloned the repo without `dotnet` installed yet" case.
Approach
Three PRs, sequenced. The split keeps PR-A as a pure additive change (the in-tool runner ships dormant; CI doesn't depend on it yet) so it can be released independently. Downstream consumers who upgrade `Fallout.GlobalTool` pick up the new runner; their old fat shims still work; they can adopt thin shims on their own schedule by re-running `fallout :setup`.
PR-A — Add in-tool build runner (#201)
Status: open, code+tests done locally, waiting on GitHub Actions to recover (see "Known issues" below).
Adds a code path so `fallout ` discovers and runs the build project directly, instead of spawning `./build.sh`/`./build.ps1`.
- New `src/Fallout.GlobalTool/BuildProjectResolver.cs` — reads optional `BuildProjectFile` field from `.fallout/parameters.json` if set; otherwise convention `build/_build.csproj`. 6 unit tests cover the resolution cases.
- New `src/Fallout.GlobalTool/Program.Run.cs` — resolves `dotnet` (PATH first; `.fallout/temp/dotnet-{unix,win}` as fallback), seeds the same env vars the shim sets (`DOTNET_CLI_TELEMETRY_OPTOUT`, `DOTNET_NOLOGO`, `DOTNET_ROLL_FORWARD`, `FALLOUT_TELEMETRY_OPTOUT`, plus the existing `FALLOUT_GLOBAL_TOOL_*` markers), runs `dotnet build` then `dotnet run --project --no-build -- ` as subprocesses with inherited stdio.
- Modified `src/Fallout.GlobalTool/Program.cs` — `Handle`'s no-`:` fallback now calls `Run(...)` instead of `Build(buildScript, ...)`. The `buildScript == null` branch of the "Could not find" prompt is removed.
- New `tests/Fallout.GlobalTool.Tests/BuildProjectResolverTests.cs` — 6 tests, green locally.
`Build()` method is NOT deleted — still called from `Program.Complete.cs` as a bootstrap path for the first-time completion request. Removed in PR-B alongside the shim deletion.
PR-B — Switch the repo to thin shims + local tool manifest, regenerate workflows
Status: not started; blocked on PR-A landing and a `Fallout.GlobalTool` release that ships the new runner.
- New `.config/dotnet-tools.json` pinning `Fallout.GlobalTool` as a local tool.
- Replace `build.sh` and `build.ps1` with thin versions (~20 lines each): keep the dotnet-provisioning block verbatim from the current scripts (`build.sh:36-68`, `build.ps1:41-75`), drop the `BUILD_PROJECT_FILE` config and the explicit `dotnet build` / `dotnet run --project` lines, end with `tool restore` + `exec dotnet fallout "$@"`.
- Delete `build.cmd`.
- Update the `[GitHubActions]` generator at `src/Fallout.Common/CI/GitHubActions/Configuration/GitHubActionsRunStep.cs:23` — currently emits `run: ./{BuildCmdPath} {targets}` as a single step; replace with three steps: `actions/setup-dotnet@v4` (reads `global.json`), `dotnet tool restore`, `dotnet fallout {targets}`. Mark `BuildCmdPath` property on `GitHubActionsAttribute` as `[Obsolete]`. This is a breaking change for downstream consumers using `[GitHubActions(InvokedTargets = ...)]` — their next regen produces three steps instead of one.
- Regenerate workflows via `fallout GenerateConfiguration` — the four files `.github/workflows/{ubuntu-latest,macos-latest,windows-latest,release}.yml` get the new shape. Do NOT edit these by hand; they're auto-generated.
- Update `fallout :setup` scaffolder at `src/Fallout.GlobalTool/Program.Setup.cs` `WriteBuildScripts()` (lines 102-240): emit thin `build.sh`/`build.ps1` from rewritten templates, emit `.config/dotnet-tools.json` from a new template, drop `build.cmd` emission, delete `src/Fallout.GlobalTool/templates/build.cmd`. Add merge logic for the case where consumer already has `.config/dotnet-tools.json`.
- Update `.fallout/build.schema.json` generation at `src/Fallout.Build/Execution/Extensions/HandleShellCompletionAttribute.cs:45` (or thereabouts) — teach the schema-emitter about the new optional `BuildProjectFile` field so IDE IntelliSense works for users editing `parameters.json`. (Manual edits to the schema file don't stick because it's regenerated on every build run.)
PR-C — Docs cleanup
Status: tracked in #198. Re-scope #198 (or open a follow-up) to cover the new shim shape: `docs/01-getting-started/02-setup.md`, `docs/01-getting-started/03-execution.md`, `docs/05-cicd/github-actions.md` (and the rest of `docs/05-cicd/*` if revived per #8), `CONTRIBUTING.md`, `CLAUDE.md`.
Backwards compatibility
| Scenario |
Behaviour |
| Existing repo, old fat shims, run `./build.sh Compile` |
Unchanged. Old shims do their own `dotnet build` + `dotnet run --project` — they don't depend on the global tool. |
| Existing repo, old fat shims, run `fallout Compile` |
Uses the new in-tool runner. Same end result, one fewer subprocess hop. |
| Existing repo with thin shims, old globally-installed `fallout` |
Thin shim does `dotnet tool restore` + `dotnet fallout`; manifest-pinned (newer) version takes over. Safe. |
| Empty directory, no `.fallout/` |
Prompt for `:setup` as before. |
Known issues / things to watch
1. GitHub Actions degraded performance (active as of 2026-05-26 09:30 UTC)
`pull_request` workflows weren't triggering across the whole repo for the past 30+ minutes — confirmed via `githubstatus.com`:
```
Actions degraded_performance
Pages degraded_performance
```
This affected #201 and #202 — neither has CI signal yet. Verify GH is healthy first thing tomorrow; if checks haven't appeared on those PRs, an empty-commit push usually nudges things. We already burned the close-and-reopen trick on PR-200 (now closed); the work landed on `feat/globaltool-runner` instead.
2. `Nuke.Common.Shim.Tests` silently broke windows-latest on main (fixed in #202)
The project is compile-only by design — see `SampleConsumerBuild.cs` header. It had zero `[Fact]`/`[Theory]` methods. `dotnet test` against a project with no discoverable tests exits 0 on Linux/macOS but 1 on Windows, so every post-merge push to `main` was failing the `windows-latest` validation step. `ubuntu-latest` and `macos-latest` were green. No-one noticed until the in-tool runner work surfaced it.
#202 adds a trivial `[Fact]` (`Shim_Compiles_When_Test_Assembly_Loads`) — discovery now succeeds on all platforms. After it merges, the next push to main should go green on windows-latest. Verify that.
3. `build.schema.json` is regenerated on every build run
By `HandleShellCompletionAttribute` (`src/Fallout.Build/Execution/Extensions/HandleShellCompletionAttribute.cs`). Manual edits don't stick. Add the `BuildProjectFile` field to the schema-generation code in PR-B, not by hand.
4. Branch-name workaround for stuck CI
The original PR-A branch `feat/global-tool-in-tool-runner` never got workflow runs (likely a GH-side webhook hiccup amplified by the ongoing Actions degradation). PR-200 was closed; the code was re-pushed to a fresh branch `feat/globaltool-runner` and reopened as #201. If a branch ever gets stuck like that again, recreate under a different name as a first attempt.
Pickup checklist (Windows laptop, tomorrow)
References
Why
Today every Fallout-using repo carries `build.cmd` (7 lines), `build.sh` (~70 lines), and `build.ps1` (~70 lines). They do two jobs:
The `fallout` global tool exists but, for target execution, just shells out to `bash ./build.sh` — it's a launcher, not a runner. We ship a .NET build tool whose canonical entry point is a shell script. That's the thing we want to fix.
Goal: one logical entry point (`fallout`), one C# code path for target execution, no `.cmd`, and thin shell shims only for the "I just cloned the repo without `dotnet` installed yet" case.
Approach
Three PRs, sequenced. The split keeps PR-A as a pure additive change (the in-tool runner ships dormant; CI doesn't depend on it yet) so it can be released independently. Downstream consumers who upgrade `Fallout.GlobalTool` pick up the new runner; their old fat shims still work; they can adopt thin shims on their own schedule by re-running `fallout :setup`.
PR-A — Add in-tool build runner (#201)
Status: open, code+tests done locally, waiting on GitHub Actions to recover (see "Known issues" below).
Adds a code path so `fallout ` discovers and runs the build project directly, instead of spawning `./build.sh`/`./build.ps1`.
`Build()` method is NOT deleted — still called from `Program.Complete.cs` as a bootstrap path for the first-time completion request. Removed in PR-B alongside the shim deletion.
PR-B — Switch the repo to thin shims + local tool manifest, regenerate workflows
Status: not started; blocked on PR-A landing and a `Fallout.GlobalTool` release that ships the new runner.
PR-C — Docs cleanup
Status: tracked in #198. Re-scope #198 (or open a follow-up) to cover the new shim shape: `docs/01-getting-started/02-setup.md`, `docs/01-getting-started/03-execution.md`, `docs/05-cicd/github-actions.md` (and the rest of `docs/05-cicd/*` if revived per #8), `CONTRIBUTING.md`, `CLAUDE.md`.
Backwards compatibility
Known issues / things to watch
1. GitHub Actions degraded performance (active as of 2026-05-26 09:30 UTC)
`pull_request` workflows weren't triggering across the whole repo for the past 30+ minutes — confirmed via `githubstatus.com`:
```
Actions degraded_performance
Pages degraded_performance
```
This affected #201 and #202 — neither has CI signal yet. Verify GH is healthy first thing tomorrow; if checks haven't appeared on those PRs, an empty-commit push usually nudges things. We already burned the close-and-reopen trick on PR-200 (now closed); the work landed on `feat/globaltool-runner` instead.
2. `Nuke.Common.Shim.Tests` silently broke windows-latest on main (fixed in #202)
The project is compile-only by design — see `SampleConsumerBuild.cs` header. It had zero `[Fact]`/`[Theory]` methods. `dotnet test` against a project with no discoverable tests exits 0 on Linux/macOS but 1 on Windows, so every post-merge push to `main` was failing the `windows-latest` validation step. `ubuntu-latest` and `macos-latest` were green. No-one noticed until the in-tool runner work surfaced it.
#202 adds a trivial `[Fact]` (`Shim_Compiles_When_Test_Assembly_Loads`) — discovery now succeeds on all platforms. After it merges, the next push to main should go green on windows-latest. Verify that.
3. `build.schema.json` is regenerated on every build run
By `HandleShellCompletionAttribute` (`src/Fallout.Build/Execution/Extensions/HandleShellCompletionAttribute.cs`). Manual edits don't stick. Add the `BuildProjectFile` field to the schema-generation code in PR-B, not by hand.
4. Branch-name workaround for stuck CI
The original PR-A branch `feat/global-tool-in-tool-runner` never got workflow runs (likely a GH-side webhook hiccup amplified by the ongoing Actions degradation). PR-200 was closed; the code was re-pushed to a fresh branch `feat/globaltool-runner` and reopened as #201. If a branch ever gets stuck like that again, recreate under a different name as a first attempt.
Pickup checklist (Windows laptop, tomorrow)
References
nukereferences withfalloutacross docs/ #198