Fix interceptor Lambda syntax error from over-escaped embedded code - #1968
Fix interceptor Lambda syntax error from over-escaped embedded code#1968antonyprasad-db wants to merge 1 commit into
Conversation
The interceptor Lambda cannot load. Cell 9 writes lambda_function.py from the embedded
INTERCEPTOR_CODE string, and line 28 of that string contains a literal backslash before
each quote:
print(f"[STEP 3] Entra JWT | email={claims.get(\"email\")} ...")
Deploying exactly what the notebook produces fails at module import:
errorType: Runtime.UserCodeSyntaxError
errorMessage: Syntax error in module 'lambda_function': unexpected character after
line continuation character (lambda_function.py, line 28)
Every Python version from 3.9 through 3.14 rejects it, with the wording differing before
and after 3.12. Because it is an import failure rather than a runtime one, it affects
every invocation, including the tools/list pass-through.
Swapping the outbound Authorization header for the caller's exchanged token is the
interceptor's entire job, so while it fails to load no token exchange happens and the
sample cannot demonstrate the per-user delegation it is about. The exact caller-visible
symptom depends on how the gateway handles an interceptor invocation failure, which I did
not verify. Either way the notebook does not raise: cell 16 gates its success banner on
"@" appearing in the result of SELECT current_user(), so the banner just never prints, and
the [STEP 3] through [STEP 6] lines that cell 17 tells you to look for are absent from
CloudWatch.
The fix single-quotes the dict keys inside the f-string. This matches cell 15, which
already writes claims.get('name') and claims.get('email') that way, so it also makes the
notebook internally consistent. Nested double quotes would work on the python3.12 runtime
this notebook pins, but the single-quoted form is valid on every version, and the update
path here (update_function_code on ResourceConflictException) never re-sets the runtime on
a function that already exists -- so version-independent sample code is the safer choice.
Verified by extracting INTERCEPTOR_CODE from this notebook, deploying it to a python3.12
Lambda and invoking it. Before: StatusCode 200 with FunctionError Unhandled and the syntax
error above. After: FunctionError none, with the interceptor logging the decoded caller
identity at STEP 3 and reaching the token exchange at STEP 4. I did not exercise the
exchange itself end to end, which needs a federation policy for the caller's issuer, so
this is verified as far as the module importing and the identity decoding correctly.
One line changed. No behaviour change beyond making the module importable.
|
Check out this pull request on See visual diffs & provide feedback on Jupyter Notebooks. Powered by ReviewNB |
|
Gentle nudge — this one is a single line and no reviewer was ever requested, so I think it just fell through triage rather than being held. The cell embeds the interceptor Lambda's source as a string, and the escaping produces Python that does not parse. Verified rather than eyeballed: Measured on Python 3.9 and 3.11. Note PEP 701 relaxed the backslash restriction in 3.12, so on a 3.12+ runtime the failure mode may differ — I have not tested that — but the escaping still is not what is intended either way. The fix is just the conventional single-quote nesting inside the f-string expression, so the generated Lambda parses. No behaviour change beyond that. Worth a look because this cell is on the critical path for the per-user delegation walkthrough: the interceptor is what carries the end user's identity through, so if its source does not parse the sample cannot be followed end to end as published. Happy to request a specific reviewer if there is a preferred owner for |
Problem
The interceptor Lambda in
03-integrations/data-platforms/databricks-dbsql-per-user-delegationcannot load.Cell 9 writes
lambda_function.pyfrom the embeddedINTERCEPTOR_CODEstring. Line 28 of that string contains a literal backslash before each quote inside an f-string expression:Deploying exactly what the notebook produces fails at module import:
Every Python version from 3.9 through 3.14 rejects it, with the wording differing before and after 3.12. Because it is an import failure rather than a runtime one, it affects every invocation.
Why it matters
Swapping the outbound
Authorizationheader for the caller's exchanged token is the interceptor's entire job, so while it fails to load no token exchange happens and the sample cannot demonstrate the per-user delegation it is about.The gateway fails closed in this state — measured below — so the sample does not half-work. Every MCP call returns
HTTP 500and cell 16's success banner never prints. The reassuring part is that there is no silent fallback to service-principal access.Fix
Single-quote the dict keys inside the f-string. One line.
This matches cell 15, which already writes
claims.get('name')andclaims.get('email')that way, so it also makes the notebook internally consistent. Nested double quotes would work on thepython3.12runtime this notebook pins, but the single-quoted form is valid on every version — and the update path here (update_function_codeonResourceConflictException) never re-sets the runtime on a function that already exists, so version-independent sample code seemed the safer choice. Happy to switch if you prefer the nested form.Verification
Lambda level. Extracted
INTERCEPTOR_CODEfrom the notebook, deployed it to apython3.12Lambda, invoked it with a synthetic JWT:StatusCode 200,FunctionError: Unhandled, the syntax error aboveFunctionError: none; logs the decoded caller identity at[STEP 3]and reaches the token exchange at[STEP 4]Through a gateway. Attached the interceptor to a real AgentCore Gateway target and swapped only the Lambda code, keeping the same gateway, target and interceptor ARN:
initializetools/listHTTP 500HTTP 500200 OK200 OK(an empty list in my setup, since the exchange is not configured end to end)Because the only variable between rows is the interceptor source, the
500is attributable to the module failing to import.Notes and open questions
exceptbranch returns a passthrough with no header override, which is a deliberate design choice with a different outcome.500. What I could not exercise is the interceptor's success path —[STEP 5]/[STEP 6], where it swaps the header — because completing the exchange needs a Databricks federation policy for the caller's issuer on the account that owns the target workspace, and I did not have one to hand. So per-user delegation working end to end is not something I am claiming here; the syntax fix is.No behaviour change beyond making the module importable.
This pull request and its description were written by Isaac.