Delegate non-local bv runs to the repository's pinned version; add bv update - #332
Conversation
When the repository's tool manifest pins bv, the pinned version is now the one that runs, no matter which bv is invoked: before parsing arguments or reading configuration, bv restores the pinned tool if needed and hands it the entire original command line with inherited standard streams, forwarding its exit code. A version mismatch with the pin always delegates; on a match, only a bv confidently running from the package cache (manifest- or dnx-run) runs in place, so global installs always defer to the repository. --skip-delegation runs the exact binary invoked, and the BV_DELEGATED environment variable set on the delegated child makes delegation loops impossible. The new update command re-pins the repository's entire Buildvana surface to the running bv's version in one operation: the bv entry in the tool manifest (via dotnet tool update/install, which also downloads the version), the Buildvana.Sdk entry in global.json, and the configuration file's schema reference; afterwards the configuration is loaded with the new model and problems surface as warnings. update is exempt from delegation and refuses downgrades without --force. It replaces sync-sdk, which delegation makes redundant; the SDK version check stays as a backstop and now points at bv update. BuildvanaPaths becomes RuntimeInfo and now carries the running bv's version and the delegating bv's version (null when not delegated) alongside the well-known paths, shared by all hook contexts. IProcessRunner gains RunWithInheritedStdioAsync, implemented with raw Process since CliWrap always redirects. A new docs page inventories the environment variables bv reads and sets. Closes Tenacom#325. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@claude please review this PR. |
|
Claude finished @rdeago's task in 6m 3s —— View job Review — PR #332: delegate non-local
|
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## main #332 +/- ##
==========================================
+ Coverage 51.54% 53.91% +2.36%
==========================================
Files 138 151 +13
Lines 4076 4394 +318
Branches 718 787 +69
==========================================
+ Hits 2101 2369 +268
- Misses 1886 1926 +40
- Partials 89 99 +10 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
- Distinguish a missing bv manifest entry from an unusable one: bv update fails up front naming the entry to fix, and delegation warns instead of silently running in place. - Gate the delegation restore on a probe of the SDK's tool resolver cache (the same check dotnet tool run makes), stream restore output to stderr, and degrade a failed restore to a warning followed by the run attempt. - Share one parsed own-version accessor; convert the schema regex to [GeneratedRegex] with cross-reference comments on both copies. - Mark the environment-mutating ProcessRunner test [NotInParallel]. - Document equal-version-vs-equal-bits in InstallLayout, the delegated child's working directory and --version semantics, and DOTNET_CLI_HOME. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Point-by-point follow-up on the review; changes are in f2c4ff4. 1 — Adopted, with one correction to the scenario. 2 — Adopted, in a stronger form than either suggested alternative. "Run first, restore on failure" can't work under inherited stdio: a resolver miss and the delegated bv's own non-zero exit are indistinguishable to the parent, so every failed build would restore-and-retry. Instead, the restore is now gated by a probe of the SDK's tool resolver cache — which is exactly and only what 3 — Keeping the subsystem; the counter-scenario is now in the doc comment, per your fallback ask. Equal versions do not guarantee equal bits: build metadata never participates in version comparison, so a locally built bv carries the same version as the published package it descends from, and a mutable private feed can republish a version outright. Under the repository-authority rule those must yield; only 4 — Documented in 5 — Documented rather than special-cased: in a delegating repository 6 — Declining. The line prints only on a version mismatch, which makes it a diagnostic — "a different bv than the one you invoked will run" — not decoration; it lives on stderr and cannot dirty piped stdout. 7 — Fixed as flagged. 8 — Unusable-pin tests added (see 1). On the cancellation test: Nits — all three taken: a shared 🤖 Generated with Claude Code |
Adds Spectre.Console.Testing as a test dependency: its TestConsole stands in for IAnsiConsole, capturing the summary lines the update command prints as its deliverable. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
@claude please take another look. |
|
Claude finished @rdeago's task in 6m 52s —— View job Re-review — PR #332 after f2c4ff4 / 21a675f / c6f3cf8
Ric — the follow-up is solid, and two of the answers are better than what I proposed. Caveat up front, same as last time: I could not run the build or tests ( Round-1 findings — verified1. Confirmed fixed, and your correction is right. 2. Better than either alternative I offered. You're right that "run first, restore on failure" is unimplementable under inherited stdio — the parent cannot tell a resolver miss from the child's own non-zero exit. The probe ( 3. Accepted. Build metadata not participating in comparison (locally built bv ≡ published package by version) plus republishable private feeds is a real counter-scenario, and it's now in the 4, 5. Documented in 6. Your call stands. Conditional on mismatch + on stderr makes it a diagnostic, and I'd have argued the same knowing it only prints when the version actually switches. 7, 8. Nits. New findingThe
Likelihood is low (manifests are normally written by Smaller notes, none blocking
Nothing here blocks the merge. The case-sensitivity item is the only code change I'd suggest; the rest is documentation or taste. |
Find the manifest's bv entry case-insensitively, the way the dotnet CLI does (it lowercases manifest keys into package IDs), so a differently-cased entry delegates and updates like any other. Stop the BV_DELEGATED marker at the delegated child: bv now removes it from the environment of its other child processes, so a bv reached through a hook or a build makes its own delegation decision. Also: restore the two ifs in TryDelegateAsync (the bool switch read worse), stop overclaiming in the resolver-cache fallback comment, and pin the manifest-first ordering rationale in UpdateRepositoryAsync. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
All five items are in 277dc09. New finding — confirmed and fixed. Your reasoning held up against the sources you couldn't reach this run:
The The fallback comment — corrected to say what the code does: an all-empty home degrades to a relative path resolved against the current directory, and a wrong cache directory only ever costs a restore. The update ordering — pinned with a comment stating the rationale: manifest first because it is the one step with an external actor, and the partial-update window self-heals on rerun exactly as you described. |
|
@claude please take another look. |
|
Claude finished @rdeago's task in 5m 29s —— View job Re-review — PR #332 after 277dc09
Ric — all five items from round 2 are in and correct. Same caveats as before, stated up front so you can weigh what follows: Round-2 items — verifiedCase-insensitive manifest key.
The two One thing I'd verify before merge
With What makes this worth checking rather than dismissing: nothing in the suite can see it. The check is a two-minute real run: in a repo pinned newer, Smaller notes
Everything else I looked at this round holds up: the delegation decision table and its tests, the probe's skip-direction-only trust, |
`dotnet tool update` refuses to move a tool to a lower version unless --allow-downgrade is passed (verified on SDK 10.0.302), so a forced downgrade - the very scenario --force exists for, e.g. bisecting a regression - died with a CLI error at the manifest-pinning step. PinToolManifestAsync now appends --allow-downgrade exactly when the manifest change is a downgrade, a state only reachable forced (EnsureNoUnforcedDowngrade throws otherwise): bv passes the flag when it has itself authorized the downgrade, and the CLI guard stays armed on every other path. The forced-downgrade test now expects the flag, and a new test pins its absence on a forced upgrade. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ProcessRunner.RunAsync hands its environment dictionary straight to CliWrap, so the "null value removes the variable from the child environment" semantics on that path are third-party behavior - and it is the path every build, hook, and dotnet spawn takes, which is exactly where the BV_DELEGATED containment now rides on it. Only the raw-Process inherited-stdio path had a test. A twin of that test against RunAsync locks the contract in, so a behavior change surfaces at CliWrap-upgrade time instead of in the field. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The delegating bv does run the minimal split that finds the subcommand and the global options before deciding to delegate, and that split rejects one malformed shape on its own: a value-bearing global option with nothing after it (say, a trailing -v) is refused by the invoked binary, never reaching the pinned one. Harmless - every bv version phrases that rejection identically - but the docs stated the no-parse rule without qualification; now they carry the caveat. Catch-and-delegate was considered and rejected: delegation consumes the split's outputs (the subcommand for the update exemption, --skip-delegation), so delegating on a parse failure would need a second, cruder pre-parser to reroute an error message that is the same everywhere anyway. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
ReadBvPin reads exactly one file - the home directory's own .config/dotnet-tools.json - while the dotnet CLI walks up from the working directory merging manifests until one is marked isRoot. An ancestor manifest's bv entry therefore pins bv for the CLI but not for bv itself. The behavior is deliberate (the repository's own manifest is the pin bv manages), but the remarks' CLI-parity argument for case-insensitive matching made it easy to over-read; a paragraph now states the narrower scope explicitly. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
All four round-3 items are addressed, one commit each.
Delegating bv judges one sliver of the command line (dad6fd0). Right — the split runs first, and Null-removal contract on the hot path (192f6b2). Agreed and done: a twin of
Gates on the pushed tree: the full @claude Could you take another look, re-reviewing the range 277dc09..0a3e772? |
|
Claude finished @rdeago's task in 4m 50s —— View job Re-review — PR #332, range
|
dad6fd0 qualified the "neither parses the command line" claim in docs/DirectoryStructure.md, but the changelog's delegation paragraph still stated it without qualification - and release notes are the copy a user meets first. The sentence now matches the doc: the delegating bv runs the minimal split that finds the subcommand and the global options, and only a value-bearing global option with no following value is rejected before delegation, identically in every version. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
0a3e772 said delegation and bv update "scope themselves" to the home directory's manifest. For delegation both halves hold: the decision reads this manifest, and the spawned `dotnet tool run`, running from the home directory, resolves the same file. For bv update only the decision half holds - the spawned dotnet CLI follows its own manifest-location rules, and the no-entry `dotnet tool install --create-manifest-if-needed` can walk up to an ancestor manifest in a nested layout. The paragraph now says exactly that. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
Both follow-ons addressed:
Gates on the pushed tree: |
- Distinguish a missing bv manifest entry from an unusable one: bv update fails up front naming the entry to fix, and delegation warns instead of silently running in place. - Gate the delegation restore on a probe of the SDK's tool resolver cache (the same check dotnet tool run makes), stream restore output to stderr, and degrade a failed restore to a warning followed by the run attempt. - Share one parsed own-version accessor; convert the schema regex to [GeneratedRegex] with cross-reference comments on both copies. - Mark the environment-mutating ProcessRunner test [NotInParallel]. - Document equal-version-vs-equal-bits in InstallLayout, the delegated child's working directory and --version semantics, and DOTNET_CLI_HOME. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Find the manifest's bv entry case-insensitively, the way the dotnet CLI does (it lowercases manifest keys into package IDs), so a differently-cased entry delegates and updates like any other. Stop the BV_DELEGATED marker at the delegated child: bv now removes it from the environment of its other child processes, so a bv reached through a hook or a build makes its own delegation decision. Also: restore the two ifs in TryDelegateAsync (the bool switch read worse), stop overclaiming in the resolver-cache fallback comment, and pin the manifest-first ordering rationale in UpdateRepositoryAsync. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Proposed changes
Why. The SDK version check was a guard, not a remedy: a global or dnx-launched
bvin a pinned repository got an error and instructions, when what the user wants is for the right version to just run. And the harm was not limited tousesSdkcommands: abvof any other version deserializesbuildvana.jsoncwith a differentBuildvana.Runtimemodel — silent drift or spurious validation errors on commands the check never gated.What. Like the Angular CLI's global
ng,bvnow always defers to the repository:bv, a non-localbvdelegates the entire invocation to the pinned version —dotnet tool restorewhen needed, thendotnet tool run bvwith the original arguments verbatim, inherited standard streams, and forwarded exit code, printing an info line on standard error when the versions differ. The delegating side neither parses arguments nor reads configuration; judging them is the pinned version's job.--skip-delegationruns the exact binary invoked, and theBV_DELEGATEDenvironment variable set on the child makes delegation loops impossible.bv update. One operation re-pins the repository's whole Buildvana surface to the runningbv's version: the tool manifest entry (viadotnet tool update/install --create-manifest-if-needed, which also downloads the version), theBuildvana.Sdkpin inglobal.json, and the configuration file's$schemareference; the configuration is then loaded with the new model and problems surface as warnings.updateis delegation-exempt ("bring this repository to me") and refuses downgrades without--force.sync-sdkis removed, fully subsumed by the two features above; the SDK version check stays as a backstop for repository-internal pin disagreements and now points atbv update.RuntimeInfo(formerlyBuildvanaPaths) now carries the runningbv's version and the delegatingbv's version (nullwhen not delegated) alongside the well-known paths, shared by all hook contexts.How. The delegation decision is primarily a version comparison: a manifest-run
bvalways matches the pin by construction, so a mismatchedbvcannot be the manifest's, and the delegated child never re-delegates. Install-layout detection (AppContext.BaseDirectorymatched against the tool-store and package-cache layouts, degrading to unknown ⇒ non-local) decides only the equal-version case, so the manifest's install always runs no matter howbvwas launched. Delegation needed true stdio inheritance, which CliWrap cannot do, soIProcessRunnergainsRunWithInheritedStdioAsyncimplemented with rawSystem.Diagnostics.Process(parent survives Ctrl-C; the child owns shutdown). A newdocs/EnvironmentVariables.mdinventories the variablesbvreads and sets.Verified end to end on this repository: a dev-built
bvdelegates--versionto the pinned 2.1.70-preview, and--skip-delegationruns it in place.Checklist of related issues / discussions
Types of changes
This pull request introduces the following types of changes:
docsdirectory) update.gitattributes,.gitignore)Breaking changes
This pull request introduces breaking changes:
Relative to the 2.1 preview line only (no stable release is affected): plain
bvin a pinned repository no longer runs the invoked binary;sync-sdkis gone;BuildvanaPathsis reshaped intoRuntimeInfoand the hook context'spathsmember becomesruntimeInfo.Checklist
docsdirectory) only:🤖 Generated with Claude Code