cdklabs / cdklabs/cdk-stacksets
CannotFindAsset error when StackSetStack is nested inside a CDK Stage
- Dominant language
- TypeScript
- Stars
- 118
- Forks
- 25
- Avg merge
- 33m
- Merged PRs (30d)
- 4
Description
## Description
When a `StackSetStack` is placed inside a CDK `Stage` (not directly under the root `App`), `cdk synth` fails with a `CannotFindAsset` error. This happens because `StackSetStackSynthesizer.addFileAsset()` constructs asset paths incorrectly when the stack is nested inside a Stage.
## Root Cause
In `stackset-stack.ts`, the `addFileAsset` method resolves asset paths like this:
```ts
const outdir = App.of(this.boundStack)?.outdir ?? 'cdk.out';
const assetPath = `${outdir}/${asset.fileName}`;
```
When a `StackSetStack` is inside a `Stage`, `asset.fileName` is **relative to the Stage's assembly directory**, not the App's outdir. For example, `asset.fileName` may be `../asset.` (with a `../` prefix) because the asset is staged in the parent assembly directory while the Stage's assembly is a subdirectory.
Since `App.of().outdir` returns the root outdir (`cdk.out`), the concatenation produces:
```
cdk.out/../asset. → asset. ✗ (resolves outside the output directory)
```
The asset file actually exists at `cdk.out/asset.`, but the resolved path escapes the outdir.
## Reproduction
```ts
import { App, Stage, Stack } from 'aws-cdk-lib';
import * as s3 from 'aws-cdk-lib/aws-s3';
import { AwsCustomResource, AwsCustomResourcePolicy, PhysicalResourceId } from 'aws-cdk-lib/custom-resources';
import { StackSetStack } from 'cdk-stacksets';
const app = new App();
const stage = new Stage(app, 'my-stage');
const parentStack = new Stack(stage, 'parent-stack', {
env: { account: '123456789012', region: 'us-east-1' },
});
const bucket = new s3.Bucket(parentStack, 'asset-bucket');
const stackSetStack = new StackSetStack(parentStack, 'ss-stack', {
assetBuckets: [bucket],
assetBucketPrefix: 'my-assets',
});
new AwsCustomResource(stackSetStack, 'CustomResource', {
resourceType: 'Custom::Example',
onCreate: {
service: 'STS',
action: 'getCallerIdentity',
physicalResourceId: PhysicalResourceId.of('example'),
},
policy: AwsCustomResourcePolicy.fromSdkCalls({
resources: AwsCustomResourcePolicy.ANY_RESOURCE,
}),
});
app.synth();
```
Running `cdk synth` produces:
```
«CannotFindAsset» Cannot find asset at /path/to/project/asset.
```
## Proposed Fix
Use `Stage.of()` instead of `App.of()` for outdir resolution, and use `path.resolve()` instead of string concatenation:
```diff
- const outdir = App.of(this.boundStack)?.outdir ?? 'cdk.out';
- const assetPath = `${outdir}/${asset.fileName}`;
+ const outdir = Stage.of(this.boundStack)?.outdir ?? App.of(this.boundStack)?.outdir ?? 'cdk.out';
+ const assetPath = path.resolve(outdir, asset.fileName);
```
`Stage.of()` returns the nearest enclosing Stage (which may be the App itself), so its `outdir` correctly matches the base directory that `asset.fileName` is relative to. `path.resolve()` then correctly normalizes any `../` segments.
## Environment
- **cdk-stacksets**: 0.0.152
- **aws-cdk-lib**: 2.248.0
- **constructs**: 10.6.0
- **Node.js**: 22.x
## Related
May be related to #258 which describes a similar `CannotFindAsset` error for bundled Lambda functions. That issue involves bundling-specific path mismatches, whereas this issue is specifically about Stage-nested stacks with non-bundled assets.
Contributor guide
Research direction
Start in stackset-stack.ts at StackSetStackSynthesizer.addFileAsset(), then run the provided Stage-nested reproduction with cdk synth. Verify that assets whose fileName contains ../ resolve to the existing asset under cdk.out and that synthesis completes without CannotFindAsset.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- aws, typescript
- Domain
- build-system
- Issue type
- Bug
- Difficulty
- 2/5
- Estimated time
- 1-3 hours
- Activity status
- Active
- Clarity
- Clearly specified
- Newbie friendliness
- 78/100