fix(cicd): make a server's version a single source of truth - #4604
Open
dihannahdi wants to merge 1 commit into
Open
fix(cicd): make a server's version a single source of truth#4604dihannahdi wants to merge 1 commit into
dihannahdi wants to merge 1 commit into
Conversation
pyproject.toml is the only version that reaches PyPI, because python.yml's `uv build` reads nothing else. Every hardcoded version literal in a package is therefore a second copy that something has to keep in sync, and the thing meant to keep it in sync fails open. PyPiPackage.bump_version() in .github/workflows/release.py always bumps pyproject.toml, then makes a best-effort attempt to patch a literal in awslabs/<module>/__init__.py. It derived that directory name from the package name instead of discovering it, so the one server whose directory name is irregular (aws-location-mcp-server: aws_location_server, not aws_location_mcp_server) never matched. Its literal froze at 1.0.0 while pyproject reached 2.1.1. The regex it uses only matches a quoted literal, so amazon-translate's `__version__ = MCP_SERVER_VERSION` was missed and the real constant in consts.py never moved. And it only ever opens that one file, so a literal anywhere else is invisible. All three paths echoed a warning and returned success, and the commit step only checks `git diff --cached --quiet` over src/, which is always dirty because pyproject.toml did change. Rather than teach the release script to chase every literal, this removes the duplicate: the version is read from the installed distribution with importlib.metadata, following the pattern already used by postgres, mssql and oracle. Nothing left to sync. Six servers were drifting. Three of them fed the stale value straight into botocore's user_agent_extra, so every AWS API call reported the wrong version: amazon-translate consts.py 1.0.0 vs 1.0.7 (used in aws_client.py) billing-cost-management aws_service_base.py 1.0.0 vs 0.0.37 (used there) stepfunctions-tool server.py 0.1.5 vs 0.2.1 (used in aws_helper.py) aws-location __init__.py 1.0.0 vs 2.1.1 (unused) valkey version.py 0.2.0 vs 1.1.1 (unused, removed) sagemaker-ai sagemaker_hyperpod 1.0.0 vs 1.1.0 (unused, removed) billing-cost-management and stepfunctions-tool each had a correct constant and a stale shadow, and the consumer had picked the shadow. release.py now discovers the subpackage directory instead of deriving its name, raises on a genuinely structural problem instead of warning, and treats the absence of a literal as correct rather than as a failure. scripts/ verify_version_sync.py walks the AST of every module under awslabs/ and fails when a version literal disagrees with pyproject.toml, wired into the existing per-package gate in python.yml. It catches the release script's blind spots because it does not care which file the literal lives in. tests/test_server.py carried a commented-out test_version_matches_pyproject() pointing at awslabs#167 and awslabs#425. awslabs#167 proposed commitizen and was closed by the stale bot without a decision, which is why 53 servers still carry [tool.commitizen] config that nothing invokes. awslabs#425 asked for exactly this and was closed with no comments. The check now covers all 62 servers, so the dead block is removed. Closes awslabs#4476 Closes awslabs#4477 Closes awslabs#425 Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
dihannahdi
requested review from
a team,
JunqiYe,
Rahul-1404,
allenss-amazon,
chittev,
jade710,
johnwangwyx,
kevinmcgehee,
madolson,
shsrams,
somsubhro and
swarnaprakash
as code owners
September 9, 2026 06:11
dihannahdi
requested review from
Comusus,
anniewn,
bhatia-di,
callnmm,
cgalvan,
finklen-amazon,
goviha01,
kmgrubbs,
mattkelsey,
rahullks and
wangyuhere
as code owners
September 9, 2026 06:11
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #4476, fixes #4477, fixes #425
Summary
pyproject.tomlis the only version that reaches PyPI —python.yml'suv buildreads nothing else. Every hardcoded version literal inside a package is therefore a second copy that something has to keep in sync, and the thing meant to keep it in sync fails open.PyPiPackage.bump_version()in.github/workflows/release.pyalways bumpspyproject.toml, then makes a best-effort attempt to patch a literal inawslabs/<module>/__init__.py. It fails silently three ways:module_name = package_name[8:].replace('-', '_')producesaws_location_mcp_server, but aws-location-mcp-server's directory isaws_location_server.init_file.exists()isFalse, so it warned and returned success. That constant froze at1.0.0whilepyproject.tomlreached2.1.1.__init__.pyis__version__ = MCP_SERVER_VERSION, an identifier, so the real constant inconsts.pywas never touched.server.pyor autilities/module is invisible to it.All three paths
click.echoa warning and still return the new version, and the commit step only checksgit diff --cached --quietoversrc/, which is always dirty becausepyproject.tomldid change. So the release PR opens green.Rather than teach the release script to chase every literal, this removes the duplicate. The version is read from the installed distribution with
importlib.metadata, following the pattern already used bypostgres,mssqlandoracle. There is then nothing to sync.Changes
Six servers were drifting. Three fed the stale value straight into botocore's
user_agent_extra, so every AWS API call reported the wrong version:pyproject.tomlconsts.py1.0.01.0.7aws_client.py:93utilities/aws_service_base.py1.0.00.0.37:99server.py0.1.50.2.1aws_helper.py:55__init__.py1.0.02.1.1version.py0.2.01.1.1sagemaker_hyperpod/__init__.py1.0.01.1.0billing-cost-management and stepfunctions-tool each had a correct constant and a stale shadow, and the consumer had picked the shadow.
release.pynow discovers the subpackage directory instead of deriving its name, raises on a genuinely structural problem instead of warning, and treats the absence of a literal as correct rather than as a failure.validate_path_securityis preserved, now called only for a file that exists — previously it raised for a missing file, was caught by the localexcept ValueError, and was misreported as "Cannot update__init__.pysafely", which made the "not found" branch unreachable.scripts/verify_version_sync.pywalks the AST of every module underawslabs/and fails when a version literal disagrees withpyproject.toml. It catches all three of the blind spots above because it does not care which file the literal lives in, and it parses rather than regex-matches, since the regex is what failed. Wired into the existing per-package gate inpython.yml, blocking (no|| true).scripts/tests/test_verify_version_sync.pycovers the matching case, the disagreeing case, the indirect-assignment case, a literal in a nested non-__init__module, a package with noawslabs/, and apyproject.tomlwith no version key. There is currently no job that runs tests for repo tooling — the matrix inpython.ymlis built fromsrc/*/pyproject.toml, and thepytesthook in.pre-commit-config.yamlisstages: [pre-push]withfiles: src\/.*\/pyproject.toml, so it never runs in CI — so a smallverify-scriptsjob was added rather than leaving a test nothing executes.stepfunctions-tool'stests/test_server.pycarried a commented-outtest_version_matches_pyproject()pointing at RFC: Package Bumping #167 and chore(cicd): bump version in all related files #425. RFC: Package Bumping #167 proposed commitizen and was closed by the stale bot without a decision, which is why 53 servers still carry[tool.commitizen]config that nothing invokes. chore(cicd): bump version in all related files #425 asked for exactly this and was closed with no comments. The new check covers all 62 servers, so the dead block is removed.User experience
Before:
awslabs.amazon-translate-mcp-server1.0.7 identified itself to AWS asmd/awslabs#mcp#amazon-translate-mcp-server/1.0.0on every Translate call. Nothing failed, nothing warned, and the same was true for Cost Explorer and Step Functions traffic. A release could not fix it, because the release is what caused it.After: the version reported at runtime is the version that was published, and a literal that disagrees with
pyproject.tomlfails the PR that introduces it.Checklist
If your change doesn't seem to apply, please leave them unchecked.
Is this a breaking change? (N)
RFC issue number:
Checklist:
How this was verified
scripts/verify_version_sync.pyacross all 62 servers: 6 violations before, 0 after.importlib.metadata.version()resolves the real published version for all four rewired distributions in a live venv (0.2.1,1.0.7,0.0.37,2.1.1) — not just correct on paper.release.pyexercised against fixtures for all four cases: derived-name mismatch now patches the right file; indirect assignment succeeds without patching; two subpackages raises; a normal package with a literal still gets patched.main(1 and 43 respectively) and the counts are identical before and after. sagemaker-ai's 2 failures are Linux-only path assertions failing on Windows, also identical. valkey could not be run —uv syncfails buildingvalkey-glidev2.3.1 in this environment, unrelated to this change; that server's edit is the deletion of an unreferenced file.pre-commit runover the diff: 21 hooks pass.gitleakscould not be installed here (its Go modules failed to download) andcheck-license-header,pyrightandpytestwere skipped — the last two arepre-push-staged and do not run in CI.Acknowledgment
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of the project license.
🤖 Generated with Claude Code