feat: make AWS::LanguageExtensions local processing opt-in via --language-extensions flag#9033
Conversation
Add language_extensions_enabled kwarg to SamLocalStackProvider.get_stacks with default=False to match pre-1.160.0 behavior. Forward to expand_language_extensions and recursive get_stacks calls for nested stacks.
…alidator Wire the --language-extensions flag from validate command through to SamTemplateValidator and expand_language_extensions. Validator takes a resolved bool (not Optional) since resolution happens at command boundary. Add language_extensions parameter to __init__, forward enabled kwarg to expand_language_extensions call, and update all test call sites.
…pection-path get_stacks calls
aws-sam-cli-bot
left a comment
There was a problem hiding this comment.
Code Review Results
Reviewed: 9ffa5ed..bdcd614
Files: 66
Comments: 1
Comments on lines outside the diff:
[samcli/commands/deploy/command.py:324] [BUG] The sam deploy command's inner PackageContext(...) invocation is not threaded with the new language_extensions flag, while the surrounding GuidedContext and DeployContext calls are. As a result, when a user runs sam deploy --language-extensions against a template that needs LE expansion, the package phase (which performs the artifact upload and walks/expands child stacks via artifact_exporter.Template) will silently run with language_extensions_enabled=False, while the deploy phase runs with it enabled.
Concrete consequences:
- Artifacts referenced inside Fn::ForEach blocks (including in nested stacks reached through artifact_exporter.Template's recursion) will not be uploaded to S3/ECR, because expand_language_extensions(...) returns the passthrough result and the dynamic artifact properties needed by Template.export() are never computed.
- The deployed CloudFormation template may reference local code paths or unresolved artifacts that the package step skipped.
- sam sync (which also chains Build → Package → Deploy) correctly passes language_extensions=language_extensions into both PackageContext and DeployContext (see samcli/commands/sync/command.py lines 373 and 410 in this PR), so the deploy command is the inconsistent one.
Fix: pass language_extensions=language_extensions to the PackageContext(...) call at this site, mirroring the sync command's wiring:
with PackageContext(
template_file=template_file,
...
parameter_overrides=context_param_overrides,
language_extensions=language_extensions,
) as package_context:
package_context.run()Without this change, the new --language-extensions flag is effectively half-applied for sam deploy, which can produce broken deployments for the very templates this flag was added to support.
aws-sam-cli-bot
left a comment
There was a problem hiding this comment.
Code Review Results
Reviewed: 9ffa5ed..ed0d4f9
Files: 66
Comments: 1
Comments on lines outside the diff:
[samcli/lib/sync/infra_sync_executor.py:609] [BUG] With the new opt-in semantics, _detect_foreach_code_changes can crash on templates that declare AWS::LanguageExtensions but where the user did not pass --language-extensions. The early-return guard only short-circuits when the template has no LE transform at all:
if not check_using_language_extension(current_template):
return
...
current_expanded = expand_language_extensions(
current_template,
parameter_values=current_params,
enabled=self._sync_context.language_extensions_enabled,
).expanded_templateWhen the transform is declared but enabled=False, expand_language_extensions now returns a passthrough (per the new if not enabled branch in sam_integration.py), so current_expanded still contains the unexpanded Fn::ForEach::* keys whose values are lists, not dicts. The subsequent loop then does:
for resource_id, resource_dict in current_resources.items():
if resource_id in original_regular_keys: # Fn::ForEach key is excluded from this set
continue
resource_type = resource_dict.get("Type") # AttributeError: 'list' object has no attribute 'get'This is the exact regression scenario the PR aims to support (template declares the transform, user opts out), and the call site in _auto_skip_infra_sync has no surrounding try/except, so the AttributeError propagates and breaks sam sync's auto-skip-infra path.
Add an early return for the disabled case, e.g.:
if not self._sync_context.language_extensions_enabled:
return
if not check_using_language_extension(current_template):
returnOr guard the loop body to skip is_foreach_key(resource_id) entries when expansion was a passthrough.
|
Re: PackageContext not threaded in deploy command (review 2026-05-20T17:51) Fixed in ed0d4f9 — |
|
Re: _detect_foreach_code_changes crash when LE disabled (review 2026-05-20T18:04) Fixed in 9f5934b — added early return when |
valerena
left a comment
There was a problem hiding this comment.
Just a couple of questions, but it looks good.
After #9033, language extensions are opt-in. The two ForEach zipfile tests added by this PR omitted the flag, causing SAM transform plugins to choke on unexpanded Fn::ForEach blocks.
PR #9033 made AWS::LanguageExtensions local processing opt-in. Two API changes broke three tests on this branch after the merge from develop: - expand_language_extensions() now requires a keyword-only `enabled` arg - PackageContext reads self._language_extensions_enabled, set in __init__ Pass enabled=True to expand_language_extensions in the two artifact-exporter tests, and set _language_extensions_enabled on the PackageContext stub that bypasses __init__ via __new__. All three tests exercise templates with Transform: AWS::LanguageExtensions, so enabling LE is the intended behavior.
Summary
AWS::LanguageExtensionsprocessing opt-in (off by default), restoring pre-1.160.0 behavior for templates that declare the transform but don't need local expansion--language-extensions / --no-language-extensionsCLI flag to all 8 affected commands: build, package, deploy, sync, validate, local invoke, local start-api, local start-lambdaSAM_CLI_ENABLE_LANGUAGE_EXTENSIONS=1env var as fallback (flag wins)samconfig.tomlpersistence vialanguage_extensions = trueMotivation
SAM CLI 1.160.0 enabled local processing of
AWS::LanguageExtensionsimplicitly when the transform was declared. This caused regressions (#9004, #9005, #9027, #9029) for templates that declared the transform for non-Fn::ForEachreasons or as a forward-compat placeholder. Making it opt-in lets customers adopt at their own pace.Changes
expand_language_extensions()now requiresenabled: boolas a keyword-only arg — unwired callers fail with TypeErrorresolve_language_extensions_enabled(flag_value: Optional[bool]) -> boolresolver (flag → env var → False)@language_extensions_optionClick decorator shared across all commandsSamLocalStackProvider.get_stacks(..., language_extensions_enabled=False)kwarg threaded through recursionTemplateclass in artifact_exporter wired for nested-stack recursionFalse--language-extensionsfor ForEach templatesTest plan
pytest tests/unit/ -q -W ignore::pytest.PytestUnknownMarkWarning)--language-extensionsparameter registered on all 8 commands (verified programmatically)make formatcleanmake lintshows zero errorstest_build_without_flag_does_not_expand_foreachverifies opt-out behavior--language-extensionsflag