Repository navigation
Four shipped projects carry independent <Version> literals; the release gate reads a deprecated one and the nightly reads a different one #3222
Description
Activity
Claude posting for Erik Darling
Three corrections, and the third reframes this issue: the repository already diagnosed and fixed this exact class one level down, and #2113's own comment describes the state this issue is reporting.
1. There are SIX
<Version>elements, not four, and my count was wrong by construction. I grepped for3.6.0— so it could only find files already carrying that value. Counting by element instead:file value Darling/PerformanceMonitor.Darling.Service/…csproj:83.6.0 Darling/PerformanceMonitor.Darling.Viewer/…csproj:183.6.0 Lite/PerformanceMonitorLite.csproj:213.6.0 deprecated/Dashboard/Dashboard.csproj:103.6.0 deprecated/Installer.Core/Installer.Core.csproj:103.3.0 deprecated/Installer/PerformanceMonitorInstaller.csproj:233.3.0 A search shaped like the answer you expect returns a confident partial count and no error. Credit to the reader who counted independently.
2. The trap has already fired, and the evidence is in the tree. Two literals sit at 3.3.0 — three releases behind. So the deprecated subtree is not merely detached from the shipped one, it is internally inconsistent, and the release gate reads into that subtree.
3. #2113 solved this within a project and nobody applied the reasoning across them. Each of the three shipped projects carries this comment verbatim:
#2113: <Version> is the ONE hand-set version. AssemblyVersion / FileVersion / InformationalVersion derive from it at build time - the 3.4.0 release bumped only <Version> and shipped binaries still STAMPED 3.3.0.0, because four hand-maintained copies of one fact is a release-day trap."Four hand-maintained copies of one fact is a release-day trap" is this issue, stated by the repository, about itself, one level down. #2113 found four version properties diverging inside a project and fixed it by deriving them from one. There are now six version elements diverging across projects, and the same remedy is available at the solution level — a single
<Version>inDirectory.Build.propsthat the six inherit.So the framing in the issue body — "a frozen convention with no instrument" — is wrong in the reporting direction. The convention is documented, three times, in the files it governs. What is missing is that the documentation is per-project while the fact is repository-wide, and the one place a reviewer would look for a repository-wide fact does not hold it.
What this changes about the fix
The "centralise or pin" trade in the issue body still stands, but centralising is now the strongly indicated arm rather than a coin flip: it is what #2113 chose for the same problem, and its stated reason applies unchanged one level up. Pinning six literals to agree would leave six copies of a fact whose duplication the repository has already called a release-day trap.
Two things a centralising change must decide, neither of which #2113 faced:
- Whether the deprecated projects join. They are at 3.3.0 and ship to nobody. Bringing them to one version is either correct or a lie about what was last built from them — and the release gate currently reads one of them, so their version is not inert.
- Whether
check-version-bump.ymlfollows. It should stop reading a deprecated project regardless. If the version centralises, it should read the central file; if it does not, it should read one that ships.
And this issue gates something outward-facing
The hold on #3196's reporter reply has a condition that the version named must be verified to contain the change, not guessed as the next number. That condition is currently unanswerable by inspection, not merely unmet:
check-version-bump.ymlstamps its judgement fromdeprecated/Dashboard/Dashboard.csprojwhilenightly.yml:114names every artifact fromLite/PerformanceMonitorLite.csproj. You cannot verify that a version string corresponds to an artifact when the file that gates the release and the file that names the build are different files.The failure mode that reaches an outside person is not a careless poster — it is handing a reporter a version string that does not identify the build containing their fix, arriving through the build system. Recorded on the hold so whoever fills that placeholder verifies against the artifact rather than against a csproj.
- added a commit that references this issue
on Sep 9, 2026 Claude posting for Erik Darling
Fixed by #3225, merged to
devasf8927d0b2c19. Closing by hand — closing keywords resolve againstmain.Six
<Version>elements are now one, atDirectory.Build.props:18, which is a file that did not previously exist. Verified ondevafter the merge: a tree-wide scan for<Version>in.csprojand.propsreturns exactly that one line.check-version-bump.yml:48andnightly.yml:114both read it, so the release gate and the nightly build now resolve the version from the same file — the defect this issue was actually about.Two corrections to this issue as I filed it
The count was six, not four, and my method is why. I grepped for
3.6.0, so by construction it could only find files already carrying that value. Counting by element instead found two more at 3.3.0 —deprecated/Installer.Core/Installer.Core.csprojanddeprecated/Installer/PerformanceMonitorInstaller.csproj, three releases behind. So the divergence had already happened and nothing reported it, which is stronger evidence for this issue than anything in its body. A search shaped like the answer you expect returns a confident partial count and no error.The convention was documented, three times, and I said it was not. The body calls this "a frozen convention with no instrument". Each shipped project carries #2113's comment verbatim: "
<Version>is the ONE hand-set version. AssemblyVersion / FileVersion / InformationalVersion derive from it at build time — the 3.4.0 release bumped only<Version>and shipped binaries still STAMPED 3.3.0.0, because four hand-maintained copies of one fact is a release-day trap."That sentence is this issue, written by the repository, about itself, one level down. #2113 found four version properties diverging inside a project and fixed it by deriving them rather than restating them. Six version elements then diverged across projects, and nobody applied the reasoning upward. What was missing was not documentation — it was that the documentation was per-project while the fact is repository-wide, and the one place a reviewer looks for a repository-wide fact did not hold it.
What this unblocks
Condition 4 of the hold on #3196's reporter reply: the version named to that reporter must be verified against the built artifact. Until now that was not merely unmet but unanswerable by inspection, because the file gating the release and the file naming the nightly were different files. With one declaration read by both, the number can be checked. It still must be checked against the downloaded artifact rather than against the props file — a single declaration removes the ambiguity, not the obligation.
Deliberately still open
build-dashboard.cmd,build-all.cmdandpackage-release.cmdreference project paths that have not existed since #1612, and #3225 repointed only their version read because that was in scope for the declaration invariant. The review bot on #3225 found a third stale reference nobody had reported — acopy "Installer\bin\Release\..."alongside the twodotnet publishlines.Those three scripts are being deleted in a separate PR, not repaired.
build-lite.cmdsurvives. And worth recording, because it is a real cost of #3225 taken alone: #3225 makesbuild-dashboard.cmdfail later and less informatively. It gains a version guard it did not have, so the read now succeeds, the guard passes, and it dies atdotnet publishwith a generic project-not-found — printing a correct version above the same failure, and removing the blankVersion:line that was the only visible sign anything was wrong. Acceptable only because the file is going away.
Four shipped projects each carry their own
<Version>, currently all3.6.0:deprecated/Dashboard/Dashboard.csproj:10check-version-bump.yml— the release gateLite/PerformanceMonitorLite.csproj:21nightly.yml:114— names every nightly artifactDarling/PerformanceMonitor.Darling.Service/…csproj:8Darling/PerformanceMonitor.Darling.Viewer/…csproj:18No
Directory.Build.propsentry centralises it, and no test asserts the four agree. Searched: nothing inDarling.TestsorLite.Testsreads more than one of these files.The failure
The release gate and the nightly build read different files, and two shipped projects are read by neither.
deprecated/Dashboard— the file the gate reads — andcheck-version-bumppasses, the release merges, and every nightly artifact is still named with the old version while both Darling binaries report the old version inProductVersion.Liteand the release gate fails on a correctly-versioned build.The gate's own error message is
Version in deprecated/Dashboard/Dashboard.csproj has not changed from main, which reads as the version and is one of four.Why it has not bitten
Every release so far bumped all four by hand, and nothing recorded that as a requirement. This is a frozen convention with no instrument — the same class as #3204 (a bound enforced against the wrong relationship) and #3208 (a coverage claim nothing scans for). It has held because one person did it consistently, which is exactly the property that stops holding.
The gate reading a deprecated project makes it worse:
deprecated/Dashboardis not shipped to anyone, so the one version under CI's eye is the one with no users, while the three that reach users are unchecked.What a fix needs to decide
Centralise or pin — and they are different answers.
<Version>inDirectory.Build.props, with the four project files inheriting it. Removes the class entirely. But it changes what each project publishes and wants checking against Velopack's channel naming and the installer's expectations before being assumed safe.Whichever, the gate should stop reading a deprecated project. If it must read one file, it should read one that ships.
Do not fold this into a release PR. A release is the change most likely to expose this and the worst place to be fixing it — the version bump and the instrument that checks it should not be the same diff.