fix(cicd): make the awslabs namespace check fail the build - #4605
dihannahdi wants to merge 1 commit into
Conversation
|
Confirming the |
scripts/verify_awslabs_init.py is wired into the per-package gate at
.github/workflows/python.yml:134 and is meant to fail when a server's
awslabs/__init__.py is not the namespace shim the repo requires. It is a click
command that signals failure with `return 1`. Click's standalone mode discards
the return value, so it printed the mismatch and exited 0. Against a
deliberately corrupted package it printed "Mismatch" and still exited 0, so the
gate has never fired. scripts/verify_package_name.py in the same directory
already uses sys.exit(); this follows it.
Two changes are needed, because turning the gate on as written would fail
servers that are not actually wrong. The check compared the file byte for byte
against a single-quoted FILE_CONTENTS, but a server that declares [tool.ruff]
in its own pyproject.toml replaces the root .ruff.toml rather than merging with
it, so a server that does not declare quote-style gets ruff's double-quote
default. ecs-mcp-server is in exactly that position: `ruff format` inside that
directory rewrites 'pkgutil' to "pkgutil", so byte equality with a
single-quoted constant is unreachable there and its double quotes were correct
all along. well-architected-security-mcp-server declares quote-style = "double"
explicitly. The check now compares the parsed AST, which accepts either quote
style and any comment wording while still rejecting a file that does anything
other than extend the namespace path. Verified it still exits 1 for a file
missing the extend_path call and for a compliant file with an extra import
appended.
With the gate working, one server was genuinely broken.
aws-serverless-mcp-server's awslabs/__init__.py had no
`__path__ = __import__('pkgutil').extend_path(...)` line at all, which makes
awslabs a regular package there rather than a namespace portion. Building real
hatchling wheels with the same `packages = ["awslabs"]` config the servers ship
and placing that one first on a multi-root sys.path -- editable installs,
PYTHONPATH dev setups, stacked Lambda layers -- makes importing a sibling
awslabs.* server's subpackage raise ModuleNotFoundError. Reversing the path
order hides it, so it is order-dependent and silent.
documentdb-mcp-server, mcp-lambda-handler and well-architected-security-mcp-server
had no awslabs/__init__.py at all. Those were verified to import correctly
alongside pkgutil-style siblings in both sys.path orders, so nothing is broken
today; they are brought into line with the other 58 servers because the shim is
an idempotent no-op and their packaging config is identical to their compliant
peers. ecs-mcp-server and oracle-mcp-server are left untouched, since the AST
comparison accepts what they already have.
The two success paths in that function still used `return 0`, the same shape as
the bug being fixed: a later edit could turn one into `return 1` and the gate
would go quiet again. Every exit path now goes through sys.exit(), matching
verify_package_name.py and verify_tool_names.py, and the annotation is `-> None`
because click never reads a return value.
All 62 servers pass the check, and each touched file is stable under its own
server's ruff configuration.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
94da678 to
73c4a04
Compare
|
Thanks for checking Two updates since your comment. The branch is force-pushed, because of the rebase.
The four Exit codes as they stand now, run against real directories:
|
Fixes
Summary
scripts/verify_awslabs_init.pyis wired into the per-package gate at.github/workflows/python.yml:134and is meant to fail the build when a server'sawslabs/__init__.pyis not the namespace shim this repo requires. It is aclickcommand that signals failure withreturn 1. Click's standalone mode discards the return value, so the process prints the mismatch and exits0.Run against a deliberately corrupted package, the original script prints
✗ Mismatch: ...and then exits0. The gate has never fired.scripts/verify_package_name.pyin the same directory usessys.exit()correctly, and this follows it.Changes
Two changes are needed, because switching the gate on as written would fail servers that are not actually wrong.
1. Compare the parsed module, not the bytes. The check compared the file byte-for-byte against a single-quoted
FILE_CONTENTS. But a server that declares[tool.ruff]in its ownpyproject.tomlreplaces the root.ruff.tomlrather than merging with it, so a server that does not declarequote-styleinherits ruff's double-quote default. ecs-mcp-server is exactly that case —ruff formatinside that directory rewrites'pkgutil'to"pkgutil", so byte equality with a single-quoted constant is unreachable there, and its double quotes were correct all along rather than sloppy. well-architected-security-mcp-server declaresquote-style = "double"explicitly. Comparingast.dump(ast.parse(...))accepts either quote style and any comment wording, while still rejecting a file that does anything other than extend the namespace path. The license header has its own check.2. Fix the one server that was genuinely broken. aws-serverless-mcp-server's
awslabs/__init__.pyhad no__path__ = __import__('pkgutil').extend_path(__path__, __name__)line at all — only a comment — which makesawslabsa regular package there rather than a namespace portion.documentdb-mcp-server, mcp-lambda-handler and well-architected-security-mcp-server had no
awslabs/__init__.pyat all. These were verified to import correctly alongside pkgutil-style siblings in bothsys.pathorders, so nothing is broken today; they are brought into line with the other 58 servers because the shim is an idempotent no-op and their packaging config (packages = ["awslabs"]) is identical to their compliant peers. ecs-mcp-server and oracle-mcp-server are left untouched — the AST comparison accepts what they already have, and rewriting them would fight their own formatter.User experience
Before: a server could ship a non-conforming
awslabs/__init__.pyand CI stayed green, because the check that existed to catch it could not fail.After: it fails. Concretely, with real hatchling wheels built from aws-serverless-mcp-server's previous content and placed first on a multi-root
sys.path— editable installs,PYTHONPATHdev setups, stacked Lambda layers — importing a siblingawslabs.*server's subpackage raisesModuleNotFoundError. Reversing the path order hides it, which is what makes it worth a gate: it is order-dependent and silent. Users routinely install severalawslabs.*MCP servers into one environment.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
✗ Mismatch, exits0. Fixed script, same fixture: exits1.1for a file missing theextend_pathcall, and for an otherwise-compliant file withimport osappended — so relaxing to an AST comparison did not relax what the gate actually enforces.awslabs/__init__.pyconfirmed already-formatted under its own server's ruff configuration, so this does not trade one red gate for another.ModuleNotFoundErrorwas reproduced with wheels built by the same backend andpackages = ["awslabs"]config the real servers use, not inferred. What is not independently reproduced: installing two actual publishedawslabs.*distributions from PyPI together.pre-commit runover the diff: no failures.gitleakscould not be installed here (Go module download failed) 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