Skip to content

ci: block releases when version strings disagree - #360

Merged
Jeomon merged 1 commit into
mainfrom
ci/version-consistency-guard
Aug 1, 2026
Merged

ci: block releases when version strings disagree#360
Jeomon merged 1 commit into
mainfrom
ci/version-consistency-guard

Conversation

@Jeomon

@Jeomon Jeomon commented Aug 1, 2026

Copy link
Copy Markdown
Member

Ports the version-consistency guard from MacOS-MCP (CursorTouch/MacOS-MCP#33), adapted to this repo's layout. A release can no longer publish unless every declared version agrees with the tag.

Why

The version is declared in four files, each consumed by a different channel:

file channel
pyproject.toml PyPI
uv.lock locked workspace member
manifest.json Claude Desktop extension
server.jsonpackages[] MCP registry's PyPI entry

Only pyproject.toml reliably gets bumped. At the v0.8.5 release, manifest.json and server.json's package entry were both still on 0.8.1 — two releases behind — because the 0.8.2 bump touched only pyproject.toml. Nothing failed; the extension just kept advertising a version that was no longer what shipped.

That's the same failure MacOS-MCP hit, where it stranded users on a build predating a permissions fix with no update path.

What this does

scripts/check_versions.py compares all four against each other and against the release tag. publish.yml runs it before uv build, so a mismatch fails the release before anything reaches PyPI.

$ python scripts/check_versions.py v0.8.5
  ok   pyproject.toml:project.version   0.8.5
  ok   uv.lock:windows-mcp              0.8.5
  ok   manifest.json:version            0.8.5
  ok   server.json:packages[0].version  0.8.5

All 4 version strings agree and match 0.8.5.

Against the reconstructed pre-0.8.5 tree:

  version strings disagree: 0.8.1, 0.8.2
  expected every version to be 0.8.2      -> exit 1, release blocked

One deliberate exclusion

server.json's top-level "version" is not checked. That field identifies the registry entry rather than the PyPI package, and it's on its own 1.x line (currently 1.0.1). Requiring it to match would either fail permanently or force a version downgrade in the registry. Only packages[].version, which must match what's published to PyPI, is checked.

test_top_level_server_version_is_ignored pins that exclusion so it isn't later "fixed" into a check that can never pass.

Tests

tests/test_check_versions.py — 14 tests covering extraction, drift detection, tag matching, the top-level exclusion, and exit codes. The one that matters is test_catches_the_historical_drift, which reconstructs the real pre-0.8.5 tree and asserts the guard would have blocked that release.

Testing caveat: I could not run the suite locally — pywin32 has no macOS wheels, so uv sync fails on this machine. I executed the 14 test bodies directly against temporary copies of the tree instead (all pass), and exercised the script end to end. CI on windows-latest runs the real suite.

The version is declared in four files, each consumed by a different
channel: pyproject.toml (PyPI), uv.lock, manifest.json (Claude Desktop
extension) and server.json's packages[] entry (MCP registry). Only
pyproject.toml reliably gets bumped.

At v0.8.5 manifest.json and server.json's package entry were both still
on 0.8.1 -- two releases behind -- because the 0.8.2 bump touched only
pyproject.toml. Nothing failed; the extension simply kept advertising a
version that was no longer what shipped.

scripts/check_versions.py compares all four against each other and
against the release tag, and publish.yml now runs it before `uv build`,
so a mismatch fails the release before anything reaches PyPI. Its
regression test reconstructs the real pre-0.8.5 tree and asserts the
guard would have blocked that release.

server.json's TOP-LEVEL "version" is deliberately excluded. That field
identifies the registry entry rather than the PyPI package and is on its
own 1.x line, so requiring it to match would either fail permanently or
force a version downgrade in the registry. A test pins the exclusion so
it isn't later "fixed" into a check that can never pass.

Not run locally: the suite needs pywin32, which has no macOS wheels, so
the test bodies were executed directly instead. CI runs on windows-latest.
@qodo-code-review

Copy link
Copy Markdown

PR Summary by Qodo

CI: block releases when declared versions drift from the tag

⚙️ Configuration changes ✨ Enhancement 🧪 Tests 🕐 20-40 Minutes

Grey Divider

AI Description

• Add a version-consistency guard across all shipped version declarations.
• Run the guard in the publish workflow before building/releasing artifacts.
• Add regression tests to prevent reintroducing historical version drift.
Diagram

graph TD
  A["GitHub Actions: publish.yml"] --> B["Run check_versions.py (tag)"] --> C["Read version files"] --> D{ "All versions agree\nand match tag?" } --> E["uv build"]
  D -->|"No"| F["Fail release job"]
  C --> G["pyproject.toml"]
  C --> H["uv.lock"]
  C --> I["manifest.json"]
  C --> J["server.json packages[].version"]
Loading
High-Level Assessment

The following are alternative approaches to this PR:

1. Single source of truth + generated files
  • ➕ Eliminates drift by construction (other files derived from pyproject version).
  • ➕ Reduces release-time failures by moving consistency earlier (during version bump).
  • ➖ Requires writing and maintaining an update/generation tool.
  • ➖ May not fit external consumers that expect manual edits or different version semantics.
2. GitHub Action / composite action for version checking
  • ➕ Reuses the same guard across repos without copying code.
  • ➕ Centralizes improvements and bug fixes to the checker.
  • ➖ Adds dependency on an external action repo/versioning.
  • ➖ Harder to run/iterate locally compared to an in-repo script.

Recommendation: Current approach (in-repo Python script invoked by publish.yml) is the best incremental fix: it adds a hard release gate with minimal moving parts and clear error output, while preserving the deliberate exclusion of server.json top-level version. Consider consolidating version declarations later, but the guard provides immediate protection without reshaping the release process.

Files changed (3) +322 / -0

Enhancement (1) +142 / -0
check_versions.pyAdd version-consistency guard across pyproject/lock/manifest/server +142/-0

Add version-consistency guard across pyproject/lock/manifest/server

• Introduces a script that extracts versions from pyproject.toml, uv.lock (windows-mcp package entry), manifest.json, and server.json packages[].version, then checks internal consistency and optional tag matching. Explicitly ignores server.json top-level version and returns non-zero exit on mismatch for CI gating.

scripts/check_versions.py

Tests (1) +168 / -0
test_check_versions.pyAdd regression tests for version extraction and release blocking +168/-0

Add regression tests for version extraction and release blocking

• Adds pytest coverage for version extraction, error cases, tag handling, and exit codes. Includes a regression that reconstructs the historical pre-v0.8.5 drift to ensure the release would have been blocked, and pins the intentional exclusion of server.json top-level version.

tests/test_check_versions.py

Other (1) +12 / -0
publish.ymlGate publish workflow on version-consistency check +12/-0

Gate publish workflow on version-consistency check

• Adds a Python setup step and runs scripts/check_versions.py using the release tag (github.ref_name) before uv build. This blocks publishing when any declared version diverges from the tag.

.github/workflows/publish.yml

@qodo-code-review

Copy link
Copy Markdown

Code Review by Qodo

🐞 Bugs (2) 📘 Rule violations (3) 📜 Skill insights (0)

Context used
✅ Compliance rules (platform): 16 rules

Grey Divider


Remediation recommended

1. Single quotes in strings 📘 Rule violation ✧ Quality
Description
New Python code and tests introduce single-quoted string literals, which violates the requirement to
use double quotes for all string literals. This can cause inconsistent style and lint/format
failures if enforced in CI.
Code

scripts/check_versions.py[43]

+    r'^\[\[package\]\]\nname = "windows-mcp"\nversion = "([^"]+)"',
Relevance

●●● Strong

Team enforces double-quote literals; prior reviews accepted changing single quotes to double quotes.

PR-#353
PR-#307

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 222799 requires all string literals to use double quotes. The added regex literal in
scripts/check_versions.py uses single quotes, and the new tests also include single-quoted string
literals (e.g., the uv.lock fixture text and replacement strings).

Rule 222799: Enforce double quotes for all string literals
scripts/check_versions.py[42-45]
tests/test_check_versions.py[81-83]
tests/test_check_versions.py[115-126]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
The PR adds single-quoted string literals, but the style rule requires double quotes for all string literals.

## Issue Context
This appears in both the new version-check script and its tests.

## Fix Focus Areas
- scripts/check_versions.py[42-45]
- tests/test_check_versions.py[81-83]
- tests/test_check_versions.py[115-126]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


2. Missing Google-style docstrings 📘 Rule violation ✧ Quality
Description
Public functions in the new scripts/check_versions.py module do not use Google-style docstrings
(missing Args:/Returns: sections), and main() has no docstring. This violates the docstring
standard and reduces maintainability for a release-critical script.
Code

scripts/check_versions.py[R48-104]

+def collect_versions(root: Path = REPO_ROOT) -> dict[str, str]:
+    """Extract every declared version string, keyed by a human-readable label.
+
+    Raises:
+        ValueError: if a file is missing the version field entirely, which is
+            just as much a packaging bug as a stale value.
+    """
+    versions: dict[str, str] = {}
+
+    pyproject = tomllib.loads((root / "pyproject.toml").read_text(encoding="utf-8"))
+    try:
+        versions["pyproject.toml:project.version"] = pyproject["project"]["version"]
+    except KeyError as exc:
+        raise ValueError("pyproject.toml is missing [project] version") from exc
+
+    lock_text = (root / "uv.lock").read_text(encoding="utf-8")
+    match = _UV_LOCK_WINDOWS_MCP.search(lock_text)
+    if match is None:
+        raise ValueError("uv.lock has no [[package]] entry for windows-mcp")
+    versions["uv.lock:windows-mcp"] = match.group(1)
+
+    manifest = json.loads((root / "manifest.json").read_text(encoding="utf-8"))
+    if "version" not in manifest:
+        raise ValueError("manifest.json is missing a top-level version field")
+    versions["manifest.json:version"] = manifest["version"]
+
+    server = json.loads((root / "server.json").read_text(encoding="utf-8"))
+    packages = server.get("packages", [])
+    if not packages:
+        raise ValueError("server.json declares no packages")
+    for index, package in enumerate(packages):
+        if "version" not in package:
+            raise ValueError(f"server.json packages[{index}] is missing a version field")
+        versions[f"server.json:packages[{index}].version"] = package["version"]
+
+    return versions
+
+
+def check(expected: str | None = None, root: Path = REPO_ROOT) -> list[str]:
+    """Return a list of human-readable problems; empty means everything agrees."""
+    versions = collect_versions(root)
+    distinct = sorted(set(versions.values()))
+
+    problems: list[str] = []
+    if len(distinct) > 1:
+        problems.append(f"version strings disagree: {', '.join(distinct)}")
+
+    if expected is not None:
+        target = expected.removeprefix("v")
+        if any(version != target for version in versions.values()):
+            problems.append(f"expected every version to be {target}")
+
+    return problems
+
+
+def main(argv: list[str]) -> int:
+    expected = argv[1] if len(argv) > 1 else None
Relevance

●●● Strong

Google-style docstrings (Args/Returns/Yields) have been requested and accepted for public functions.

PR-#358
PR-#353

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 222802 requires Google-style docstrings on public functions. collect_versions() includes only
a brief description and Raises: but no Args:/Returns: sections, check() has a one-liner
docstring without the required sections, and main() lacks any docstring.

Rule 222802: Require Google-style docstrings on public functions and classes
scripts/check_versions.py[48-55]
scripts/check_versions.py[86-88]
scripts/check_versions.py[103-104]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
Public functions need Google-style docstrings with the required sections (`Args:`, `Returns:`, and `Raises:` where applicable). Some functions have minimal docstrings and `main()` has none.

## Issue Context
The new script is invoked by the release workflow; clear docstrings help prevent future regressions and clarify expected inputs/outputs.

## Fix Focus Areas
- scripts/check_versions.py[48-55]
- scripts/check_versions.py[86-100]
- scripts/check_versions.py[103-110]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


3. Test helpers lack type hints 📘 Rule violation ✧ Quality
Description
The new test module adds several def functions without complete type annotations for parameters
and/or return types. This violates the requirement for type hints on all function signatures and can
weaken static analysis.
Code

tests/test_check_versions.py[R30-56]

+def _load_module():
+    """Load scripts/check_versions.py, which is not an installed package."""
+    path = REPO_ROOT / "scripts" / "check_versions.py"
+    spec = importlib.util.spec_from_file_location("check_versions", path)
+    module = importlib.util.module_from_spec(spec)
+    spec.loader.exec_module(module)
+    return module
+
+
+check_versions = _load_module()
+
+
+@pytest.fixture
+def repo(tmp_path):
+    """A throwaway copy of the repo's version-bearing files."""
+    for filename in VERSIONED_FILES:
+        shutil.copy(REPO_ROOT / filename, tmp_path / filename)
+    return tmp_path
+
+
+def _write_json(root: Path, filename: str, data) -> None:
+    (root / filename).write_text(json.dumps(data, indent=2), encoding="utf-8")
+
+
+def _read_json(root: Path, filename: str):
+    return json.loads((root / filename).read_text(encoding="utf-8"))
+
Relevance

●●● Strong

Missing type hints in tests/helpers/fixtures have been fixed in past and accepted.

PR-#307

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
Rule 222805 requires explicit type hints for all function signatures. In the added test module,
_load_module() has no return type, the repo() fixture lacks parameter and return annotations,
and _read_json() lacks a return type annotation.

Rule 222805: Require type hints for all function signatures
tests/test_check_versions.py[30-37]
tests/test_check_versions.py[42-47]
tests/test_check_versions.py[54-55]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
New test helper functions and fixtures are missing required type hints for parameters and/or return types.

## Issue Context
The rule applies to all `def`-based functions, including tests and fixtures.

## Fix Focus Areas
- tests/test_check_versions.py[30-37]
- tests/test_check_versions.py[42-47]
- tests/test_check_versions.py[50-55]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


View more (1)
4. Brittle uv.lock parsing 🐞 Bug ☼ Reliability
Description
collect_versions() extracts the windows-mcp version from uv.lock with a regex that requires the
[[package]] block to have name and version on consecutive lines in a specific order. If uv.lock
formatting changes (e.g., an extra field inserted or reordering within the package block), the regex
will fail and incorrectly block releases even when versions are correct.
Code

scripts/check_versions.py[R42-45]

+_UV_LOCK_WINDOWS_MCP = re.compile(
+    r'^\[\[package\]\]\nname = "windows-mcp"\nversion = "([^"]+)"',
+    re.MULTILINE,
+)
Relevance

●● Moderate

No close precedent on uv.lock parsing; robustness fixes are plausible but not clearly enforced.

PR-#358

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
The checker hard-codes a consecutive-line pattern for the windows-mcp package, so any non-semantic
formatting/order change within the [[package]] block can make it miss the version and raise a
ValueError. The current uv.lock entry matches today, but it includes additional fields in the block,
demonstrating that ordering/adjacent-line assumptions are incidental rather than guaranteed.

scripts/check_versions.py[42-67]
uv.lock[1923-1927]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`scripts/check_versions.py` parses `uv.lock` using a formatting-sensitive regex that assumes the `[[package]]` entry for `windows-mcp` has `name` immediately followed by `version`.

## Issue Context
`uv.lock` is structured TOML and may evolve in formatting/order. A release guard should be resilient to harmless formatting changes.

## Fix Focus Areas
- scripts/check_versions.py[42-67]

## Suggested fix
- Replace the regex extraction with a TOML parse (`tomllib.loads(lock_text)`) and then locate the `windows-mcp` entry from the parsed `package` list.
- Alternatively, make the regex tolerant of intervening lines and whitespace, but TOML parsing is preferable for stability.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools



Informational

5. Unchecked second metadata read 🐞 Bug ☼ Reliability
Description
main() reads version metadata via collect_versions() inside a try/except, but then calls check(),
which re-reads/parses the same files outside that exception handler. If a transient I/O or parsing
failure occurs between the two reads, the script can crash with a traceback instead of emitting the
intended single-line error and exit code.
Code

scripts/check_versions.py[112]

+    problems = check(expected)
Relevance

●● Moderate

Potential reliability improvement, but no close precedent about eliminating double reads/exception
scope for scripts.

PR-#358

ⓘ Recommendations generated based on similar findings in past PRs

Evidence
check() re-reads all files via collect_versions(root), while main() has already read them once
and only guards that first read with exception handling. The second read happens after the
try/except and can therefore raise uncaught exceptions.

scripts/check_versions.py[86-89]
scripts/check_versions.py[103-113]

Agent prompt
The issue below was found during a code review. Follow the provided context and guidance below and implement a solution

## Issue description
`main()` calls `collect_versions()` and then calls `check()`, but `check()` calls `collect_versions()` again. The second read is not protected by `main()`'s existing exception handler.

## Issue Context
This duplicates I/O and creates an unhandled exception path if the repository files change or a transient read/parse error occurs between calls.

## Fix Focus Areas
- scripts/check_versions.py[86-100]
- scripts/check_versions.py[103-114]

## Suggested fix
- Refactor `check()` to accept an already-collected `versions: dict[str, str]` (or add a helper like `check_versions(versions, expected)`), and have `main()` call it with its `versions`.
- Alternatively, wrap the `problems = check(expected)` call in the same try/except, but eliminating the second read is cleaner.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools


Grey Divider

To customize comments, go to the Qodo configuration screen, or learn more in the docs.

Qodo Logo

Comment thread scripts/check_versions.py
REPO_ROOT = Path(__file__).resolve().parent.parent

_UV_LOCK_WINDOWS_MCP = re.compile(
r'^\[\[package\]\]\nname = "windows-mcp"\nversion = "([^"]+)"',

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

1. Single quotes in strings 📘 Rule violation ✧ Quality

New Python code and tests introduce single-quoted string literals, which violates the requirement to
use double quotes for all string literals. This can cause inconsistent style and lint/format
failures if enforced in CI.
Agent Prompt
## Issue description
The PR adds single-quoted string literals, but the style rule requires double quotes for all string literals.

## Issue Context
This appears in both the new version-check script and its tests.

## Fix Focus Areas
- scripts/check_versions.py[42-45]
- tests/test_check_versions.py[81-83]
- tests/test_check_versions.py[115-126]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread scripts/check_versions.py
Comment on lines +48 to +104
def collect_versions(root: Path = REPO_ROOT) -> dict[str, str]:
"""Extract every declared version string, keyed by a human-readable label.

Raises:
ValueError: if a file is missing the version field entirely, which is
just as much a packaging bug as a stale value.
"""
versions: dict[str, str] = {}

pyproject = tomllib.loads((root / "pyproject.toml").read_text(encoding="utf-8"))
try:
versions["pyproject.toml:project.version"] = pyproject["project"]["version"]
except KeyError as exc:
raise ValueError("pyproject.toml is missing [project] version") from exc

lock_text = (root / "uv.lock").read_text(encoding="utf-8")
match = _UV_LOCK_WINDOWS_MCP.search(lock_text)
if match is None:
raise ValueError("uv.lock has no [[package]] entry for windows-mcp")
versions["uv.lock:windows-mcp"] = match.group(1)

manifest = json.loads((root / "manifest.json").read_text(encoding="utf-8"))
if "version" not in manifest:
raise ValueError("manifest.json is missing a top-level version field")
versions["manifest.json:version"] = manifest["version"]

server = json.loads((root / "server.json").read_text(encoding="utf-8"))
packages = server.get("packages", [])
if not packages:
raise ValueError("server.json declares no packages")
for index, package in enumerate(packages):
if "version" not in package:
raise ValueError(f"server.json packages[{index}] is missing a version field")
versions[f"server.json:packages[{index}].version"] = package["version"]

return versions


def check(expected: str | None = None, root: Path = REPO_ROOT) -> list[str]:
"""Return a list of human-readable problems; empty means everything agrees."""
versions = collect_versions(root)
distinct = sorted(set(versions.values()))

problems: list[str] = []
if len(distinct) > 1:
problems.append(f"version strings disagree: {', '.join(distinct)}")

if expected is not None:
target = expected.removeprefix("v")
if any(version != target for version in versions.values()):
problems.append(f"expected every version to be {target}")

return problems


def main(argv: list[str]) -> int:
expected = argv[1] if len(argv) > 1 else None

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

2. Missing google-style docstrings 📘 Rule violation ✧ Quality

Public functions in the new scripts/check_versions.py module do not use Google-style docstrings
(missing Args:/Returns: sections), and main() has no docstring. This violates the docstring
standard and reduces maintainability for a release-critical script.
Agent Prompt
## Issue description
Public functions need Google-style docstrings with the required sections (`Args:`, `Returns:`, and `Raises:` where applicable). Some functions have minimal docstrings and `main()` has none.

## Issue Context
The new script is invoked by the release workflow; clear docstrings help prevent future regressions and clarify expected inputs/outputs.

## Fix Focus Areas
- scripts/check_versions.py[48-55]
- scripts/check_versions.py[86-100]
- scripts/check_versions.py[103-110]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment on lines +30 to +56
def _load_module():
"""Load scripts/check_versions.py, which is not an installed package."""
path = REPO_ROOT / "scripts" / "check_versions.py"
spec = importlib.util.spec_from_file_location("check_versions", path)
module = importlib.util.module_from_spec(spec)
spec.loader.exec_module(module)
return module


check_versions = _load_module()


@pytest.fixture
def repo(tmp_path):
"""A throwaway copy of the repo's version-bearing files."""
for filename in VERSIONED_FILES:
shutil.copy(REPO_ROOT / filename, tmp_path / filename)
return tmp_path


def _write_json(root: Path, filename: str, data) -> None:
(root / filename).write_text(json.dumps(data, indent=2), encoding="utf-8")


def _read_json(root: Path, filename: str):
return json.loads((root / filename).read_text(encoding="utf-8"))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

3. Test helpers lack type hints 📘 Rule violation ✧ Quality

The new test module adds several def functions without complete type annotations for parameters
and/or return types. This violates the requirement for type hints on all function signatures and can
weaken static analysis.
Agent Prompt
## Issue description
New test helper functions and fixtures are missing required type hints for parameters and/or return types.

## Issue Context
The rule applies to all `def`-based functions, including tests and fixtures.

## Fix Focus Areas
- tests/test_check_versions.py[30-37]
- tests/test_check_versions.py[42-47]
- tests/test_check_versions.py[50-55]

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread scripts/check_versions.py
Comment on lines +42 to +45
_UV_LOCK_WINDOWS_MCP = re.compile(
r'^\[\[package\]\]\nname = "windows-mcp"\nversion = "([^"]+)"',
re.MULTILINE,
)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Remediation recommended

4. Brittle uv.lock parsing 🐞 Bug ☼ Reliability

collect_versions() extracts the windows-mcp version from uv.lock with a regex that requires the
[[package]] block to have name and version on consecutive lines in a specific order. If uv.lock
formatting changes (e.g., an extra field inserted or reordering within the package block), the regex
will fail and incorrectly block releases even when versions are correct.
Agent Prompt
## Issue description
`scripts/check_versions.py` parses `uv.lock` using a formatting-sensitive regex that assumes the `[[package]]` entry for `windows-mcp` has `name` immediately followed by `version`.

## Issue Context
`uv.lock` is structured TOML and may evolve in formatting/order. A release guard should be resilient to harmless formatting changes.

## Fix Focus Areas
- scripts/check_versions.py[42-67]

## Suggested fix
- Replace the regex extraction with a TOML parse (`tomllib.loads(lock_text)`) and then locate the `windows-mcp` entry from the parsed `package` list.
- Alternatively, make the regex tolerant of intervening lines and whitespace, but TOML parsing is preferable for stability.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

Comment thread scripts/check_versions.py
print(f"error: could not read version metadata: {exc}", file=sys.stderr)
return 1

problems = check(expected)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Informational

5. Unchecked second metadata read 🐞 Bug ☼ Reliability

main() reads version metadata via collect_versions() inside a try/except, but then calls check(),
which re-reads/parses the same files outside that exception handler. If a transient I/O or parsing
failure occurs between the two reads, the script can crash with a traceback instead of emitting the
intended single-line error and exit code.
Agent Prompt
## Issue description
`main()` calls `collect_versions()` and then calls `check()`, but `check()` calls `collect_versions()` again. The second read is not protected by `main()`'s existing exception handler.

## Issue Context
This duplicates I/O and creates an unhandled exception path if the repository files change or a transient read/parse error occurs between calls.

## Fix Focus Areas
- scripts/check_versions.py[86-100]
- scripts/check_versions.py[103-114]

## Suggested fix
- Refactor `check()` to accept an already-collected `versions: dict[str, str]` (or add a helper like `check_versions(versions, expected)`), and have `main()` call it with its `versions`.
- Alternatively, wrap the `problems = check(expected)` call in the same try/except, but eliminating the second read is cleaner.

ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools

@Jeomon
Jeomon merged commit 7bddaad into main Aug 1, 2026
2 checks passed
@Jeomon
Jeomon deleted the ci/version-consistency-guard branch August 1, 2026 07:50
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant