Skip to content

Stop reading the tool manifest from .config/dotnet-tools.json - #394

Merged
rdeago merged 8 commits into
Tenacom:mainfrom
rdeago:fix/root-tool-manifest
Sep 11, 2026
Merged

rdeago merged 8 commits into
Tenacom:mainfrom
rdeago:fix/root-tool-manifest

Conversation

@rdeago

@rdeago rdeago commented Sep 9, 2026

Copy link
Copy Markdown
Member

Checklist of related issues / discussions

  • Closes #
  • Part of #
  • Related discussion(s): #

Proposed changes

The .NET 10 SDK creates the tool manifest as dotnet-tools.json in the repository root.
dotnet tool install and dotnet new tool-manifest both write it there, and the minimum .NET SDK of Buildvana is 10.0.400.
bv read .config/dotnet-tools.json alone.
With a root manifest, dotnet bv never delegated, bv self-update reported the pin as added on every run, and bv dependencies listed no tool.
The post-release commit of bv release left the manifest alone.

bv reads dotnet-tools.json in the home directory, and fails on .config/dotnet-tools.json with a message that names the git mv, or the merge when both files exist.
Ignoring the file would not do: the dotnet CLI writes the bv pin into whichever file exists, so a file bv ignored would keep receiving it.
Reading both locations, as the CLI does, was considered and rejected: it is a dual-track design for a layout that only history produces, and its precedence rule would need documenting.

bv self-update creates the manifest with dotnet new tool-manifest when the home directory has none, and passes --tool-manifest dotnet-tools.json to every dotnet tool verb it runs.
Left to dotnet tool install, the CLI picks the directory by its own markers, which can be an ancestor of the home directory.
When the template writes no dotnet-tools.json in the home directory, bv self-update fails before the install.

bv dependencies reads every dotnet-tools.json under the home directory, as the packages scope reads every project file, and writes each pin back to its own file.
A .config/dotnet-tools.json in a subdirectory fails the read too, and the message names every such file.
A bv entry in a manifest below the home directory is a tool pin like any other, because delegation and bv self-update read the home directory's manifest alone.

docs/directory-structure.md describes the location and gains a migration section, docs/getting-started.md drops -o .config, and terminology.md gains a "tool manifest" row.

This repository keeps .config/dotnet-tools.json for now.
The release that ships this change runs the pinned bv, which rewrites that file.
After the release, the new bv refuses to run here until the file moves, which is a single commit on main: the manifest, Buildvana.slnx, .github/labeler.yml, the two workflows, the comments of buildvana.jsonc and buildvana.next.jsonc, the constant of RepositoryConfigFilesTests, and .claude/rules/dependency-management.md.

Additional changes

None.

Types of changes

This pull request introduces the following types of changes:

  • Bug fix
  • New feature
  • Test addition / update (no changes to non-test code)
  • Refactor (no changes in public API syntax or semantics)
  • Performance improvement (no changes in public API syntax or semantics)
  • Documentation update (not including changelog)
  • Dependency addition / update
  • Changes to toolchain configuration
  • Changes to CI (workflows, bot / app configurations)
  • Changes to repository files (.gitattributes, .gitignore)
  • Other

Breaking changes

This pull request introduces breaking changes:

  • Yes
  • No

Checklist

  • For all types of changes:
  • For code changes only:
    • The project builds on my machine, via the provided build script, with zero warnings
    • I have added tests that prove my feature works / my fix is effective
    • I have added / modified XML documentation according to changes in code
    • I have checked that all the links I added or modified in XML documentation point to their intended destination
  • For documentation changes only:
    • I have checked and proofread documentation locally
    • I have checked that all the links I added or modified point to their intended destination

🤖 Generated with Claude Code

The .NET 10 SDK creates the tool manifest as `dotnet-tools.json` in the repository root, and `bv` read `.config/dotnet-tools.json` alone. With a root manifest, `dotnet bv` never delegated, `bv self-update` reported the `bv` pin as added on every run, and `bv dependencies` listed no tool. `bv` fails on `.config/dotnet-tools.json` instead of ignoring it, because the dotnet CLI writes bv's pin into whichever file exists. `bv self-update` creates a missing manifest with `dotnet new tool-manifest` and names it with `--tool-manifest`, because the dotnet CLI picks a new manifest's directory by its own markers. Those markers can select an ancestor of the home directory. `bv dependencies` reads every `dotnet-tools.json` under the home directory, as it reads every project file.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@rdeago

rdeago commented Sep 9, 2026

Copy link
Copy Markdown
Member Author

@claude Please review this pull request.

@github-actions github-actions Bot added area:docs [issue/PR] affects documentation (excluding XML documentation that is part of source code). area:code [issue/PR] affects project code (excluding tests). labels Sep 9, 2026
@claude

claude Bot commented Sep 9, 2026

Copy link
Copy Markdown

Claude finished @rdeago's task in 7m 36s —— View job


Review

  • Read the diff against origin/main
  • Read the changed source, the finder it now depends on, and the callers of ToolManifest
  • Search the repository for every site of each finding
  • Post the review

Two findings block. One premise I could not check from here, because this environment has no dotnet.


Blocking

1. A bv pin in a manifest outside the home directory is moved by nothing

ToolPinReader now reads every dotnet-tools.json under the home directory, and skips family ids in all of them (src/Buildvana.Tool/Services/Dependencies/ToolPinReader.cs:72).
The stated reason is in the class remarks at ToolPinReader.cs:23-24: "the bv entry is a family pin and is not among the results: bv self-update is the one command that moves it."

bv self-update moves the entry of one file.
SelfVersionService.cs:331 passes --tool-manifest ToolManifest.RelativePath, which is the home directory's manifest.
FamilyPinUpdater covers package items and #:package directives, not manifests.
SelfReferenceUpdater.cs:53 registers the same single path for the post-release commit.

Failure case: a repository with tools/dotnet-tools.json pinning bv at 2.1.40-preview.
bv deps show lists no pin for it, on the ground that bv self-update owns it.
bv self-update --to 2.1.41-preview leaves it at 2.1.40-preview.
bv release leaves it there too.
dotnet bv run from tools/ resolves that manifest through the dotnet CLI and runs a version no bv command maintains.

Either exclude family ids only in the home directory's manifest, and let bv deps manage the others, or state in the remarks that a family pin outside the home manifest is unmanaged.

Fix this →

2. The .config rejection reaches less far than docs/directory-structure.md says

docs/directory-structure.md:156 states:

When the home directory, or a directory under it, holds one, bv stops with an error that names the file and the move.

ToolManifest.EnsureNoLegacyManifest (src/Buildvana.Tool/Services/ToolManifest.cs:103) tests one path, .config/dotnet-tools.json in the home directory.
It is the check that DelegationService, SelfReferenceUpdater, and ReadBvPin call.
The walk over every depth lives in ToolPinReader.cs:45 alone, and DependencyDiscovery.cs:62 runs it only when the tools scope is selected.

Failure case: a repository with dotnet-tools.json in the home directory and docs/.config/dotnet-tools.json under it.
bv build succeeds.
bv deps show --packages succeeds.
bv deps show fails.
The documented behavior is a stop in all three.

The cheapest fix is the sentence: name the home directory for bv, and the whole tree for bv dependencies.
The changelog bullet at CHANGELOG.md:87 says "when a repository holds one" and has the same reach problem.

Fix this →


One question

PinToolManifestAsync (src/Buildvana.Tool/Services/SelfVersionService.cs:323-331) runs dotnet new tool-manifest and then passes --tool-manifest dotnet-tools.json.
That works only if the template writes the file in the working directory rather than under .config.
The PR description states it does on .NET SDK 10, docs/getting-started.md now teaches it, and I could not run dotnet here to confirm.

If the template ever writes .config/dotnet-tools.json, bv self-update on a repository with no manifest creates the one file bv refuses, and the dotnet tool install that follows fails on a missing path.
A File.Exists assertion after the new tool-manifest run, failing with bv's own message, turns that into a diagnostic instead of a CLI error plus a stray file.


Non-blocking

One round, prose and messages only.

  • LegacyManifestError proposes git mv .config/dotnet-tools.json dotnet-tools.json.
    When both files exist, which is exactly the case where the CLI and bv read different pins, git mv refuses with fatal: destination exists.
    The reader needs "merge the two", not "move one".
  • ToolManifest.RelativePath and ToolManifest.FileName now hold the same string (src/Buildvana.Tool/Services/ToolManifest.cs:30, :35).
    Two names for one value invite the wrong one at the next call site.
  • ToolPinReader.Read() throws on an unparseable manifest before it reports the .config manifests it has collected, so a repository with both problems hears about the parse error first.

The stray CRLF fix on the using line of ToolPinReaderTests.cs is welcome.
· branch fix/root-tool-manifest

@codecov

codecov Bot commented Sep 9, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 82.83%. Comparing base (7dfec94) to head (4c34d8c).
⚠️ Report is 8 commits behind head on main.

Additional details and impacted files
@@            Coverage Diff             @@
##             main     #394      +/-   ##
==========================================
+ Coverage   82.73%   82.83%   +0.10%     
==========================================
  Files         278      278              
  Lines        8263     8314      +51     
  Branches     1606     1618      +12     
==========================================
+ Hits         6836     6887      +51     
  Misses       1151     1151              
  Partials      276      276              

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

rdeago and others added 6 commits September 11, 2026 04:30
`ToolPinReader` skipped the `bv` entry of every tool manifest, on the ground that `bv self-update` moves it. `bv self-update` moves the entry of the home directory's manifest alone. A `bv` entry in a subdirectory manifest was therefore listed by nothing and moved by nothing. Delegation reads the home directory's manifest alone as well, so a `bv` entry elsewhere takes no part in the lockstep of the family. `bv dependencies` manages such an entry as a tool pin.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`ToolPinReader.Read` parsed each tool manifest as the repository walk found it, and reported the manifests under `.config` after the walk. A repository with a manifest under `.config` and an unparseable one heard about the parse error alone. The walk collects both lists first, and the parse starts after the `.config` check.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`ToolManifest.LegacyManifestError` proposed `git mv .config/dotnet-tools.json dotnet-tools.json` whatever the state of the destination. When both files exist, `git mv` refuses with "destination exists". That case is the one the failure exists for. The dotnet CLI reads the two files as one manifest, and writes the `bv` pin into the one under `.config`. When the destination exists, the message asks for a merge into it, and names the move otherwise.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`ToolManifest.RelativePath` and `ToolManifest.FileName` held one string, because the home directory's manifest is a file of that name in the home directory. Two names for one value invite the wrong one at the next call site. `FileName` reads right at every site, so `RelativePath` goes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`SelfVersionService.PinToolManifestAsync` ran `dotnet new tool-manifest` and passed the home directory's `dotnet-tools.json` to `dotnet tool install` without checking that the template had written it. The template writes the file in the working directory from the .NET SDK 10 on. Were a later SDK to write it under `.config` again, the install would fail on the missing path. The next `bv` run would then refuse the stray file. The check after the template run reports the missing file in one message, before the install.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
`docs/directory-structure.md` said `bv` stops when the home directory or a directory under it holds `.config/dotnet-tools.json`, and the changelog bullet said "when a repository holds one". `ToolManifest.EnsureNoLegacyManifest` tests the home directory alone. `ToolPinReader` is the one reader that walks the tree, and only the `tools` scope of `bv dependencies` runs it. The page and the bullet state the two reaches.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@rdeago

rdeago commented Sep 11, 2026

Copy link
Copy Markdown
Member Author

Round 1 addressed, in six commits.

1. A bv pin outside the home directory's manifest. Took the first fix, in c0eb676: ToolPinReader skips family ids in the home directory's manifest alone. Delegation runs dotnet tool run bv from the home directory, and bv self-update moves the entry of that manifest, so a bv entry elsewhere takes no part in the lockstep, and bv dependencies manages it as a tool pin. The remarks of ToolPinReader, docs/tool-commands/dependencies.md, and a test say so.

2. Reach of the .config rejection. Prose, as proposed, in 1fbf400. The page states the home directory for bv and the subdirectories for bv dependencies. The changelog bullet says "when the home directory holds one".

Question. dotnet new tool-manifest writes dotnet-tools.json in the working directory on SDK 10.0.401, verified locally: {"version":1,"isRoot":true,"tools":{}}. The check is in anyway, in 2897192: when the file is missing after the template run, bv self-update fails before the install, with a message that names the template.

Non-blocking.

  • git mv with an existing destination: LegacyManifestError names the merge when the destination exists, and the move otherwise, in 9534673. The migration section of docs/directory-structure.md says the same.
  • RelativePath and FileName: RelativePath is gone, in de9c7b9.
  • Parse error before the .config report: Read collects the paths first, reports the .config manifests, then parses, in 4ca5d03.

@claude Please review the new commits. Items 1, 3, and 4 change behavior.

@claude

claude Bot commented Sep 11, 2026

Copy link
Copy Markdown

Claude finished @rdeago's task in 3m 52s —— View job


Review, round 2

  • Read the six new commits
  • Verify each fix against the tree
  • Search the repository for every site of the one finding
  • Post the review

No blocking findings


Non-blocking

One shape, three sites. c0eb676 made a bv entry below the home directory a managed tool pin, and updated the remarks of ToolPinReader and docs/tool-commands/dependencies.md:163-166. Three statements of the same shape were left behind.

  • src/Buildvana.Tool/Services/Dependencies/DependencyResolver.cs:77-78: "A family id never has one: the family moves in lockstep, and one command moves it."
    With a bv pin in tools/dotnet-tools.json, EnsureStatedVersionHasAPin returns at line 94 and the run moves that pin.
  • src/Buildvana.Tool/Services/BuildvanaFamily.cs:11: "bv deps (once it exists) will treat family pins as invisible".
    The parenthesis is stale on its own, and the clause after it is now true of the home directory's manifest alone.
  • docs/tool-commands/dependencies.md:316: "A Buildvana family package is the second case, because bv self-update moves its pins as one."
    Dropping "always" softened the sentence, but bv deps update bv --to 2.1.50 on a repository with tools/dotnet-tools.json moves that pin instead of raising BV1203.

The code is right at all three sites. The comments and the page are what say otherwise.

Fix this →
· branch fix/root-tool-manifest

The remarks of `BuildvanaFamily`, `DependencyPin`, and `DirectivePinReader` said that a family id never has a pin `bv dependencies` manages. A comment in `DependencyResolver`, one in `DependencyResolverTests`, the BV1203 entry of `docs/tool-diagnostics.md`, and the "Moving a pin past its policy" section of `docs/tool-commands/dependencies.md` said the same. `ToolPinReader` reads the `bv` entry of a tool manifest below the home directory as a tool pin. `bv self-update` moves the entry of the home directory's manifest alone, so nothing else would move such an entry. Each statement names that exception. The remark of `BuildvanaFamily` also said that `bv deps` did not exist yet.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@rdeago

rdeago commented Sep 11, 2026

Copy link
Copy Markdown
Member Author

Round 2 addressed in 4c34d8c.

The three sites named are fixed, and four more of the same shape, found by searching for "lockstep", "invisible", "one command", and "family":

  • the BV1203 entry of docs/tool-diagnostics.md
  • the remarks of DependencyPin
  • a comment in DirectivePinReader
  • a comment in DependencyResolverTests

Each statement names the exception: a bv entry in a tool manifest below the home directory is a tool pin that bv dependencies manages. The round changed comments and documentation alone.

@rdeago
rdeago merged commit ab36e9a into Tenacom:main Sep 11, 2026
6 checks passed
@rdeago
rdeago deleted the fix/root-tool-manifest branch September 11, 2026 03:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area:code [issue/PR] affects project code (excluding tests). area:docs [issue/PR] affects documentation (excluding XML documentation that is part of source code).

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant