sam sync/local operations silently target the wrong physical resource when --resource-id collides across sibling nested stacks
- Dominant language
- Python
- Stars
- 6.7k
- Forks
- 1.2k
- Avg merge
- 1d 10h
- Merged PRs (30d)
- 52
Description
### Description
`get_resource_by_id()` in `samcli/lib/providers/provider.py` is the core lookup used by `sam sync --resource-id ` (and the underlying file-watch/code-trigger machinery used by `sam sync --watch`) to resolve a bare, unqualified resource logical ID to the deployed resource it should operate on.
When a bare ID (no `Stack/` path prefix) is given and it matches a resource with the same logical ID in **more than one nested stack**, with no match in the root stack to prefer, the function silently returns whichever stack happens to come first in its internal stack list — with no error, warning, or disambiguation prompt.
```python
def get_resource_by_id(
stacks: List[Stack], identifier: ResourceIdentifier, explicit_nested: bool = False
) -> Optional[Dict[str, Any]]:
...
search_all_stacks = not identifier.stack_path and not explicit_nested
for stack in stacks:
if stack.stack_path == identifier.stack_path or search_all_stacks:
found_resource = None
for logical_id, resource in stack.resources.items():
...
if resource_id == identifier.resource_iac_id or (...):
found_resource = resource
break
if found_resource:
return cast(Dict[str, Any], found_resource) # <-- first match wins, silently
return None
```
### Reproduction
A SAM template using nested stacks where two different nested `AWS::CloudFormation::Stack` resources each contain a resource with the same logical ID (e.g. a shared/reusable nested-stack module deployed twice, or two independently-authored nested stacks that happen to reuse a common name like `Function1`) — no root-level resource with that ID.
```
sam sync --watch --resource-id Function1
```
### Expected behavior
Since the ID is genuinely ambiguous (matches deployed resources in two different nested stacks), the CLI should either ask the user to disambiguate or fail with a clear error telling them to qualify the ID with the full stack path (e.g. `NestedStackA/Function1`), which `ResourceIdentifier` already supports as valid input syntax.
### Actual behavior
The lookup silently returns whichever nested stack's resource happens to be first in the internal (construction-order-dependent) stack list. Since `sam sync` bypasses full CloudFormation deployment and pushes code directly to the resolved physical resource (e.g. via `UpdateFunctionCode`), this means the wrong live, deployed Lambda function (or other resource) can silently receive the local code changes intended for a different one — with no error printed and no indication anything went wrong.
Note: this is distinct from, and does not affect, the existing (and intentional/tested) precedence where a root-stack resource always wins over a same-named nested-stack resource for a bare ID — that behavior is correct and unaffected by this report. The bug is specifically the nested-vs-nested collision case, which has no precedence rule and no test coverage today.
### Fix
PR incoming — raises a clear, actionable error when a bare ID matches more than one nested stack with no root-stack match to prefer, instead of silently picking the first one.
Contributor guide
Research direction
Start in samcli/lib/providers/provider.py at get_resource_by_id() and reproduce the bare-ID lookup with two nested stacks containing the same resource logical ID. The change is complete when an ambiguous nested match raises a clear error requiring a qualified stack path, while root-stack precedence remains unchanged; add coverage for the collision case and existing precedence behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, python
- Domain
- cli, cloud
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 45/100