cdklabs / cdklabs/cdk-stacksets

CannotFindAsset error when StackSetStack is nested inside a CDK Stage

Open Beginner friendly
#886 4 comments 3 reactions 0 assignees View on GitHub
bug effort/medium p2
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

Open the contributing 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

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.