feat(project): implement project build - #1970

Merged
tejaskash merged 3 commits into
refactorfrom
feat/project-build-synth
Aug 13, 2026
Merged

feat(project): implement project build#1970
tejaskash merged 3 commits into
refactorfrom
feat/project-build-synth

Conversation

@notgitika

@notgitikanotgitika commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

agentcore project build compiles the project's CDK app and synthesizes its CloudFormation templates, so the deployable artifacts exist before deploy.

The generated package.json defines cdk as "npm run build && cdk", so that one command covers both compile and synthesis — no need to construct a node_modules/.bin/cdk path or branch on win32.

Synthesis is credential-free, by design

An earlier draft of this called STS GetCallerIdentity and wrote agentcore/aws-targets.json as a side effect of build. That isn't needed: each stack takes an explicit env: { account, region } from aws-targets.json and nothing in the app calls fromLookup, so synth never talks to AWS. The account is only load-bearing at deploy.

So build stays offline and does not write config. When aws-targets.json is still [], the CDK app raises its own error, which already says what to do:

AgentCore CDK synthesis failed: No deployment targets configured.
Please define targets in agentcore/aws-targets.json

wrapped by ProcessFailedError with Fix the issue and run 'cd .../agentcore/cdk && npm run cdk -- synth --quiet' to retry.

Deliberately not guessing the account also avoids a trap the earlier draft had: it wrote region unvalidated, but AgentCoreRegionSchema is an enum of 9 regions. --region eu-west-2 would have written a region synth then rejects, and because the CLI's own re-read used z.array(z.unknown()) it would see a non-empty list and never repair the file.

withProject goes into service

src/middleware/withProject.tsx already existed but was never exported and never used. It now resolves the enclosing project and hands it to the handler via ProjectKey, so the handler body is just:

constproject=ctx.require(ProjectKey);forawait(consteventofconfig.projectManager.build(project)){config.io.stderr.write(`${event.message}\n`);}

Two changes to it:

  • cwd is optional and resolved per invocation (config.cwd ?? process.cwd()) rather than captured at wiring time, so the directory the user actually ran in is the one searched.
  • Failure is a ProjectStateError naming the path searched and the file looked for, and pointing at agentcore project create.

It wraps onlybuild, not the whole router — create refuses to nest inside an existing project, so requiring one would break it.

Second commit: runtimeVersion on the CodeZip template

Included here rather than as a follow-up, so build is never merged in a state where it fails on the config the CLI itself just scaffolded.

The CDK construct library's schema refines build !== 'Container' && !runtimeVersion into runtimeVersion is required for CodeZip builds, and our hello-world-python template didn't set it. Five lines: runtimeVersion: "PYTHON_3_14" on the template runtime plus the matching test assertion. Container builds take their version from the image, so the container template is untouched.

A follow-up will mirror that refinement into our own AgentEnvSpecSchema, which currently marks runtimeVersion plain .optional() — that way the CLI catches this class of error itself instead of surfacing CDK's zod path error.

Verification

Beyond unit tests, I ran this against a real scaffolded project with a real npm install:

  1. Empty aws-targets.json → the CDK app's "No deployment targets configured" error, wrapped with the retry hint.
  2. One target filled in, with AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN blanked and AWS_PROFILE pointed at a nonexistent profile → Built project 'Demo', emitting cdk.out/AgentCore-Demo-dev.template.json. Confirms synth needs no credentials.
  3. Stripping runtimeVersion back out of that same project → reproduces the CodeZip failure, which is what motivated the second commit.

Unit coverage: the exact synth command and cwd, the missing-node_modules error, subprocess failure propagation, the progress event, resolution from a nested directory, and failing outside a project.

  • bun test — 1061 pass, 0 fail
  • tsc --noEmit clean, oxlint clean

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 11, 2026
@codecov-commenter

codecov-commenter commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.82%. Comparing base (0e33398) to head (05c244e).

Additional details and impacted files
@@ Coverage Diff @@
## refactor #1970 +/- ##
============================================
+ Coverage 96.81% 96.82% +0.01% ============================================
Files 329 329 Lines 18754 18794 +40 ============================================
+ Hits 18157 18198 +41 + Misses 597 596 -1 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.
yield { message: "Synthesizing CloudFormation templates" };
await this.run(["npm", "run", "cdk", "--", "synth", "--quiet"], cdkDir);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we not using @aws-cdk/toolkit-lib anymore?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, I went with a subprocess to keep the PR small. using toolkit-lib means porting the wrapper and the schema pinning first, and deploy is what actually needs those. So I will introduce it when I introduce deploy. It should be easy to switch later, build() already yields events so it's a 2-way door decision

return project;
}

public async *build(project: Project): AsyncGenerator<ProjectEvent, void> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this build impl handle alternative project backends like terraform and SDK if we chose to implement those in the future?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the commit I just pushed, we use the managedBy field in agentcore.json

Comment on lines +14 to +24
handle: async (ctx) => {
// withProject has already resolved the enclosing project.
const project = ctx.require(ProjectKey);

// Progress goes to stderr, keeping stdout for machine output. Subprocess
// output goes to the debug log; on failure ProcessFailedError carries it.
for await (const event of config.projectManager.build(project)) {
config.io.stderr.write(`${event.message}\n`);
}

config.io.stderr.write(`Built project '${project.name}'\n`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that this is kept clean. It doesn't need to be aware of the build backend or any of the other steps.

Comment on lines +132 to +138
await this.checkTool("npm", "Install Node.js: https://nodejs.org/");

// The generated package.json defines `cdk` as "npm run build && cdk", so this
// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.
yield { message: "Synthesizing CloudFormation templates" };
await this.run(["npm", "run", "cdk", "--", "synth", "--quiet"], cdkDir);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we had discussed that build would:

  • Validate Schemas
  • Generate ZIP artifacts for CodeZIP Agents
  • Run CDK Synth

I'm not sure if Generate ZIP artifacts for CodeZIP Agents is happening here or if we decided to move that to a different PR.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three are happening. schema validation is up front, withProject resolves the project through ProjectSpecSchema, so build can't run on an invalid agentcore.json (#1972 makes that catch the CodeZip runtimeVersion rule and name the field).

the ZIP is generated by synth: the construct library's packager runs uv pip install and stages the asset, so cdk.out gets the zip and the template's CodeConfiguration.Code.S3 points at it.

I validated and checked a real synth to be sure. so there's no separate ZIP step to write, doing our own would duplicate the packager deploy relies on. LMK what you think

Base automatically changed from chore/project-event-shape to refactorAugust 11, 2026 19:03
@notgitika
notgitikaforce-pushed the feat/project-build-synth branch from 305bf56 to 639227fCompareAugust 11, 2026 19:03
@tejaskash
tejaskashforce-pushed the feat/project-build-synth branch from 78097a8 to 0b2da99CompareAugust 12, 2026 20:24
tejaskash
tejaskash previously approved these changes Aug 12, 2026
aidandaly24
aidandaly24 previously approved these changes Aug 12, 2026

@aidandaly24aidandaly24 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me one really minor comment:


// The generated package.json defines `cdk` as "npm run build && cdk", so this
// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking and fine as a follow-up: synthesis succeeds without credentials, but AWS_PROFILE currently causes the pinned ConfigIO.readAWSDeploymentTargets() to call STS even when every target already has an account. I confirmed this by redirecting STS locally. The build still succeeded, but made six GetCallerIdentity attempts and inherited the retry latency. Could we avoid that fallback when account values are already present so build is fully offline?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

creating a follow up issue for this. thanks for catching this!

`agentcore project build` compiles the project's CDK app and synthesizes its
CloudFormation templates, so the deployable artifacts exist before deploy.
Synthesis runs offline: each stack's environment comes from aws-targets.json,
so no credentials are needed. An empty targets file makes the CDK app fail with
its own actionable message rather than the CLI guessing an account.
The generated package.json defines `cdk` as "npm run build && cdk", so one
`npm run cdk -- synth --quiet` covers both compile and synthesis.
Also puts withProject to work for the first time: it resolves the enclosing
project and hands it to the handler through ProjectKey, and it wraps only build
so that `create` (which refuses to nest inside a project) stays unaffected. Its
cwd is now resolved per invocation instead of at wiring time, so the directory
the user actually ran in is the one searched.
The CDK construct library rejects a CodeZip runtime that declares no
runtimeVersion ("runtimeVersion is required for CodeZip builds"), and it is the
field that selects the packager. Without it, synthesizing a freshly created
python project fails on its own scaffolded config.
Container builds take their version from the image, so the container template
is unaffected.
agentcore.json already records `managedBy` (CDK is the only value today), but
nothing read it: build() hardcoded the CDK path, so adding a terraform or
no-IaC backend later would have meant editing that path instead of adding
alongside it.
Carry managedBy on Project and switch on it in build(), delegating the CDK
work to a private buildWithCdk(). The default arm assigns to `never`, so a new
ManagedBySchema member fails to compile until it has an arm here.

@tejaskashtejaskash left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@tejaskash
tejaskash merged commit 25d59fb into refactorAug 13, 2026
13 checks passed
@tejaskash
tejaskash deleted the feat/project-build-synth branch August 13, 2026 16:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@notgitika@codecov-commenter@tejaskash@aidandaly24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
Skip to content

feat(project): implement project build - #1970

Merged
tejaskash merged 3 commits into
refactorfrom
feat/project-build-synth
Aug 13, 2026
Merged

feat(project): implement project build#1970
tejaskash merged 3 commits into
refactorfrom
feat/project-build-synth

Conversation

@notgitika

@notgitikanotgitika commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

agentcore project build compiles the project's CDK app and synthesizes its CloudFormation templates, so the deployable artifacts exist before deploy.

The generated package.json defines cdk as "npm run build && cdk", so that one command covers both compile and synthesis — no need to construct a node_modules/.bin/cdk path or branch on win32.

Synthesis is credential-free, by design

An earlier draft of this called STS GetCallerIdentity and wrote agentcore/aws-targets.json as a side effect of build. That isn't needed: each stack takes an explicit env: { account, region } from aws-targets.json and nothing in the app calls fromLookup, so synth never talks to AWS. The account is only load-bearing at deploy.

So build stays offline and does not write config. When aws-targets.json is still [], the CDK app raises its own error, which already says what to do:

AgentCore CDK synthesis failed: No deployment targets configured.
Please define targets in agentcore/aws-targets.json

wrapped by ProcessFailedError with Fix the issue and run 'cd .../agentcore/cdk && npm run cdk -- synth --quiet' to retry.

Deliberately not guessing the account also avoids a trap the earlier draft had: it wrote region unvalidated, but AgentCoreRegionSchema is an enum of 9 regions. --region eu-west-2 would have written a region synth then rejects, and because the CLI's own re-read used z.array(z.unknown()) it would see a non-empty list and never repair the file.

withProject goes into service

src/middleware/withProject.tsx already existed but was never exported and never used. It now resolves the enclosing project and hands it to the handler via ProjectKey, so the handler body is just:

constproject=ctx.require(ProjectKey);forawait(consteventofconfig.projectManager.build(project)){config.io.stderr.write(`${event.message}\n`);}

Two changes to it:

  • cwd is optional and resolved per invocation (config.cwd ?? process.cwd()) rather than captured at wiring time, so the directory the user actually ran in is the one searched.
  • Failure is a ProjectStateError naming the path searched and the file looked for, and pointing at agentcore project create.

It wraps onlybuild, not the whole router — create refuses to nest inside an existing project, so requiring one would break it.

Second commit: runtimeVersion on the CodeZip template

Included here rather than as a follow-up, so build is never merged in a state where it fails on the config the CLI itself just scaffolded.

The CDK construct library's schema refines build !== 'Container' && !runtimeVersion into runtimeVersion is required for CodeZip builds, and our hello-world-python template didn't set it. Five lines: runtimeVersion: "PYTHON_3_14" on the template runtime plus the matching test assertion. Container builds take their version from the image, so the container template is untouched.

A follow-up will mirror that refinement into our own AgentEnvSpecSchema, which currently marks runtimeVersion plain .optional() — that way the CLI catches this class of error itself instead of surfacing CDK's zod path error.

Verification

Beyond unit tests, I ran this against a real scaffolded project with a real npm install:

  1. Empty aws-targets.json → the CDK app's "No deployment targets configured" error, wrapped with the retry hint.
  2. One target filled in, with AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN blanked and AWS_PROFILE pointed at a nonexistent profile → Built project 'Demo', emitting cdk.out/AgentCore-Demo-dev.template.json. Confirms synth needs no credentials.
  3. Stripping runtimeVersion back out of that same project → reproduces the CodeZip failure, which is what motivated the second commit.

Unit coverage: the exact synth command and cwd, the missing-node_modules error, subprocess failure propagation, the progress event, resolution from a nested directory, and failing outside a project.

  • bun test — 1061 pass, 0 fail
  • tsc --noEmit clean, oxlint clean

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 11, 2026
@codecov-commenter

codecov-commenter commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.82%. Comparing base (0e33398) to head (05c244e).

Additional details and impacted files
@@ Coverage Diff @@
## refactor #1970 +/- ##
============================================
+ Coverage 96.81% 96.82% +0.01% ============================================
Files 329 329 Lines 18754 18794 +40 ============================================
+ Hits 18157 18198 +41 + Misses 597 596 -1 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.
yield { message: "Synthesizing CloudFormation templates" };
await this.run(["npm", "run", "cdk", "--", "synth", "--quiet"], cdkDir);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we not using @aws-cdk/toolkit-lib anymore?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, I went with a subprocess to keep the PR small. using toolkit-lib means porting the wrapper and the schema pinning first, and deploy is what actually needs those. So I will introduce it when I introduce deploy. It should be easy to switch later, build() already yields events so it's a 2-way door decision

return project;
}

public async *build(project: Project): AsyncGenerator<ProjectEvent, void> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this build impl handle alternative project backends like terraform and SDK if we chose to implement those in the future?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the commit I just pushed, we use the managedBy field in agentcore.json

Comment on lines +14 to +24
handle: async (ctx) => {
// withProject has already resolved the enclosing project.
const project = ctx.require(ProjectKey);

// Progress goes to stderr, keeping stdout for machine output. Subprocess
// output goes to the debug log; on failure ProcessFailedError carries it.
for await (const event of config.projectManager.build(project)) {
config.io.stderr.write(`${event.message}\n`);
}

config.io.stderr.write(`Built project '${project.name}'\n`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that this is kept clean. It doesn't need to be aware of the build backend or any of the other steps.

Comment on lines +132 to +138
await this.checkTool("npm", "Install Node.js: https://nodejs.org/");

// The generated package.json defines `cdk` as "npm run build && cdk", so this
// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.
yield { message: "Synthesizing CloudFormation templates" };
await this.run(["npm", "run", "cdk", "--", "synth", "--quiet"], cdkDir);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we had discussed that build would:

  • Validate Schemas
  • Generate ZIP artifacts for CodeZIP Agents
  • Run CDK Synth

I'm not sure if Generate ZIP artifacts for CodeZIP Agents is happening here or if we decided to move that to a different PR.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three are happening. schema validation is up front, withProject resolves the project through ProjectSpecSchema, so build can't run on an invalid agentcore.json (#1972 makes that catch the CodeZip runtimeVersion rule and name the field).

the ZIP is generated by synth: the construct library's packager runs uv pip install and stages the asset, so cdk.out gets the zip and the template's CodeConfiguration.Code.S3 points at it.

I validated and checked a real synth to be sure. so there's no separate ZIP step to write, doing our own would duplicate the packager deploy relies on. LMK what you think

Base automatically changed from chore/project-event-shape to refactorAugust 11, 2026 19:03
@notgitika
notgitikaforce-pushed the feat/project-build-synth branch from 305bf56 to 639227fCompareAugust 11, 2026 19:03
@tejaskash
tejaskashforce-pushed the feat/project-build-synth branch from 78097a8 to 0b2da99CompareAugust 12, 2026 20:24
tejaskash
tejaskash previously approved these changes Aug 12, 2026
aidandaly24
aidandaly24 previously approved these changes Aug 12, 2026

@aidandaly24aidandaly24 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me one really minor comment:


// The generated package.json defines `cdk` as "npm run build && cdk", so this
// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking and fine as a follow-up: synthesis succeeds without credentials, but AWS_PROFILE currently causes the pinned ConfigIO.readAWSDeploymentTargets() to call STS even when every target already has an account. I confirmed this by redirecting STS locally. The build still succeeded, but made six GetCallerIdentity attempts and inherited the retry latency. Could we avoid that fallback when account values are already present so build is fully offline?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

creating a follow up issue for this. thanks for catching this!

`agentcore project build` compiles the project's CDK app and synthesizes its
CloudFormation templates, so the deployable artifacts exist before deploy.
Synthesis runs offline: each stack's environment comes from aws-targets.json,
so no credentials are needed. An empty targets file makes the CDK app fail with
its own actionable message rather than the CLI guessing an account.
The generated package.json defines `cdk` as "npm run build && cdk", so one
`npm run cdk -- synth --quiet` covers both compile and synthesis.
Also puts withProject to work for the first time: it resolves the enclosing
project and hands it to the handler through ProjectKey, and it wraps only build
so that `create` (which refuses to nest inside a project) stays unaffected. Its
cwd is now resolved per invocation instead of at wiring time, so the directory
the user actually ran in is the one searched.
The CDK construct library rejects a CodeZip runtime that declares no
runtimeVersion ("runtimeVersion is required for CodeZip builds"), and it is the
field that selects the packager. Without it, synthesizing a freshly created
python project fails on its own scaffolded config.
Container builds take their version from the image, so the container template
is unaffected.
agentcore.json already records `managedBy` (CDK is the only value today), but
nothing read it: build() hardcoded the CDK path, so adding a terraform or
no-IaC backend later would have meant editing that path instead of adding
alongside it.
Carry managedBy on Project and switch on it in build(), delegating the CDK
work to a private buildWithCdk(). The default arm assigns to `never`, so a new
ManagedBySchema member fails to compile until it has an arm here.

@tejaskashtejaskash left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@tejaskash
tejaskash merged commit 25d59fb into refactorAug 13, 2026
13 checks passed
@tejaskash
tejaskash deleted the feat/project-build-synth branch August 13, 2026 16:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@notgitika@codecov-commenter@tejaskash@aidandaly24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(project): implement project build - #1970

Merged
tejaskash merged 3 commits into
refactorfrom
feat/project-build-synth
Aug 13, 2026
Merged

feat(project): implement project build#1970
tejaskash merged 3 commits into
refactorfrom
feat/project-build-synth

Conversation

@notgitika

@notgitikanotgitika commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

agentcore project build compiles the project's CDK app and synthesizes its CloudFormation templates, so the deployable artifacts exist before deploy.

The generated package.json defines cdk as "npm run build && cdk", so that one command covers both compile and synthesis — no need to construct a node_modules/.bin/cdk path or branch on win32.

Synthesis is credential-free, by design

An earlier draft of this called STS GetCallerIdentity and wrote agentcore/aws-targets.json as a side effect of build. That isn't needed: each stack takes an explicit env: { account, region } from aws-targets.json and nothing in the app calls fromLookup, so synth never talks to AWS. The account is only load-bearing at deploy.

So build stays offline and does not write config. When aws-targets.json is still [], the CDK app raises its own error, which already says what to do:

AgentCore CDK synthesis failed: No deployment targets configured.
Please define targets in agentcore/aws-targets.json

wrapped by ProcessFailedError with Fix the issue and run 'cd .../agentcore/cdk && npm run cdk -- synth --quiet' to retry.

Deliberately not guessing the account also avoids a trap the earlier draft had: it wrote region unvalidated, but AgentCoreRegionSchema is an enum of 9 regions. --region eu-west-2 would have written a region synth then rejects, and because the CLI's own re-read used z.array(z.unknown()) it would see a non-empty list and never repair the file.

withProject goes into service

src/middleware/withProject.tsx already existed but was never exported and never used. It now resolves the enclosing project and hands it to the handler via ProjectKey, so the handler body is just:

constproject=ctx.require(ProjectKey);forawait(consteventofconfig.projectManager.build(project)){config.io.stderr.write(`${event.message}\n`);}

Two changes to it:

  • cwd is optional and resolved per invocation (config.cwd ?? process.cwd()) rather than captured at wiring time, so the directory the user actually ran in is the one searched.
  • Failure is a ProjectStateError naming the path searched and the file looked for, and pointing at agentcore project create.

It wraps onlybuild, not the whole router — create refuses to nest inside an existing project, so requiring one would break it.

Second commit: runtimeVersion on the CodeZip template

Included here rather than as a follow-up, so build is never merged in a state where it fails on the config the CLI itself just scaffolded.

The CDK construct library's schema refines build !== 'Container' && !runtimeVersion into runtimeVersion is required for CodeZip builds, and our hello-world-python template didn't set it. Five lines: runtimeVersion: "PYTHON_3_14" on the template runtime plus the matching test assertion. Container builds take their version from the image, so the container template is untouched.

A follow-up will mirror that refinement into our own AgentEnvSpecSchema, which currently marks runtimeVersion plain .optional() — that way the CLI catches this class of error itself instead of surfacing CDK's zod path error.

Verification

Beyond unit tests, I ran this against a real scaffolded project with a real npm install:

  1. Empty aws-targets.json → the CDK app's "No deployment targets configured" error, wrapped with the retry hint.
  2. One target filled in, with AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN blanked and AWS_PROFILE pointed at a nonexistent profile → Built project 'Demo', emitting cdk.out/AgentCore-Demo-dev.template.json. Confirms synth needs no credentials.
  3. Stripping runtimeVersion back out of that same project → reproduces the CodeZip failure, which is what motivated the second commit.

Unit coverage: the exact synth command and cwd, the missing-node_modules error, subprocess failure propagation, the progress event, resolution from a nested directory, and failing outside a project.

  • bun test — 1061 pass, 0 fail
  • tsc --noEmit clean, oxlint clean

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 11, 2026
@codecov-commenter

codecov-commenter commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.82%. Comparing base (0e33398) to head (05c244e).

Additional details and impacted files
@@ Coverage Diff @@
## refactor #1970 +/- ##
============================================
+ Coverage 96.81% 96.82% +0.01% ============================================
Files 329 329 Lines 18754 18794 +40 ============================================
+ Hits 18157 18198 +41 + Misses 597 596 -1 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.
yield { message: "Synthesizing CloudFormation templates" };
await this.run(["npm", "run", "cdk", "--", "synth", "--quiet"], cdkDir);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we not using @aws-cdk/toolkit-lib anymore?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, I went with a subprocess to keep the PR small. using toolkit-lib means porting the wrapper and the schema pinning first, and deploy is what actually needs those. So I will introduce it when I introduce deploy. It should be easy to switch later, build() already yields events so it's a 2-way door decision

return project;
}

public async *build(project: Project): AsyncGenerator<ProjectEvent, void> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this build impl handle alternative project backends like terraform and SDK if we chose to implement those in the future?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the commit I just pushed, we use the managedBy field in agentcore.json

Comment on lines +14 to +24
handle: async (ctx) => {
// withProject has already resolved the enclosing project.
const project = ctx.require(ProjectKey);

// Progress goes to stderr, keeping stdout for machine output. Subprocess
// output goes to the debug log; on failure ProcessFailedError carries it.
for await (const event of config.projectManager.build(project)) {
config.io.stderr.write(`${event.message}\n`);
}

config.io.stderr.write(`Built project '${project.name}'\n`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that this is kept clean. It doesn't need to be aware of the build backend or any of the other steps.

Comment on lines +132 to +138
await this.checkTool("npm", "Install Node.js: https://nodejs.org/");

// The generated package.json defines `cdk` as "npm run build && cdk", so this
// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.
yield { message: "Synthesizing CloudFormation templates" };
await this.run(["npm", "run", "cdk", "--", "synth", "--quiet"], cdkDir);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we had discussed that build would:

  • Validate Schemas
  • Generate ZIP artifacts for CodeZIP Agents
  • Run CDK Synth

I'm not sure if Generate ZIP artifacts for CodeZIP Agents is happening here or if we decided to move that to a different PR.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three are happening. schema validation is up front, withProject resolves the project through ProjectSpecSchema, so build can't run on an invalid agentcore.json (#1972 makes that catch the CodeZip runtimeVersion rule and name the field).

the ZIP is generated by synth: the construct library's packager runs uv pip install and stages the asset, so cdk.out gets the zip and the template's CodeConfiguration.Code.S3 points at it.

I validated and checked a real synth to be sure. so there's no separate ZIP step to write, doing our own would duplicate the packager deploy relies on. LMK what you think

Base automatically changed from chore/project-event-shape to refactorAugust 11, 2026 19:03
@notgitika
notgitikaforce-pushed the feat/project-build-synth branch from 305bf56 to 639227fCompareAugust 11, 2026 19:03
@tejaskash
tejaskashforce-pushed the feat/project-build-synth branch from 78097a8 to 0b2da99CompareAugust 12, 2026 20:24
tejaskash
tejaskash previously approved these changes Aug 12, 2026
aidandaly24
aidandaly24 previously approved these changes Aug 12, 2026

@aidandaly24aidandaly24 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me one really minor comment:


// The generated package.json defines `cdk` as "npm run build && cdk", so this
// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking and fine as a follow-up: synthesis succeeds without credentials, but AWS_PROFILE currently causes the pinned ConfigIO.readAWSDeploymentTargets() to call STS even when every target already has an account. I confirmed this by redirecting STS locally. The build still succeeded, but made six GetCallerIdentity attempts and inherited the retry latency. Could we avoid that fallback when account values are already present so build is fully offline?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

creating a follow up issue for this. thanks for catching this!

`agentcore project build` compiles the project's CDK app and synthesizes its
CloudFormation templates, so the deployable artifacts exist before deploy.
Synthesis runs offline: each stack's environment comes from aws-targets.json,
so no credentials are needed. An empty targets file makes the CDK app fail with
its own actionable message rather than the CLI guessing an account.
The generated package.json defines `cdk` as "npm run build && cdk", so one
`npm run cdk -- synth --quiet` covers both compile and synthesis.
Also puts withProject to work for the first time: it resolves the enclosing
project and hands it to the handler through ProjectKey, and it wraps only build
so that `create` (which refuses to nest inside a project) stays unaffected. Its
cwd is now resolved per invocation instead of at wiring time, so the directory
the user actually ran in is the one searched.
The CDK construct library rejects a CodeZip runtime that declares no
runtimeVersion ("runtimeVersion is required for CodeZip builds"), and it is the
field that selects the packager. Without it, synthesizing a freshly created
python project fails on its own scaffolded config.
Container builds take their version from the image, so the container template
is unaffected.
agentcore.json already records `managedBy` (CDK is the only value today), but
nothing read it: build() hardcoded the CDK path, so adding a terraform or
no-IaC backend later would have meant editing that path instead of adding
alongside it.
Carry managedBy on Project and switch on it in build(), delegating the CDK
work to a private buildWithCdk(). The default arm assigns to `never`, so a new
ManagedBySchema member fails to compile until it has an arm here.

@tejaskashtejaskash left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@tejaskash
tejaskash merged commit 25d59fb into refactorAug 13, 2026
13 checks passed
@tejaskash
tejaskash deleted the feat/project-build-synth branch August 13, 2026 16:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@notgitika@codecov-commenter@tejaskash@aidandaly24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(project): implement project build - #1970

Merged
tejaskash merged 3 commits into
refactorfrom
feat/project-build-synth
Aug 13, 2026
Merged

feat(project): implement project build#1970
tejaskash merged 3 commits into
refactorfrom
feat/project-build-synth

Conversation

@notgitika

@notgitikanotgitika commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

agentcore project build compiles the project's CDK app and synthesizes its CloudFormation templates, so the deployable artifacts exist before deploy.

The generated package.json defines cdk as "npm run build && cdk", so that one command covers both compile and synthesis — no need to construct a node_modules/.bin/cdk path or branch on win32.

Synthesis is credential-free, by design

An earlier draft of this called STS GetCallerIdentity and wrote agentcore/aws-targets.json as a side effect of build. That isn't needed: each stack takes an explicit env: { account, region } from aws-targets.json and nothing in the app calls fromLookup, so synth never talks to AWS. The account is only load-bearing at deploy.

So build stays offline and does not write config. When aws-targets.json is still [], the CDK app raises its own error, which already says what to do:

AgentCore CDK synthesis failed: No deployment targets configured.
Please define targets in agentcore/aws-targets.json

wrapped by ProcessFailedError with Fix the issue and run 'cd .../agentcore/cdk && npm run cdk -- synth --quiet' to retry.

Deliberately not guessing the account also avoids a trap the earlier draft had: it wrote region unvalidated, but AgentCoreRegionSchema is an enum of 9 regions. --region eu-west-2 would have written a region synth then rejects, and because the CLI's own re-read used z.array(z.unknown()) it would see a non-empty list and never repair the file.

withProject goes into service

src/middleware/withProject.tsx already existed but was never exported and never used. It now resolves the enclosing project and hands it to the handler via ProjectKey, so the handler body is just:

constproject=ctx.require(ProjectKey);forawait(consteventofconfig.projectManager.build(project)){config.io.stderr.write(`${event.message}\n`);}

Two changes to it:

  • cwd is optional and resolved per invocation (config.cwd ?? process.cwd()) rather than captured at wiring time, so the directory the user actually ran in is the one searched.
  • Failure is a ProjectStateError naming the path searched and the file looked for, and pointing at agentcore project create.

It wraps onlybuild, not the whole router — create refuses to nest inside an existing project, so requiring one would break it.

Second commit: runtimeVersion on the CodeZip template

Included here rather than as a follow-up, so build is never merged in a state where it fails on the config the CLI itself just scaffolded.

The CDK construct library's schema refines build !== 'Container' && !runtimeVersion into runtimeVersion is required for CodeZip builds, and our hello-world-python template didn't set it. Five lines: runtimeVersion: "PYTHON_3_14" on the template runtime plus the matching test assertion. Container builds take their version from the image, so the container template is untouched.

A follow-up will mirror that refinement into our own AgentEnvSpecSchema, which currently marks runtimeVersion plain .optional() — that way the CLI catches this class of error itself instead of surfacing CDK's zod path error.

Verification

Beyond unit tests, I ran this against a real scaffolded project with a real npm install:

  1. Empty aws-targets.json → the CDK app's "No deployment targets configured" error, wrapped with the retry hint.
  2. One target filled in, with AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN blanked and AWS_PROFILE pointed at a nonexistent profile → Built project 'Demo', emitting cdk.out/AgentCore-Demo-dev.template.json. Confirms synth needs no credentials.
  3. Stripping runtimeVersion back out of that same project → reproduces the CodeZip failure, which is what motivated the second commit.

Unit coverage: the exact synth command and cwd, the missing-node_modules error, subprocess failure propagation, the progress event, resolution from a nested directory, and failing outside a project.

  • bun test — 1061 pass, 0 fail
  • tsc --noEmit clean, oxlint clean

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 11, 2026
@codecov-commenter

codecov-commenter commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.82%. Comparing base (0e33398) to head (05c244e).

Additional details and impacted files
@@ Coverage Diff @@
## refactor #1970 +/- ##
============================================
+ Coverage 96.81% 96.82% +0.01% ============================================
Files 329 329 Lines 18754 18794 +40 ============================================
+ Hits 18157 18198 +41 + Misses 597 596 -1 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.
yield { message: "Synthesizing CloudFormation templates" };
await this.run(["npm", "run", "cdk", "--", "synth", "--quiet"], cdkDir);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we not using @aws-cdk/toolkit-lib anymore?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, I went with a subprocess to keep the PR small. using toolkit-lib means porting the wrapper and the schema pinning first, and deploy is what actually needs those. So I will introduce it when I introduce deploy. It should be easy to switch later, build() already yields events so it's a 2-way door decision

return project;
}

public async *build(project: Project): AsyncGenerator<ProjectEvent, void> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this build impl handle alternative project backends like terraform and SDK if we chose to implement those in the future?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the commit I just pushed, we use the managedBy field in agentcore.json

Comment on lines +14 to +24
handle: async (ctx) => {
// withProject has already resolved the enclosing project.
const project = ctx.require(ProjectKey);

// Progress goes to stderr, keeping stdout for machine output. Subprocess
// output goes to the debug log; on failure ProcessFailedError carries it.
for await (const event of config.projectManager.build(project)) {
config.io.stderr.write(`${event.message}\n`);
}

config.io.stderr.write(`Built project '${project.name}'\n`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that this is kept clean. It doesn't need to be aware of the build backend or any of the other steps.

Comment on lines +132 to +138
await this.checkTool("npm", "Install Node.js: https://nodejs.org/");

// The generated package.json defines `cdk` as "npm run build && cdk", so this
// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.
yield { message: "Synthesizing CloudFormation templates" };
await this.run(["npm", "run", "cdk", "--", "synth", "--quiet"], cdkDir);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we had discussed that build would:

  • Validate Schemas
  • Generate ZIP artifacts for CodeZIP Agents
  • Run CDK Synth

I'm not sure if Generate ZIP artifacts for CodeZIP Agents is happening here or if we decided to move that to a different PR.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three are happening. schema validation is up front, withProject resolves the project through ProjectSpecSchema, so build can't run on an invalid agentcore.json (#1972 makes that catch the CodeZip runtimeVersion rule and name the field).

the ZIP is generated by synth: the construct library's packager runs uv pip install and stages the asset, so cdk.out gets the zip and the template's CodeConfiguration.Code.S3 points at it.

I validated and checked a real synth to be sure. so there's no separate ZIP step to write, doing our own would duplicate the packager deploy relies on. LMK what you think

Base automatically changed from chore/project-event-shape to refactorAugust 11, 2026 19:03
@notgitika
notgitikaforce-pushed the feat/project-build-synth branch from 305bf56 to 639227fCompareAugust 11, 2026 19:03
@tejaskash
tejaskashforce-pushed the feat/project-build-synth branch from 78097a8 to 0b2da99CompareAugust 12, 2026 20:24
tejaskash
tejaskash previously approved these changes Aug 12, 2026
aidandaly24
aidandaly24 previously approved these changes Aug 12, 2026

@aidandaly24aidandaly24 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me one really minor comment:


// The generated package.json defines `cdk` as "npm run build && cdk", so this
// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking and fine as a follow-up: synthesis succeeds without credentials, but AWS_PROFILE currently causes the pinned ConfigIO.readAWSDeploymentTargets() to call STS even when every target already has an account. I confirmed this by redirecting STS locally. The build still succeeded, but made six GetCallerIdentity attempts and inherited the retry latency. Could we avoid that fallback when account values are already present so build is fully offline?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

creating a follow up issue for this. thanks for catching this!

`agentcore project build` compiles the project's CDK app and synthesizes its
CloudFormation templates, so the deployable artifacts exist before deploy.
Synthesis runs offline: each stack's environment comes from aws-targets.json,
so no credentials are needed. An empty targets file makes the CDK app fail with
its own actionable message rather than the CLI guessing an account.
The generated package.json defines `cdk` as "npm run build && cdk", so one
`npm run cdk -- synth --quiet` covers both compile and synthesis.
Also puts withProject to work for the first time: it resolves the enclosing
project and hands it to the handler through ProjectKey, and it wraps only build
so that `create` (which refuses to nest inside a project) stays unaffected. Its
cwd is now resolved per invocation instead of at wiring time, so the directory
the user actually ran in is the one searched.
The CDK construct library rejects a CodeZip runtime that declares no
runtimeVersion ("runtimeVersion is required for CodeZip builds"), and it is the
field that selects the packager. Without it, synthesizing a freshly created
python project fails on its own scaffolded config.
Container builds take their version from the image, so the container template
is unaffected.
agentcore.json already records `managedBy` (CDK is the only value today), but
nothing read it: build() hardcoded the CDK path, so adding a terraform or
no-IaC backend later would have meant editing that path instead of adding
alongside it.
Carry managedBy on Project and switch on it in build(), delegating the CDK
work to a private buildWithCdk(). The default arm assigns to `never`, so a new
ManagedBySchema member fails to compile until it has an arm here.

@tejaskashtejaskash left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@tejaskash
tejaskash merged commit 25d59fb into refactorAug 13, 2026
13 checks passed
@tejaskash
tejaskash deleted the feat/project-build-synth branch August 13, 2026 16:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@notgitika@codecov-commenter@tejaskash@aidandaly24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
Skip to content

feat(project): implement project build - #1970

Merged
tejaskash merged 3 commits into
refactorfrom
feat/project-build-synth
Aug 13, 2026
Merged

feat(project): implement project build#1970
tejaskash merged 3 commits into
refactorfrom
feat/project-build-synth

Conversation

@notgitika

@notgitikanotgitika commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

agentcore project build compiles the project's CDK app and synthesizes its CloudFormation templates, so the deployable artifacts exist before deploy.

The generated package.json defines cdk as "npm run build && cdk", so that one command covers both compile and synthesis — no need to construct a node_modules/.bin/cdk path or branch on win32.

Synthesis is credential-free, by design

An earlier draft of this called STS GetCallerIdentity and wrote agentcore/aws-targets.json as a side effect of build. That isn't needed: each stack takes an explicit env: { account, region } from aws-targets.json and nothing in the app calls fromLookup, so synth never talks to AWS. The account is only load-bearing at deploy.

So build stays offline and does not write config. When aws-targets.json is still [], the CDK app raises its own error, which already says what to do:

AgentCore CDK synthesis failed: No deployment targets configured.
Please define targets in agentcore/aws-targets.json

wrapped by ProcessFailedError with Fix the issue and run 'cd .../agentcore/cdk && npm run cdk -- synth --quiet' to retry.

Deliberately not guessing the account also avoids a trap the earlier draft had: it wrote region unvalidated, but AgentCoreRegionSchema is an enum of 9 regions. --region eu-west-2 would have written a region synth then rejects, and because the CLI's own re-read used z.array(z.unknown()) it would see a non-empty list and never repair the file.

withProject goes into service

src/middleware/withProject.tsx already existed but was never exported and never used. It now resolves the enclosing project and hands it to the handler via ProjectKey, so the handler body is just:

constproject=ctx.require(ProjectKey);forawait(consteventofconfig.projectManager.build(project)){config.io.stderr.write(`${event.message}\n`);}

Two changes to it:

  • cwd is optional and resolved per invocation (config.cwd ?? process.cwd()) rather than captured at wiring time, so the directory the user actually ran in is the one searched.
  • Failure is a ProjectStateError naming the path searched and the file looked for, and pointing at agentcore project create.

It wraps onlybuild, not the whole router — create refuses to nest inside an existing project, so requiring one would break it.

Second commit: runtimeVersion on the CodeZip template

Included here rather than as a follow-up, so build is never merged in a state where it fails on the config the CLI itself just scaffolded.

The CDK construct library's schema refines build !== 'Container' && !runtimeVersion into runtimeVersion is required for CodeZip builds, and our hello-world-python template didn't set it. Five lines: runtimeVersion: "PYTHON_3_14" on the template runtime plus the matching test assertion. Container builds take their version from the image, so the container template is untouched.

A follow-up will mirror that refinement into our own AgentEnvSpecSchema, which currently marks runtimeVersion plain .optional() — that way the CLI catches this class of error itself instead of surfacing CDK's zod path error.

Verification

Beyond unit tests, I ran this against a real scaffolded project with a real npm install:

  1. Empty aws-targets.json → the CDK app's "No deployment targets configured" error, wrapped with the retry hint.
  2. One target filled in, with AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN blanked and AWS_PROFILE pointed at a nonexistent profile → Built project 'Demo', emitting cdk.out/AgentCore-Demo-dev.template.json. Confirms synth needs no credentials.
  3. Stripping runtimeVersion back out of that same project → reproduces the CodeZip failure, which is what motivated the second commit.

Unit coverage: the exact synth command and cwd, the missing-node_modules error, subprocess failure propagation, the progress event, resolution from a nested directory, and failing outside a project.

  • bun test — 1061 pass, 0 fail
  • tsc --noEmit clean, oxlint clean

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 11, 2026
@codecov-commenter

codecov-commenter commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.82%. Comparing base (0e33398) to head (05c244e).

Additional details and impacted files
@@ Coverage Diff @@
## refactor #1970 +/- ##
============================================
+ Coverage 96.81% 96.82% +0.01% ============================================
Files 329 329 Lines 18754 18794 +40 ============================================
+ Hits 18157 18198 +41 + Misses 597 596 -1 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.
yield { message: "Synthesizing CloudFormation templates" };
await this.run(["npm", "run", "cdk", "--", "synth", "--quiet"], cdkDir);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we not using @aws-cdk/toolkit-lib anymore?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, I went with a subprocess to keep the PR small. using toolkit-lib means porting the wrapper and the schema pinning first, and deploy is what actually needs those. So I will introduce it when I introduce deploy. It should be easy to switch later, build() already yields events so it's a 2-way door decision

return project;
}

public async *build(project: Project): AsyncGenerator<ProjectEvent, void> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this build impl handle alternative project backends like terraform and SDK if we chose to implement those in the future?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the commit I just pushed, we use the managedBy field in agentcore.json

Comment on lines +14 to +24
handle: async (ctx) => {
// withProject has already resolved the enclosing project.
const project = ctx.require(ProjectKey);

// Progress goes to stderr, keeping stdout for machine output. Subprocess
// output goes to the debug log; on failure ProcessFailedError carries it.
for await (const event of config.projectManager.build(project)) {
config.io.stderr.write(`${event.message}\n`);
}

config.io.stderr.write(`Built project '${project.name}'\n`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that this is kept clean. It doesn't need to be aware of the build backend or any of the other steps.

Comment on lines +132 to +138
await this.checkTool("npm", "Install Node.js: https://nodejs.org/");

// The generated package.json defines `cdk` as "npm run build && cdk", so this
// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.
yield { message: "Synthesizing CloudFormation templates" };
await this.run(["npm", "run", "cdk", "--", "synth", "--quiet"], cdkDir);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we had discussed that build would:

  • Validate Schemas
  • Generate ZIP artifacts for CodeZIP Agents
  • Run CDK Synth

I'm not sure if Generate ZIP artifacts for CodeZIP Agents is happening here or if we decided to move that to a different PR.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three are happening. schema validation is up front, withProject resolves the project through ProjectSpecSchema, so build can't run on an invalid agentcore.json (#1972 makes that catch the CodeZip runtimeVersion rule and name the field).

the ZIP is generated by synth: the construct library's packager runs uv pip install and stages the asset, so cdk.out gets the zip and the template's CodeConfiguration.Code.S3 points at it.

I validated and checked a real synth to be sure. so there's no separate ZIP step to write, doing our own would duplicate the packager deploy relies on. LMK what you think

Base automatically changed from chore/project-event-shape to refactorAugust 11, 2026 19:03
@notgitika
notgitikaforce-pushed the feat/project-build-synth branch from 305bf56 to 639227fCompareAugust 11, 2026 19:03
@tejaskash
tejaskashforce-pushed the feat/project-build-synth branch from 78097a8 to 0b2da99CompareAugust 12, 2026 20:24
tejaskash
tejaskash previously approved these changes Aug 12, 2026
aidandaly24
aidandaly24 previously approved these changes Aug 12, 2026

@aidandaly24aidandaly24 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me one really minor comment:


// The generated package.json defines `cdk` as "npm run build && cdk", so this
// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking and fine as a follow-up: synthesis succeeds without credentials, but AWS_PROFILE currently causes the pinned ConfigIO.readAWSDeploymentTargets() to call STS even when every target already has an account. I confirmed this by redirecting STS locally. The build still succeeded, but made six GetCallerIdentity attempts and inherited the retry latency. Could we avoid that fallback when account values are already present so build is fully offline?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

creating a follow up issue for this. thanks for catching this!

`agentcore project build` compiles the project's CDK app and synthesizes its
CloudFormation templates, so the deployable artifacts exist before deploy.
Synthesis runs offline: each stack's environment comes from aws-targets.json,
so no credentials are needed. An empty targets file makes the CDK app fail with
its own actionable message rather than the CLI guessing an account.
The generated package.json defines `cdk` as "npm run build && cdk", so one
`npm run cdk -- synth --quiet` covers both compile and synthesis.
Also puts withProject to work for the first time: it resolves the enclosing
project and hands it to the handler through ProjectKey, and it wraps only build
so that `create` (which refuses to nest inside a project) stays unaffected. Its
cwd is now resolved per invocation instead of at wiring time, so the directory
the user actually ran in is the one searched.
The CDK construct library rejects a CodeZip runtime that declares no
runtimeVersion ("runtimeVersion is required for CodeZip builds"), and it is the
field that selects the packager. Without it, synthesizing a freshly created
python project fails on its own scaffolded config.
Container builds take their version from the image, so the container template
is unaffected.
agentcore.json already records `managedBy` (CDK is the only value today), but
nothing read it: build() hardcoded the CDK path, so adding a terraform or
no-IaC backend later would have meant editing that path instead of adding
alongside it.
Carry managedBy on Project and switch on it in build(), delegating the CDK
work to a private buildWithCdk(). The default arm assigns to `never`, so a new
ManagedBySchema member fails to compile until it has an arm here.

@tejaskashtejaskash left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@tejaskash
tejaskash merged commit 25d59fb into refactorAug 13, 2026
13 checks passed
@tejaskash
tejaskash deleted the feat/project-build-synth branch August 13, 2026 16:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@notgitika@codecov-commenter@tejaskash@aidandaly24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(project): implement project build - #1970

Merged
tejaskash merged 3 commits into
refactorfrom
feat/project-build-synth
Aug 13, 2026
Merged

feat(project): implement project build#1970
tejaskash merged 3 commits into
refactorfrom
feat/project-build-synth

Conversation

@notgitika

@notgitikanotgitika commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

agentcore project build compiles the project's CDK app and synthesizes its CloudFormation templates, so the deployable artifacts exist before deploy.

The generated package.json defines cdk as "npm run build && cdk", so that one command covers both compile and synthesis — no need to construct a node_modules/.bin/cdk path or branch on win32.

Synthesis is credential-free, by design

An earlier draft of this called STS GetCallerIdentity and wrote agentcore/aws-targets.json as a side effect of build. That isn't needed: each stack takes an explicit env: { account, region } from aws-targets.json and nothing in the app calls fromLookup, so synth never talks to AWS. The account is only load-bearing at deploy.

So build stays offline and does not write config. When aws-targets.json is still [], the CDK app raises its own error, which already says what to do:

AgentCore CDK synthesis failed: No deployment targets configured.
Please define targets in agentcore/aws-targets.json

wrapped by ProcessFailedError with Fix the issue and run 'cd .../agentcore/cdk && npm run cdk -- synth --quiet' to retry.

Deliberately not guessing the account also avoids a trap the earlier draft had: it wrote region unvalidated, but AgentCoreRegionSchema is an enum of 9 regions. --region eu-west-2 would have written a region synth then rejects, and because the CLI's own re-read used z.array(z.unknown()) it would see a non-empty list and never repair the file.

withProject goes into service

src/middleware/withProject.tsx already existed but was never exported and never used. It now resolves the enclosing project and hands it to the handler via ProjectKey, so the handler body is just:

constproject=ctx.require(ProjectKey);forawait(consteventofconfig.projectManager.build(project)){config.io.stderr.write(`${event.message}\n`);}

Two changes to it:

  • cwd is optional and resolved per invocation (config.cwd ?? process.cwd()) rather than captured at wiring time, so the directory the user actually ran in is the one searched.
  • Failure is a ProjectStateError naming the path searched and the file looked for, and pointing at agentcore project create.

It wraps onlybuild, not the whole router — create refuses to nest inside an existing project, so requiring one would break it.

Second commit: runtimeVersion on the CodeZip template

Included here rather than as a follow-up, so build is never merged in a state where it fails on the config the CLI itself just scaffolded.

The CDK construct library's schema refines build !== 'Container' && !runtimeVersion into runtimeVersion is required for CodeZip builds, and our hello-world-python template didn't set it. Five lines: runtimeVersion: "PYTHON_3_14" on the template runtime plus the matching test assertion. Container builds take their version from the image, so the container template is untouched.

A follow-up will mirror that refinement into our own AgentEnvSpecSchema, which currently marks runtimeVersion plain .optional() — that way the CLI catches this class of error itself instead of surfacing CDK's zod path error.

Verification

Beyond unit tests, I ran this against a real scaffolded project with a real npm install:

  1. Empty aws-targets.json → the CDK app's "No deployment targets configured" error, wrapped with the retry hint.
  2. One target filled in, with AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN blanked and AWS_PROFILE pointed at a nonexistent profile → Built project 'Demo', emitting cdk.out/AgentCore-Demo-dev.template.json. Confirms synth needs no credentials.
  3. Stripping runtimeVersion back out of that same project → reproduces the CodeZip failure, which is what motivated the second commit.

Unit coverage: the exact synth command and cwd, the missing-node_modules error, subprocess failure propagation, the progress event, resolution from a nested directory, and failing outside a project.

  • bun test — 1061 pass, 0 fail
  • tsc --noEmit clean, oxlint clean

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 11, 2026
@codecov-commenter

codecov-commenter commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.82%. Comparing base (0e33398) to head (05c244e).

Additional details and impacted files
@@ Coverage Diff @@
## refactor #1970 +/- ##
============================================
+ Coverage 96.81% 96.82% +0.01% ============================================
Files 329 329 Lines 18754 18794 +40 ============================================
+ Hits 18157 18198 +41 + Misses 597 596 -1 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.
yield { message: "Synthesizing CloudFormation templates" };
await this.run(["npm", "run", "cdk", "--", "synth", "--quiet"], cdkDir);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we not using @aws-cdk/toolkit-lib anymore?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, I went with a subprocess to keep the PR small. using toolkit-lib means porting the wrapper and the schema pinning first, and deploy is what actually needs those. So I will introduce it when I introduce deploy. It should be easy to switch later, build() already yields events so it's a 2-way door decision

return project;
}

public async *build(project: Project): AsyncGenerator<ProjectEvent, void> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this build impl handle alternative project backends like terraform and SDK if we chose to implement those in the future?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the commit I just pushed, we use the managedBy field in agentcore.json

Comment on lines +14 to +24
handle: async (ctx) => {
// withProject has already resolved the enclosing project.
const project = ctx.require(ProjectKey);

// Progress goes to stderr, keeping stdout for machine output. Subprocess
// output goes to the debug log; on failure ProcessFailedError carries it.
for await (const event of config.projectManager.build(project)) {
config.io.stderr.write(`${event.message}\n`);
}

config.io.stderr.write(`Built project '${project.name}'\n`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that this is kept clean. It doesn't need to be aware of the build backend or any of the other steps.

Comment on lines +132 to +138
await this.checkTool("npm", "Install Node.js: https://nodejs.org/");

// The generated package.json defines `cdk` as "npm run build && cdk", so this
// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.
yield { message: "Synthesizing CloudFormation templates" };
await this.run(["npm", "run", "cdk", "--", "synth", "--quiet"], cdkDir);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we had discussed that build would:

  • Validate Schemas
  • Generate ZIP artifacts for CodeZIP Agents
  • Run CDK Synth

I'm not sure if Generate ZIP artifacts for CodeZIP Agents is happening here or if we decided to move that to a different PR.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three are happening. schema validation is up front, withProject resolves the project through ProjectSpecSchema, so build can't run on an invalid agentcore.json (#1972 makes that catch the CodeZip runtimeVersion rule and name the field).

the ZIP is generated by synth: the construct library's packager runs uv pip install and stages the asset, so cdk.out gets the zip and the template's CodeConfiguration.Code.S3 points at it.

I validated and checked a real synth to be sure. so there's no separate ZIP step to write, doing our own would duplicate the packager deploy relies on. LMK what you think

Base automatically changed from chore/project-event-shape to refactorAugust 11, 2026 19:03
@notgitika
notgitikaforce-pushed the feat/project-build-synth branch from 305bf56 to 639227fCompareAugust 11, 2026 19:03
@tejaskash
tejaskashforce-pushed the feat/project-build-synth branch from 78097a8 to 0b2da99CompareAugust 12, 2026 20:24
tejaskash
tejaskash previously approved these changes Aug 12, 2026
aidandaly24
aidandaly24 previously approved these changes Aug 12, 2026

@aidandaly24aidandaly24 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me one really minor comment:


// The generated package.json defines `cdk` as "npm run build && cdk", so this
// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking and fine as a follow-up: synthesis succeeds without credentials, but AWS_PROFILE currently causes the pinned ConfigIO.readAWSDeploymentTargets() to call STS even when every target already has an account. I confirmed this by redirecting STS locally. The build still succeeded, but made six GetCallerIdentity attempts and inherited the retry latency. Could we avoid that fallback when account values are already present so build is fully offline?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

creating a follow up issue for this. thanks for catching this!

`agentcore project build` compiles the project's CDK app and synthesizes its
CloudFormation templates, so the deployable artifacts exist before deploy.
Synthesis runs offline: each stack's environment comes from aws-targets.json,
so no credentials are needed. An empty targets file makes the CDK app fail with
its own actionable message rather than the CLI guessing an account.
The generated package.json defines `cdk` as "npm run build && cdk", so one
`npm run cdk -- synth --quiet` covers both compile and synthesis.
Also puts withProject to work for the first time: it resolves the enclosing
project and hands it to the handler through ProjectKey, and it wraps only build
so that `create` (which refuses to nest inside a project) stays unaffected. Its
cwd is now resolved per invocation instead of at wiring time, so the directory
the user actually ran in is the one searched.
The CDK construct library rejects a CodeZip runtime that declares no
runtimeVersion ("runtimeVersion is required for CodeZip builds"), and it is the
field that selects the packager. Without it, synthesizing a freshly created
python project fails on its own scaffolded config.
Container builds take their version from the image, so the container template
is unaffected.
agentcore.json already records `managedBy` (CDK is the only value today), but
nothing read it: build() hardcoded the CDK path, so adding a terraform or
no-IaC backend later would have meant editing that path instead of adding
alongside it.
Carry managedBy on Project and switch on it in build(), delegating the CDK
work to a private buildWithCdk(). The default arm assigns to `never`, so a new
ManagedBySchema member fails to compile until it has an arm here.

@tejaskashtejaskash left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@tejaskash
tejaskash merged commit 25d59fb into refactorAug 13, 2026
13 checks passed
@tejaskash
tejaskash deleted the feat/project-build-synth branch August 13, 2026 16:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@notgitika@codecov-commenter@tejaskash@aidandaly24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
Skip to content

feat(project): implement project build - #1970

Merged
tejaskash merged 3 commits into
refactorfrom
feat/project-build-synth
Aug 13, 2026
Merged

feat(project): implement project build#1970
tejaskash merged 3 commits into
refactorfrom
feat/project-build-synth

Conversation

@notgitika

@notgitikanotgitika commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

agentcore project build compiles the project's CDK app and synthesizes its CloudFormation templates, so the deployable artifacts exist before deploy.

The generated package.json defines cdk as "npm run build && cdk", so that one command covers both compile and synthesis — no need to construct a node_modules/.bin/cdk path or branch on win32.

Synthesis is credential-free, by design

An earlier draft of this called STS GetCallerIdentity and wrote agentcore/aws-targets.json as a side effect of build. That isn't needed: each stack takes an explicit env: { account, region } from aws-targets.json and nothing in the app calls fromLookup, so synth never talks to AWS. The account is only load-bearing at deploy.

So build stays offline and does not write config. When aws-targets.json is still [], the CDK app raises its own error, which already says what to do:

AgentCore CDK synthesis failed: No deployment targets configured.
Please define targets in agentcore/aws-targets.json

wrapped by ProcessFailedError with Fix the issue and run 'cd .../agentcore/cdk && npm run cdk -- synth --quiet' to retry.

Deliberately not guessing the account also avoids a trap the earlier draft had: it wrote region unvalidated, but AgentCoreRegionSchema is an enum of 9 regions. --region eu-west-2 would have written a region synth then rejects, and because the CLI's own re-read used z.array(z.unknown()) it would see a non-empty list and never repair the file.

withProject goes into service

src/middleware/withProject.tsx already existed but was never exported and never used. It now resolves the enclosing project and hands it to the handler via ProjectKey, so the handler body is just:

constproject=ctx.require(ProjectKey);forawait(consteventofconfig.projectManager.build(project)){config.io.stderr.write(`${event.message}\n`);}

Two changes to it:

  • cwd is optional and resolved per invocation (config.cwd ?? process.cwd()) rather than captured at wiring time, so the directory the user actually ran in is the one searched.
  • Failure is a ProjectStateError naming the path searched and the file looked for, and pointing at agentcore project create.

It wraps onlybuild, not the whole router — create refuses to nest inside an existing project, so requiring one would break it.

Second commit: runtimeVersion on the CodeZip template

Included here rather than as a follow-up, so build is never merged in a state where it fails on the config the CLI itself just scaffolded.

The CDK construct library's schema refines build !== 'Container' && !runtimeVersion into runtimeVersion is required for CodeZip builds, and our hello-world-python template didn't set it. Five lines: runtimeVersion: "PYTHON_3_14" on the template runtime plus the matching test assertion. Container builds take their version from the image, so the container template is untouched.

A follow-up will mirror that refinement into our own AgentEnvSpecSchema, which currently marks runtimeVersion plain .optional() — that way the CLI catches this class of error itself instead of surfacing CDK's zod path error.

Verification

Beyond unit tests, I ran this against a real scaffolded project with a real npm install:

  1. Empty aws-targets.json → the CDK app's "No deployment targets configured" error, wrapped with the retry hint.
  2. One target filled in, with AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN blanked and AWS_PROFILE pointed at a nonexistent profile → Built project 'Demo', emitting cdk.out/AgentCore-Demo-dev.template.json. Confirms synth needs no credentials.
  3. Stripping runtimeVersion back out of that same project → reproduces the CodeZip failure, which is what motivated the second commit.

Unit coverage: the exact synth command and cwd, the missing-node_modules error, subprocess failure propagation, the progress event, resolution from a nested directory, and failing outside a project.

  • bun test — 1061 pass, 0 fail
  • tsc --noEmit clean, oxlint clean

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 11, 2026
@codecov-commenter

codecov-commenter commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.82%. Comparing base (0e33398) to head (05c244e).

Additional details and impacted files
@@ Coverage Diff @@
## refactor #1970 +/- ##
============================================
+ Coverage 96.81% 96.82% +0.01% ============================================
Files 329 329 Lines 18754 18794 +40 ============================================
+ Hits 18157 18198 +41 + Misses 597 596 -1 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.
yield { message: "Synthesizing CloudFormation templates" };
await this.run(["npm", "run", "cdk", "--", "synth", "--quiet"], cdkDir);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we not using @aws-cdk/toolkit-lib anymore?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, I went with a subprocess to keep the PR small. using toolkit-lib means porting the wrapper and the schema pinning first, and deploy is what actually needs those. So I will introduce it when I introduce deploy. It should be easy to switch later, build() already yields events so it's a 2-way door decision

return project;
}

public async *build(project: Project): AsyncGenerator<ProjectEvent, void> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this build impl handle alternative project backends like terraform and SDK if we chose to implement those in the future?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the commit I just pushed, we use the managedBy field in agentcore.json

Comment on lines +14 to +24
handle: async (ctx) => {
// withProject has already resolved the enclosing project.
const project = ctx.require(ProjectKey);

// Progress goes to stderr, keeping stdout for machine output. Subprocess
// output goes to the debug log; on failure ProcessFailedError carries it.
for await (const event of config.projectManager.build(project)) {
config.io.stderr.write(`${event.message}\n`);
}

config.io.stderr.write(`Built project '${project.name}'\n`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that this is kept clean. It doesn't need to be aware of the build backend or any of the other steps.

Comment on lines +132 to +138
await this.checkTool("npm", "Install Node.js: https://nodejs.org/");

// The generated package.json defines `cdk` as "npm run build && cdk", so this
// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.
yield { message: "Synthesizing CloudFormation templates" };
await this.run(["npm", "run", "cdk", "--", "synth", "--quiet"], cdkDir);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we had discussed that build would:

  • Validate Schemas
  • Generate ZIP artifacts for CodeZIP Agents
  • Run CDK Synth

I'm not sure if Generate ZIP artifacts for CodeZIP Agents is happening here or if we decided to move that to a different PR.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three are happening. schema validation is up front, withProject resolves the project through ProjectSpecSchema, so build can't run on an invalid agentcore.json (#1972 makes that catch the CodeZip runtimeVersion rule and name the field).

the ZIP is generated by synth: the construct library's packager runs uv pip install and stages the asset, so cdk.out gets the zip and the template's CodeConfiguration.Code.S3 points at it.

I validated and checked a real synth to be sure. so there's no separate ZIP step to write, doing our own would duplicate the packager deploy relies on. LMK what you think

Base automatically changed from chore/project-event-shape to refactorAugust 11, 2026 19:03
@notgitika
notgitikaforce-pushed the feat/project-build-synth branch from 305bf56 to 639227fCompareAugust 11, 2026 19:03
@tejaskash
tejaskashforce-pushed the feat/project-build-synth branch from 78097a8 to 0b2da99CompareAugust 12, 2026 20:24
tejaskash
tejaskash previously approved these changes Aug 12, 2026
aidandaly24
aidandaly24 previously approved these changes Aug 12, 2026

@aidandaly24aidandaly24 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me one really minor comment:


// The generated package.json defines `cdk` as "npm run build && cdk", so this
// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking and fine as a follow-up: synthesis succeeds without credentials, but AWS_PROFILE currently causes the pinned ConfigIO.readAWSDeploymentTargets() to call STS even when every target already has an account. I confirmed this by redirecting STS locally. The build still succeeded, but made six GetCallerIdentity attempts and inherited the retry latency. Could we avoid that fallback when account values are already present so build is fully offline?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

creating a follow up issue for this. thanks for catching this!

`agentcore project build` compiles the project's CDK app and synthesizes its
CloudFormation templates, so the deployable artifacts exist before deploy.
Synthesis runs offline: each stack's environment comes from aws-targets.json,
so no credentials are needed. An empty targets file makes the CDK app fail with
its own actionable message rather than the CLI guessing an account.
The generated package.json defines `cdk` as "npm run build && cdk", so one
`npm run cdk -- synth --quiet` covers both compile and synthesis.
Also puts withProject to work for the first time: it resolves the enclosing
project and hands it to the handler through ProjectKey, and it wraps only build
so that `create` (which refuses to nest inside a project) stays unaffected. Its
cwd is now resolved per invocation instead of at wiring time, so the directory
the user actually ran in is the one searched.
The CDK construct library rejects a CodeZip runtime that declares no
runtimeVersion ("runtimeVersion is required for CodeZip builds"), and it is the
field that selects the packager. Without it, synthesizing a freshly created
python project fails on its own scaffolded config.
Container builds take their version from the image, so the container template
is unaffected.
agentcore.json already records `managedBy` (CDK is the only value today), but
nothing read it: build() hardcoded the CDK path, so adding a terraform or
no-IaC backend later would have meant editing that path instead of adding
alongside it.
Carry managedBy on Project and switch on it in build(), delegating the CDK
work to a private buildWithCdk(). The default arm assigns to `never`, so a new
ManagedBySchema member fails to compile until it has an arm here.

@tejaskashtejaskash left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@tejaskash
tejaskash merged commit 25d59fb into refactorAug 13, 2026
13 checks passed
@tejaskash
tejaskash deleted the feat/project-build-synth branch August 13, 2026 16:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@notgitika@codecov-commenter@tejaskash@aidandaly24
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
Skip to content

feat(project): implement project build - #1970

Merged
tejaskash merged 3 commits into
refactorfrom
feat/project-build-synth
Aug 13, 2026
Merged

feat(project): implement project build#1970
tejaskash merged 3 commits into
refactorfrom
feat/project-build-synth

Conversation

@notgitika

@notgitikanotgitika commented Aug 11, 2026

Copy link
Copy Markdown
Contributor

agentcore project build compiles the project's CDK app and synthesizes its CloudFormation templates, so the deployable artifacts exist before deploy.

The generated package.json defines cdk as "npm run build && cdk", so that one command covers both compile and synthesis — no need to construct a node_modules/.bin/cdk path or branch on win32.

Synthesis is credential-free, by design

An earlier draft of this called STS GetCallerIdentity and wrote agentcore/aws-targets.json as a side effect of build. That isn't needed: each stack takes an explicit env: { account, region } from aws-targets.json and nothing in the app calls fromLookup, so synth never talks to AWS. The account is only load-bearing at deploy.

So build stays offline and does not write config. When aws-targets.json is still [], the CDK app raises its own error, which already says what to do:

AgentCore CDK synthesis failed: No deployment targets configured.
Please define targets in agentcore/aws-targets.json

wrapped by ProcessFailedError with Fix the issue and run 'cd .../agentcore/cdk && npm run cdk -- synth --quiet' to retry.

Deliberately not guessing the account also avoids a trap the earlier draft had: it wrote region unvalidated, but AgentCoreRegionSchema is an enum of 9 regions. --region eu-west-2 would have written a region synth then rejects, and because the CLI's own re-read used z.array(z.unknown()) it would see a non-empty list and never repair the file.

withProject goes into service

src/middleware/withProject.tsx already existed but was never exported and never used. It now resolves the enclosing project and hands it to the handler via ProjectKey, so the handler body is just:

constproject=ctx.require(ProjectKey);forawait(consteventofconfig.projectManager.build(project)){config.io.stderr.write(`${event.message}\n`);}

Two changes to it:

  • cwd is optional and resolved per invocation (config.cwd ?? process.cwd()) rather than captured at wiring time, so the directory the user actually ran in is the one searched.
  • Failure is a ProjectStateError naming the path searched and the file looked for, and pointing at agentcore project create.

It wraps onlybuild, not the whole router — create refuses to nest inside an existing project, so requiring one would break it.

Second commit: runtimeVersion on the CodeZip template

Included here rather than as a follow-up, so build is never merged in a state where it fails on the config the CLI itself just scaffolded.

The CDK construct library's schema refines build !== 'Container' && !runtimeVersion into runtimeVersion is required for CodeZip builds, and our hello-world-python template didn't set it. Five lines: runtimeVersion: "PYTHON_3_14" on the template runtime plus the matching test assertion. Container builds take their version from the image, so the container template is untouched.

A follow-up will mirror that refinement into our own AgentEnvSpecSchema, which currently marks runtimeVersion plain .optional() — that way the CLI catches this class of error itself instead of surfacing CDK's zod path error.

Verification

Beyond unit tests, I ran this against a real scaffolded project with a real npm install:

  1. Empty aws-targets.json → the CDK app's "No deployment targets configured" error, wrapped with the retry hint.
  2. One target filled in, with AWS_ACCESS_KEY_ID/SECRET/SESSION_TOKEN blanked and AWS_PROFILE pointed at a nonexistent profile → Built project 'Demo', emitting cdk.out/AgentCore-Demo-dev.template.json. Confirms synth needs no credentials.
  3. Stripping runtimeVersion back out of that same project → reproduces the CodeZip failure, which is what motivated the second commit.

Unit coverage: the exact synth command and cwd, the missing-node_modules error, subprocess failure propagation, the progress event, resolution from a nested directory, and failing outside a project.

  • bun test — 1061 pass, 0 fail
  • tsc --noEmit clean, oxlint clean

@github-actionsgithub-actionsBot added agentcore-harness-reviewing AgentCore Harness review in progress and removed agentcore-harness-reviewing AgentCore Harness review in progress labels Aug 11, 2026
@codecov-commenter

codecov-commenter commented Aug 11, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 96.82%. Comparing base (0e33398) to head (05c244e).

Additional details and impacted files
@@ Coverage Diff @@
## refactor #1970 +/- ##
============================================
+ Coverage 96.81% 96.82% +0.01% ============================================
Files 329 329 Lines 18754 18794 +40 ============================================
+ Hits 18157 18198 +41 + Misses 597 596 -1 

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.
yield { message: "Synthesizing CloudFormation templates" };
await this.run(["npm", "run", "cdk", "--", "synth", "--quiet"], cdkDir);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Are we not using @aws-cdk/toolkit-lib anymore?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For now, I went with a subprocess to keep the PR small. using toolkit-lib means porting the wrapper and the schema pinning first, and deploy is what actually needs those. So I will introduce it when I introduce deploy. It should be easy to switch later, build() already yields events so it's a 2-way door decision

return project;
}

public async *build(project: Project): AsyncGenerator<ProjectEvent, void> {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

How does this build impl handle alternative project backends like terraform and SDK if we chose to implement those in the future?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In the commit I just pushed, we use the managedBy field in agentcore.json

Comment on lines +14 to +24
handle: async (ctx) => {
// withProject has already resolved the enclosing project.
const project = ctx.require(ProjectKey);

// Progress goes to stderr, keeping stdout for machine output. Subprocess
// output goes to the debug log; on failure ProcessFailedError carries it.
for await (const event of config.projectManager.build(project)) {
config.io.stderr.write(`${event.message}\n`);
}

config.io.stderr.write(`Built project '${project.name}'\n`);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like that this is kept clean. It doesn't need to be aware of the build backend or any of the other steps.

Comment on lines +132 to +138
await this.checkTool("npm", "Install Node.js: https://nodejs.org/");

// The generated package.json defines `cdk` as "npm run build && cdk", so this
// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.
yield { message: "Synthesizing CloudFormation templates" };
await this.run(["npm", "run", "cdk", "--", "synth", "--quiet"], cdkDir);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we had discussed that build would:

  • Validate Schemas
  • Generate ZIP artifacts for CodeZIP Agents
  • Run CDK Synth

I'm not sure if Generate ZIP artifacts for CodeZIP Agents is happening here or if we decided to move that to a different PR.

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All three are happening. schema validation is up front, withProject resolves the project through ProjectSpecSchema, so build can't run on an invalid agentcore.json (#1972 makes that catch the CodeZip runtimeVersion rule and name the field).

the ZIP is generated by synth: the construct library's packager runs uv pip install and stages the asset, so cdk.out gets the zip and the template's CodeConfiguration.Code.S3 points at it.

I validated and checked a real synth to be sure. so there's no separate ZIP step to write, doing our own would duplicate the packager deploy relies on. LMK what you think

Base automatically changed from chore/project-event-shape to refactorAugust 11, 2026 19:03
@notgitika
notgitikaforce-pushed the feat/project-build-synth branch from 305bf56 to 639227fCompareAugust 11, 2026 19:03
@tejaskash
tejaskashforce-pushed the feat/project-build-synth branch from 78097a8 to 0b2da99CompareAugust 12, 2026 20:24
tejaskash
tejaskash previously approved these changes Aug 12, 2026
aidandaly24
aidandaly24 previously approved these changes Aug 12, 2026

@aidandaly24aidandaly24 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks good to me one really minor comment:


// The generated package.json defines `cdk` as "npm run build && cdk", so this
// single command compiles the app and then synthesizes it. Synthesis needs no
// AWS credentials: each stack's environment comes from aws-targets.json.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Non-blocking and fine as a follow-up: synthesis succeeds without credentials, but AWS_PROFILE currently causes the pinned ConfigIO.readAWSDeploymentTargets() to call STS even when every target already has an account. I confirmed this by redirecting STS locally. The build still succeeded, but made six GetCallerIdentity attempts and inherited the retry latency. Could we avoid that fallback when account values are already present so build is fully offline?

Copy link
Copy Markdown
ContributorAuthor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

creating a follow up issue for this. thanks for catching this!

`agentcore project build` compiles the project's CDK app and synthesizes its
CloudFormation templates, so the deployable artifacts exist before deploy.
Synthesis runs offline: each stack's environment comes from aws-targets.json,
so no credentials are needed. An empty targets file makes the CDK app fail with
its own actionable message rather than the CLI guessing an account.
The generated package.json defines `cdk` as "npm run build && cdk", so one
`npm run cdk -- synth --quiet` covers both compile and synthesis.
Also puts withProject to work for the first time: it resolves the enclosing
project and hands it to the handler through ProjectKey, and it wraps only build
so that `create` (which refuses to nest inside a project) stays unaffected. Its
cwd is now resolved per invocation instead of at wiring time, so the directory
the user actually ran in is the one searched.
The CDK construct library rejects a CodeZip runtime that declares no
runtimeVersion ("runtimeVersion is required for CodeZip builds"), and it is the
field that selects the packager. Without it, synthesizing a freshly created
python project fails on its own scaffolded config.
Container builds take their version from the image, so the container template
is unaffected.
agentcore.json already records `managedBy` (CDK is the only value today), but
nothing read it: build() hardcoded the CDK path, so adding a terraform or
no-IaC backend later would have meant editing that path instead of adding
alongside it.
Carry managedBy on Project and switch on it in build(), delegating the CDK
work to a private buildWithCdk(). The default arm assigns to `never`, so a new
ManagedBySchema member fails to compile until it has an arm here.

@tejaskashtejaskash left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM

@tejaskash
tejaskash merged commit 25d59fb into refactorAug 13, 2026
13 checks passed
@tejaskash
tejaskash deleted the feat/project-build-synth branch August 13, 2026 16:26
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants

@notgitika@codecov-commenter@tejaskash@aidandaly24