W9007 reports duplicate values for arrays of distinct Fn::GetStackOutput values. This is the same class of problem as #52 (fixed for Fn::ImportValue in #83), but the fix was never extended to Fn::GetStackOutput.
Snippet:
{
"Resources": {
"ALB": {
"Type": "AWS::ElasticLoadBalancingV2::LoadBalancer",
"Properties": {
"Type": "application",
"Subnets": [
{ "Fn::GetStackOutput": { "StackName": "VpcStack", "Region": "ap-northeast-1", "OutputName": "PublishOutputRefVpcPublicSubnet1Subnet5C2D37C4F5EE3F2D" } },
{ "Fn::GetStackOutput": { "StackName": "VpcStack", "Region": "ap-northeast-1", "OutputName": "PublishOutputRefVpcPublicSubnet2Subnet691E08A328131BFA" } },
{ "Fn::GetStackOutput": { "StackName": "VpcStack", "Region": "ap-northeast-1", "OutputName": "PublishOutputRefVpcPublicSubnet3SubnetBE12F0B6AC1253B4" } }
]
}
}
}
}
Warning:
WARNING Subnets: Array property 'Subnets' contains duplicate values (CloudFormation Validate)
AlbStack/ALB/Resource (ALBAEE750D2) aws-cdk-lib.aws_elasticloadbalancingv2.CfnLoadBalancer
Acknowledge with 'CloudFormation-Validate::W9007'
The three entries reference different outputs, so they are distinct unknowable deploy-time values.
Scope
I probed RegoEngine.validateDetailed() (engine 1.5.1, as bundled in aws-cdk-lib 2.262.1) with the same resource shape, varying only the Subnets entries:
Subnets contents |
W9007 |
distinct Fn::GetStackOutput (different OutputName) |
fires |
distinct Fn::GetStackOutput (different StackName) |
fires |
distinct Fn::ImportValue |
clean |
distinct Ref |
clean |
distinct Fn::GetAtt |
clean |
| distinct literals |
clean |
| duplicate literals |
fires (correct) |
one Fn::GetStackOutput + one literal |
clean |
So the misfire is exclusive to Fn::GetStackOutput, and even a differing StackName is not enough to make two values distinct.
Cause
In src/template-model/src/resolver.rs on main, the ImportValue arm folds the export name into the resolved value's reason, which is what keeps two different imports distinct:
IntrinsicFn::ImportValue(arg) => {
let reason = match self.resolve_node(*arg) {
ResolvedValue::Concrete { value } => match value.as_str() {
Some(export) => format!("cross-stack import: {export}"),
None => "cross-stack import".into(),
},
_ => "cross-stack import".into(),
};
ResolvedValue::TypedDynamic { reason, param_type: PARAM_TYPE_STRING.into() }
}
The GetStackOutput arm resolves its arguments only to maintain current_path, discards the results, and returns a constant reason:
IntrinsicFn::GetStackOutput(args) => {
let saved = self.current_path.clone();
for (key, arg) in args {
self.current_path = format!("{}.{}", saved, key);
let _ = self.resolve_node(*arg);
}
self.current_path = saved;
ResolvedValue::Dynamic { reason: "cross-stack output".into() }
}
StackName, Region, and OutputName never reach the resolved value, so every Fn::GetStackOutput in a template resolves identically and W9007's duplicate detection sees N identical entries.
Suggested fix
Mirror the ImportValue arm: carry the distinguishing detail (StackName + Region + OutputName) into the resolved value's reason when the arguments resolve to concrete values, and consider returning TypedDynamic with PARAM_TYPE_STRING for parity.
Impact
CDK renders cross-stack references as Fn::GetStackOutput when the @aws-cdk/core:defaultCrossStackReferences feature flag is set to weak, so this fires on common patterns whose subnet lists come from another stack — ALB Subnets, AWS::RDS::DBSubnetGroup, and AWS::ElastiCache::SubnetGroup. Reported downstream as aws/aws-cdk#38424.
Engine version tested: 1.5.1 (bundled in aws-cdk-lib 2.262.1). The code above is still present on main as of 2026-07-28.
W9007 reports duplicate values for arrays of distinct
Fn::GetStackOutputvalues. This is the same class of problem as #52 (fixed forFn::ImportValuein #83), but the fix was never extended toFn::GetStackOutput.Snippet:
{ "Resources": { "ALB": { "Type": "AWS::ElasticLoadBalancingV2::LoadBalancer", "Properties": { "Type": "application", "Subnets": [ { "Fn::GetStackOutput": { "StackName": "VpcStack", "Region": "ap-northeast-1", "OutputName": "PublishOutputRefVpcPublicSubnet1Subnet5C2D37C4F5EE3F2D" } }, { "Fn::GetStackOutput": { "StackName": "VpcStack", "Region": "ap-northeast-1", "OutputName": "PublishOutputRefVpcPublicSubnet2Subnet691E08A328131BFA" } }, { "Fn::GetStackOutput": { "StackName": "VpcStack", "Region": "ap-northeast-1", "OutputName": "PublishOutputRefVpcPublicSubnet3SubnetBE12F0B6AC1253B4" } } ] } } } }Warning:
The three entries reference different outputs, so they are distinct unknowable deploy-time values.
Scope
I probed
RegoEngine.validateDetailed()(engine 1.5.1, as bundled in aws-cdk-lib 2.262.1) with the same resource shape, varying only theSubnetsentries:SubnetscontentsFn::GetStackOutput(differentOutputName)Fn::GetStackOutput(differentStackName)Fn::ImportValueRefFn::GetAttFn::GetStackOutput+ one literalSo the misfire is exclusive to
Fn::GetStackOutput, and even a differingStackNameis not enough to make two values distinct.Cause
In
src/template-model/src/resolver.rsonmain, theImportValuearm folds the export name into the resolved value'sreason, which is what keeps two different imports distinct:The
GetStackOutputarm resolves its arguments only to maintaincurrent_path, discards the results, and returns a constant reason:StackName,Region, andOutputNamenever reach the resolved value, so everyFn::GetStackOutputin a template resolves identically and W9007's duplicate detection sees N identical entries.Suggested fix
Mirror the
ImportValuearm: carry the distinguishing detail (StackName+Region+OutputName) into the resolved value'sreasonwhen the arguments resolve to concrete values, and consider returningTypedDynamicwithPARAM_TYPE_STRINGfor parity.Impact
CDK renders cross-stack references as
Fn::GetStackOutputwhen the@aws-cdk/core:defaultCrossStackReferencesfeature flag is set toweak, so this fires on common patterns whose subnet lists come from another stack — ALBSubnets,AWS::RDS::DBSubnetGroup, andAWS::ElastiCache::SubnetGroup. Reported downstream as aws/aws-cdk#38424.Engine version tested: 1.5.1 (bundled in aws-cdk-lib 2.262.1). The code above is still present on
mainas of 2026-07-28.