Skip to content

Four shipped projects carry independent <Version> literals; the release gate reads a deprecated one and the nightly reads a different one #3222

Description

@erikdarlingdata

Four shipped projects each carry their own <Version>, currently all 3.6.0:

file consumed by
deprecated/Dashboard/Dashboard.csproj:10 check-version-bump.yml — the release gate
Lite/PerformanceMonitorLite.csproj:21 nightly.yml:114 — names every nightly artifact
Darling/PerformanceMonitor.Darling.Service/…csproj:8 nothing
Darling/PerformanceMonitor.Darling.Viewer/…csproj:18 nothing

No Directory.Build.props entry centralises it, and no test asserts the four agree. Searched: nothing in Darling.Tests or Lite.Tests reads 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.

  • Bump only deprecated/Dashboard — the file the gate reads — and check-version-bump passes, the release merges, and every nightly artifact is still named with the old version while both Darling binaries report the old version in ProductVersion.
  • Bump only Lite and the release gate fails on a correctly-versioned build.
  • Bump three of four and nothing anywhere notices.

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/Dashboard is 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.

  • Centralise: a single <Version> in Directory.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.
  • Pin: leave four literals and assert they agree, in the shape the whole-tree guards already use. Cheaper, keeps existing publish behaviour, and leaves four literals that must be edited together — a partial edit reds instead of shipping.

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.

Activity

  1. erikdarlingdata commented on Sep 9, 2026

    @erikdarlingdata
    OwnerAuthor

    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 for 3.6.0 — so it could only find files already carrying that value. Counting by element instead:

    file value
    Darling/PerformanceMonitor.Darling.Service/…csproj:8 3.6.0
    Darling/PerformanceMonitor.Darling.Viewer/…csproj:18 3.6.0
    Lite/PerformanceMonitorLite.csproj:21 3.6.0
    deprecated/Dashboard/Dashboard.csproj:10 3.6.0
    deprecated/Installer.Core/Installer.Core.csproj:10 3.3.0
    deprecated/Installer/PerformanceMonitorInstaller.csproj:23 3.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> in Directory.Build.props that 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.yml follows. 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.yml stamps its judgement from deprecated/Dashboard/Dashboard.csproj while nightly.yml:114 names every artifact from Lite/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.

  2. erikdarlingdata commented on Sep 9, 2026

    @erikdarlingdata
    OwnerAuthor

    Claude posting for Erik Darling

    Fixed by #3225, merged to dev as f8927d0b2c19. Closing by hand — closing keywords resolve against main.

    Six <Version> elements are now one, at Directory.Build.props:18, which is a file that did not previously exist. Verified on dev after the merge: a tree-wide scan for <Version> in .csproj and .props returns exactly that one line. check-version-bump.yml:48 and nightly.yml:114 both 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.csproj and deprecated/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.cmd and package-release.cmd reference 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 — a copy "Installer\bin\Release\..." alongside the two dotnet publish lines.

    Those three scripts are being deleted in a separate PR, not repaired. build-lite.cmd survives. And worth recording, because it is a real cost of #3225 taken alone: #3225 makes build-dashboard.cmd fail later and less informatively. It gains a version guard it did not have, so the read now succeeds, the guard passes, and it dies at dotnet publish with a generic project-not-found — printing a correct version above the same failure, and removing the blank Version: line that was the only visible sign anything was wrong. Acceptable only because the file is going away.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions