Skip to content

fix(cicd): make the awslabs namespace check fail the build - #4605

Open
dihannahdi wants to merge 1 commit into
awslabs:mainfrom
dihannahdi:fix/awslabs-init-gate-exit-code
Open

dihannahdi wants to merge 1 commit into
awslabs:mainfrom
dihannahdi:fix/awslabs-init-gate-exit-code

Conversation

@dihannahdi

Copy link
Copy Markdown

Fixes

Summary

scripts/verify_awslabs_init.py is wired into the per-package gate at .github/workflows/python.yml:134 and is meant to fail the build when a server's awslabs/__init__.py is not the namespace shim this repo requires. It is a click command that signals failure with return 1. Click's standalone mode discards the return value, so the process prints the mismatch and exits 0.

Run against a deliberately corrupted package, the original script prints ✗ Mismatch: ... and then exits 0. The gate has never fired. scripts/verify_package_name.py in the same directory uses sys.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 own pyproject.toml replaces the root .ruff.toml rather than merging with it, so a server that does not declare quote-style inherits ruff's double-quote default. ecs-mcp-server is exactly that case — 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 rather than sloppy. well-architected-security-mcp-server declares quote-style = "double" explicitly. Comparing ast.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__.py had no __path__ = __import__('pkgutil').extend_path(__path__, __name__) line at all — only a comment — which makes awslabs a regular package there rather than a namespace portion.

documentdb-mcp-server, mcp-lambda-handler and well-architected-security-mcp-server had no awslabs/__init__.py at all. These 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 (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__.py and 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, PYTHONPATH dev setups, stacked Lambda layers — importing a sibling awslabs.* server's subpackage raises ModuleNotFoundError. Reversing the path order hides it, which is what makes it worth a gate: it is order-dependent and silent. Users routinely install several awslabs.* MCP servers into one environment.

Checklist

If your change doesn't seem to apply, please leave them unchecked.

  • I have reviewed the contributing guidelines
  • I have performed a self-review of this change
  • Changes have been tested
  • Changes are documented

Is this a breaking change? (N)

RFC issue number:

Checklist:

  • Migration process documented
  • Implement warnings (if it can live side by side)

How this was verified

  • Original script against a corrupted fixture: prints ✗ Mismatch, exits 0. Fixed script, same fixture: exits 1.
  • Fixed script still exits 1 for a file missing the extend_path call, and for an otherwise-compliant file with import os appended — so relaxing to an AST comparison did not relax what the gate actually enforces.
  • All 62 servers pass after this change (6 failed before it).
  • Each touched awslabs/__init__.py confirmed already-formatted under its own server's ruff configuration, so this does not trade one red gate for another.
  • The ModuleNotFoundError was reproduced with wheels built by the same backend and packages = ["awslabs"] config the real servers use, not inferred. What is not independently reproduced: installing two actual published awslabs.* distributions from PyPI together.
  • pre-commit run over the diff: no failures. gitleaks could not be installed here (Go module download failed) and check-license-header, pyright and pytest were skipped — the last two are pre-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

@mikegc-aws

Copy link
Copy Markdown
Contributor

Confirming the src/mcp-lambda-handler/awslabs/__init__.py portion of this PR from a package-owner perspective: that package is currently missing the awslabs namespace shim on main (the directory holds only mcp_lambda_handler/), so the added file — the standard pkgutil.extend_path PEP 420 shim, byte-identical to the other servers here — is correct and needed. Your root-cause writeup (Click's standalone mode discarding return 1, so the gate exits 0) matches what I see: the check has never actually fired, which is why this gap went unnoticed. No concerns on the mcp-lambda-handler change.

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>
@dihannahdi
dihannahdi force-pushed the fix/awslabs-init-gate-exit-code branch from 94da678 to 73c4a04 Compare September 11, 2026 09:12
@dihannahdi

Copy link
Copy Markdown
Author

Thanks for checking mcp-lambda-handler from the package-owner side.

Two updates since your comment. The branch is force-pushed, because of the rebase.

  1. Rebased onto main (83ac913). Re-ran the check over all 62 servers, all pass.
  2. Two success paths in the script still used return 0 — the same shape as the bug this PR fixes, so a later edit could turn one of them into return 1 and the gate would go quiet again without anyone noticing. Every exit now goes through sys.exit(), the way scripts/verify_package_name.py already does.

The four awslabs/__init__.py files are unchanged from what you reviewed. Only scripts/verify_awslabs_init.py moved.

Exit codes as they stand now, run against real directories:

  • compliant shim, single quotes → 0
  • compliant shim, double quotes → 0
  • no awslabs/ directory at all (README-only server) → 0
  • __init__.py with no extend_path line → 1
  • compliant shim with import os appended → 1
  • awslabs/ present but no __init__.py1

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: To triage

Development

Successfully merging this pull request may close these issues.

2 participants