Raised out of #1166. The fleet pins one version line per host tool and has no mechanism that notices when upstream ships a later one, so the decision to move is made whenever somebody happens to look.
The state today
winget publishes Python as one package id per minor line, Python.Python.3.0 through Python.Python.3.14, and host-setup/windows/install-tools.ps1 pins Python.Python.3.13. A pinned per-line id is a fixed release that can never advance, so -Upgrade python only ever moves 3.13.x and never reaches 3.14, which was 3.14.7 in the catalog when this was written. Microsoft.DotNet.SDK.10 has the same shape and the same question when .NET 11 ships. node is the one tool that does not, because OpenJS.NodeJS.LTS is a floating id that follows the LTS line on its own.
The floor in spec/host-tools.json is a genuine minimum rather than a target: a Linux distribution ships the line it ships, so a host at the floor and a host above it both pass. That is deliberate and is not what this issue asks to change. What is missing is the signal that a later line exists and is worth adopting.
The mechanism already exists here
.github/workflows/check-upstream-version-task.yml is a hub-hosted reusable task built for exactly this shape: a resolve-upstream hook action resolves current upstream versions, the task diffs them against a committed state file, and it opens a rolling App-signed pull request naming only what moved. auto-merge: false routes that pull request to a human instead of the merge bot. ESPHome-NonRoot runs two instances of it in production, one of them with auto-merge: false.
The hub has never pointed it at itself. It carries no scheduled workflow calling the task and no .github/actions/resolve-upstream/, so those two pieces are the whole gap.
Why the pin stays pinned
The maintainer's call, recorded here so it is not relitigated: the tracker opens a pull request and a human moves the pin. Moving the Python line has to move with pyproject.toml's three settings (target-version = "py313", python_version = "3.13", pythonVersion = "3.13") and the three CI python-version: "3.13" settings in .github/actions/validate/action.yml and .github/workflows/validate-task.yml. An installer that followed the newest line on its own would put hosts ahead of what ruff, mypy and pyright are configured for, and Linux hosts could not follow it anyway since the distribution decides there.
What this issue needs
A design pass before any code: what the state file watches (a resolved latest line per pinned tool, or the full catalog), where it lives, how a bump pull request names the settings that must move with the pin, and whether dotnet and python share one tracker or take one each.
Raised out of #1166. The fleet pins one version line per host tool and has no mechanism that notices when upstream ships a later one, so the decision to move is made whenever somebody happens to look.
The state today
winget publishes Python as one package id per minor line,
Python.Python.3.0throughPython.Python.3.14, andhost-setup/windows/install-tools.ps1pinsPython.Python.3.13. A pinned per-line id is a fixed release that can never advance, so-Upgrade pythononly ever moves 3.13.x and never reaches 3.14, which was 3.14.7 in the catalog when this was written.Microsoft.DotNet.SDK.10has the same shape and the same question when .NET 11 ships.nodeis the one tool that does not, becauseOpenJS.NodeJS.LTSis a floating id that follows the LTS line on its own.The floor in
spec/host-tools.jsonis a genuine minimum rather than a target: a Linux distribution ships the line it ships, so a host at the floor and a host above it both pass. That is deliberate and is not what this issue asks to change. What is missing is the signal that a later line exists and is worth adopting.The mechanism already exists here
.github/workflows/check-upstream-version-task.ymlis a hub-hosted reusable task built for exactly this shape: aresolve-upstreamhook action resolves current upstream versions, the task diffs them against a committed state file, and it opens a rolling App-signed pull request naming only what moved.auto-merge: falseroutes that pull request to a human instead of the merge bot. ESPHome-NonRoot runs two instances of it in production, one of them withauto-merge: false.The hub has never pointed it at itself. It carries no scheduled workflow calling the task and no
.github/actions/resolve-upstream/, so those two pieces are the whole gap.Why the pin stays pinned
The maintainer's call, recorded here so it is not relitigated: the tracker opens a pull request and a human moves the pin. Moving the Python line has to move with
pyproject.toml's three settings (target-version = "py313",python_version = "3.13",pythonVersion = "3.13") and the three CIpython-version: "3.13"settings in.github/actions/validate/action.ymland.github/workflows/validate-task.yml. An installer that followed the newest line on its own would put hosts ahead of what ruff, mypy and pyright are configured for, and Linux hosts could not follow it anyway since the distribution decides there.What this issue needs
A design pass before any code: what the state file watches (a resolved latest line per pinned tool, or the full catalog), where it lives, how a bump pull request names the settings that must move with the pin, and whether
dotnetandpythonshare one tracker or take one each.