Skip to content

fix(event_handler): strip the HTTP API stage only when rawPath has it - #8605

Merged
leandrodamascena merged 2 commits into
aws-powertools:developfrom
Thanatos9404:fix/8577-http-api-stage-prefix
Oct 22, 2026
Merged

leandrodamascena merged 2 commits into
aws-powertools:developfrom
Thanatos9404:fix/8577-http-api-stage-prefix

Conversation

@Thanatos9404

Copy link
Copy Markdown
Contributor

Issue number: closes #8577

Summary

Changes

APIGatewayProxyEventV2.path removed len("/" + stage) characters from rawPath for every named stage. Through the execute-api endpoint the stage is part of rawPath, so that worked. Behind a custom domain API mapping, or in SAM local, rawPath has no stage, so the path got cut (/my/path became path for stage dev) and APIGatewayHttpResolver returned 404 for every route.

path now strips the stage only when rawPath is /{stage} or starts with /{stage}/. Otherwise it returns rawPath as is. $default is unchanged.

  • rawPath equal to /{stage} (the root of a named stage) now gives / instead of an empty string, so it matches a / route like /{stage}/ already did. The empty string didn't match any route.
  • A path that only starts with the stage name, like /devices for stage dev, is no longer cut (it used to become ices).
  • LambdaFunctionUrlEvent inherits path, but its stage is always $default, so nothing changes there. APIGatewayProxyEvent (REST), ALBEvent and the VPC Lattice events don't strip a stage in path, so they don't have this bug.

Tests

  • tests/functional/event_handler/required_dependencies/test_api_gateway.py: test_api_gateway_v2_stage_prefix_in_raw_path sends a GET through APIGatewayHttpResolver for 7 shapes: named stage with and without the stage in rawPath, $default, rawPath equal to /dev and to /dev/, /devices on stage dev, and /dev/devices. It checks the status code and app.current_event.path.
  • tests/unit/data_classes/required_dependencies/test_api_gateway_proxy_event.py: the same shapes against APIGatewayProxyEventV2.path, plus /dev/dev to show the stage is stripped once.

6 of the 14 new cases fail on develop (no stage in rawPath, rawPath equal to the stage, and /devices, in both files). All 14 pass with this change.

Checks

  • tests/functional/event_handler, tests/functional/data_classes, tests/unit/event_handler and tests/unit/data_classes: 1385 passed, 1 skipped. Three tests fail on my Windows machine and fail the same way on develop (test_openapi_encode_purepath, test_load_resolver_file_not_found, test_code_pipeline_get_artifact; path separators and temp file locking).
  • ruff check and ruff format --check on the changed files: clean.
  • mypy and ty check on api_gateway_proxy_event.py: clean.

User experience

Before, with the snippet from the issue (stage dev, custom domain, rawPath /my/path):

print(app.resolve(ev, None)["statusCode"])  # 404
print(app.current_event.path)               # "path"

After: 200, and app.current_event.path == "/my/path". Requests through the execute-api URL (rawPath /dev/my/path) still resolve to /my/path.

This also fixes strip_prefixes for an API mapping path on a named stage. With APIGatewayHttpResolver(strip_prefixes=["/payment"]), stage dev and rawPath /payment/my/path, path used to be ment/my/path, so the prefix never matched and the request got a 404. Now it routes to /my/path.

Risk: code that reads event.path for a named stage behind a custom domain gets the real path instead of a truncated one. If a custom domain request's own path starts with /{stage}/ (a /dev/items route on stage dev), the stage is still stripped, because rawPath alone can't tell the two cases apart. That part behaves as it did before.


By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of your choice.

Disclaimer: We value your time and bandwidth. As such, any pull requests created on non-triaged issues might not be successful.

APIGatewayProxyEventV2.path removed len("/" + stage) characters from
rawPath for any named stage. Behind a custom domain API mapping or in
SAM local the stage is not part of rawPath, so the path was truncated
and every route returned 404.

Strip the stage only when rawPath equals "/{stage}" or starts with
"/{stage}/", and return "/" for the stage root.
@Thanatos9404
Thanatos9404 requested a review from a team as a code owner October 18, 2026 12:13
@boring-cyborg

boring-cyborg Bot commented Oct 18, 2026

Copy link
Copy Markdown

Thanks a lot for your first contribution! Please check out our contributing guidelines and don't hesitate to ask whatever you need.
In the meantime, check out the #python channel on our Powertools for AWS Lambda Discord: Invite link

@boring-cyborg boring-cyborg Bot added the tests label Oct 18, 2026
@powertools-for-aws-oss-automation powertools-for-aws-oss-automation Bot added the size/M Denotes a PR that changes 30-99 lines, ignoring generated files. label Oct 18, 2026
@leandrodamascena leandrodamascena added this to the 3.38.0 milestone Oct 19, 2026

@leandrodamascena leandrodamascena left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Thanks @Thanatos9404, really appreciate this one! I ran the suites and also deployed it behind a real HTTP API with $default, dev and stage-1 stages, plus direct invokes with the custom-domain event shape. Everything that worked before still works, and the cases that change were all returning garbage before. I even found one more on develop: stage dev with rawPath /api/users was routed to /users by accident because the stage length matched the segment. Your fix handles it.

I'm approving now, but this will go out in 3.38.0, so it will sit here for a bit before I merge. You don't need to rebase or touch anything in the meantime, I'll take care of that, and you'll get the credit in the release notes. Thank you so much!

@codecov

codecov Bot commented Oct 19, 2026 •

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.94%. Comparing base (6c841da) to head (d875c05).
⚠️ Report is 1 commits behind head on develop.

Additional details and impacted files
@@           Coverage Diff            @@
##           develop    #8605   +/-   ##
========================================
  Coverage    96.93%   96.94%           
========================================
  Files          318      318           
  Lines        16045    16051    +6     
  Branches      1425     1427    +2     
========================================
+ Hits         15554    15560    +6     
  Misses         349      349           
  Partials       142      142           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

@sonarqubecloud

Copy link
Copy Markdown

@leandrodamascena
leandrodamascena merged commit 9c6ca41 into aws-powertools:develop Oct 22, 2026
13 of 14 checks passed
@boring-cyborg

boring-cyborg Bot commented Oct 22, 2026

Copy link
Copy Markdown

Awesome work, congrats on your first merged pull request and thank you for helping improve everyone's experience!

@powertools-for-aws-oss-automation

Copy link
Copy Markdown

Awesome work, congrats on your first merged pull request and thank you for helping improve everyone's experience!

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

Labels

size/M Denotes a PR that changes 30-99 lines, ignoring generated files. tests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Bug: APIGatewayHttpResolver returns 404 for every route when the stage is not $default and rawPath has no stage prefix

2 participants