Skip to content

Fix interceptor Lambda syntax error from over-escaped embedded code - #1968

Open
antonyprasad-db wants to merge 1 commit into
awslabs:mainfrom
antonyprasad-db:antony/fix-interceptor-lambda-syntax
Open

Fix interceptor Lambda syntax error from over-escaped embedded code#1968
antonyprasad-db wants to merge 1 commit into
awslabs:mainfrom
antonyprasad-db:antony/fix-interceptor-lambda-syntax

Conversation

@antonyprasad-db

@antonyprasad-db antonyprasad-db commented Aug 19, 2026

Copy link
Copy Markdown

Problem

The interceptor Lambda in 03-integrations/data-platforms/databricks-dbsql-per-user-delegation cannot load.

Cell 9 writes lambda_function.py from the embedded INTERCEPTOR_CODE string. Line 28 of that string contains a literal backslash before each quote inside an f-string expression:

print(f"[STEP 3] Entra JWT | email={claims.get(\"email\")} name={claims.get(\"name\")}")

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.

Why it matters

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 gateway fails closed in this state — measured below — so the sample does not half-work. Every MCP call returns HTTP 500 and 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') 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 seemed the safer choice. Happy to switch if you prefer the nested form.

Verification

Lambda level. Extracted INTERCEPTOR_CODE from the notebook, deployed it to a python3.12 Lambda, invoked it with a synthetic JWT:

Code Result
As published StatusCode 200, FunctionError: Unhandled, the syntax error above
Patched FunctionError: 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:

Code initialize tools/list
As published HTTP 500 HTTP 500
Patched 200 OK 200 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 500 is attributable to the module failing to import.

Notes and open questions

  • Is the fail-closed behaviour documented? I could not find it. It seems worth a line in the sample, since "an interceptor error fails the whole call" is useful when debugging. This is distinct from the interceptor's own error handling: its except branch returns a passthrough with no header override, which is a deliberate design choice with a different outcome.
  • Scope of the verification. Everything this PR changes is verified: the module imports, the caller's identity decodes, and the gateway stops returning 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.

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.
@review-notebook-app

Copy link
Copy Markdown

Check out this pull request on  ReviewNB

See visual diffs & provide feedback on Jupyter Notebooks.


Powered by ReviewNB

@github-actions github-actions Bot added the 03-integrations 03-integrations label Aug 19, 2026
@antonyprasad-db

Copy link
Copy Markdown
Author

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:

BEFORE (currently on main):
  print(f"[STEP 3] Entra JWT | email={claims.get(\\"email\\")} name={claims.get(\\"name\\")}")
  -> SyntaxError: f-string expression part cannot include a backslash

AFTER (this PR):
  print(f"[STEP 3] Entra JWT | email={claims.get('email')} name={claims.get('name')}")
  -> compiles

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 03-integrations/data-platforms/ — just point me at them.

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

Labels

03-integrations 03-integrations

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant