Skip to content

[Bug]: WebServer route layer doesn't URL-decode path segments #222

@yaythomas

Description

@yaythomas

Summary

dex-local-runner (WebRunner) returns 404 for every Get/State/History/Checkpoint/Stop call against an execution whose ARN contains a literal /, and silently returns an empty list for ListDurableExecutionsByFunction when the function name contains : or $. The cause is that the WebServer's route layer does not URL-decode captured path segments before using them as store-lookup keys, while boto's rest-json serializer percent-encodes those characters in non-greedy URI labels.

Affected versions

aws-durable-execution-sdk-python-testing == 1.2.0 (PyPI). The bug was introduced in #216 (which started minting ARNs of the form <uuid>/<invocation-id>) and shipped in v1.2.0.

Affected runners

  • WebRunner / dex-local-runner — broken for every nontrivial durable function, since the first context.step / context.wait / context.callback triggers a checkpoint call that 404s.
  • DurableFunctionTestRunner (in-process) — not affected; uses InMemoryServiceClient which ignores durable_execution_arn.
  • DurableFunctionCloudTestRunner (real AWS) — not affected; ARNs come from the real Lambda backend and never round-trip through the local route layer.

Affected operations

All five durable-execution operations whose URI label is the ARN, plus the by-function list:

Operation URI template
GetDurableExecution GET /2025-12-01/durable-executions/{DurableExecutionArn}
GetDurableExecutionState GET /2025-12-01/durable-executions/{DurableExecutionArn}/state
GetDurableExecutionHistory GET /2025-12-01/durable-executions/{DurableExecutionArn}/history
CheckpointDurableExecution POST /2025-12-01/durable-executions/{DurableExecutionArn}/checkpoint
StopDurableExecution POST /2025-12-01/durable-executions/{DurableExecutionArn}/stop
ListDurableExecutionsByFunction GET /2025-12-01/functions/{FunctionName}/durable-executions

For non-greedy labels (no +), boto encodes:

  • /%2F
  • :%3A
  • $%24

Reproduction

import boto3
from aws_durable_execution_sdk_python_testing import WebRunner, WebRunnerConfig
from aws_durable_execution_sdk_python_testing.web.server import WebServiceConfig

with WebRunner(WebRunnerConfig(web_service=WebServiceConfig(port=0))) as runner:
    # ...start an execution via runner so a slash-containing ARN is minted...
    arn = "<uuid>/<invocation-id>"  # the format Execution.new() produces
    client = boto3.client("lambda", endpoint_url="http://127.0.0.1:<port>", ...)
    client.get_durable_execution(DurableExecutionArn=arn)
    # botocore.exceptions.ClientError:
    #   An error occurred (404) when calling the GetDurableExecution operation:
    #   Execution <uuid>%2F<invocation-id> not found

The %2F literal in the error message is the giveaway — the WebServer is looking up the still-encoded form rather than the canonical ARN.

Why nothing caught it

  • All routing tests use slash-free literals like "test-arn" or "my-function", so Route.from_string never produced a segment containing %XX.
  • All boto3.client usages in the test suite are mocked (patch("boto3.client")), so the real botocore serializer that does the encoding is never exercised.
  • The integration test in tests/web/e2e/server_int_test.py constructs HTTPRequest objects in-process and bypasses the HTTP/socket layer.
  • The headline e2e test (tests/e2e/basic_success_path_test.py) uses DurableFunctionTestRunner, which doesn't go through the affected route layer.
  • CI runs only hatch test / hatch fmt --check / hatch run types:check; no test exercised the boto→WebServer seam.

#117 fixed the same bug shape for callback IDs but the systemic fix (decode at the parser layer) was not applied at the time.

Expected fix

URL-decode captured path segments at the parser layer (Route.from_string) so every existing and future captured field inherits the behavior, and add a real-boto-through-WebServer regression test so the seam stays covered. PR forthcoming.

Metadata

Metadata

Assignees

Labels

No labels
No labels

Type

No fields configured for Bug.

Projects

Status

Done

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions