From 6a4980fe8c7878c09cb29828764a4f2df1a1f15a Mon Sep 17 00:00:00 2001 From: Tejas Kashinath Date: Fri, 20 Mar 2026 11:56:07 -0400 Subject: [PATCH 1/6] fix: improve old CLI conflict detection in preinstall hook Replace brittle PATH-based detection with direct package-manager queries (pip list, pipx list, uv tool list) and keep the old PATH check as a fallback. Exit non-zero so npm always surfaces the error. Add AGENTCORE_SKIP_CONFLICT_CHECK env var bypass. Update README to show all three uninstall methods before the install command. Closes #587 --- README.md | 17 +- package-lock.json | 4 +- scripts/check-old-cli.lib.mjs | 97 ++++++++ scripts/check-old-cli.mjs | 31 +-- .../__tests__/check-old-cli.test.ts | 224 ++++++++++++++++++ 5 files changed, 341 insertions(+), 32 deletions(-) create mode 100644 scripts/check-old-cli.lib.mjs create mode 100644 src/cli/external-requirements/__tests__/check-old-cli.test.ts diff --git a/README.md b/README.md index ee68cb0b..7dbe586c 100644 --- a/README.md +++ b/README.md @@ -17,18 +17,19 @@ AgentCore with minimal configuration. ## Installation -```bash -npm install -g @aws/agentcore -``` - -> **Public Preview**: If you previously used the -> [Bedrock AgentCore Starter Toolkit](https://github.com/aws/bedrock-agentcore-starter-toolkit), uninstall it before -> using this CLI: +> **Upgrading from the Bedrock AgentCore Starter Toolkit?** The old Python CLI conflicts with this package. Uninstall it +> first using whichever tool you used to install it: > > ```bash -> pip uninstall bedrock-agentcore-starter-toolkit +> pip uninstall bedrock-agentcore-starter-toolkit # if installed via pip +> pipx uninstall bedrock-agentcore-starter-toolkit # if installed via pipx +> uv tool uninstall bedrock-agentcore-starter-toolkit # if installed via uv > ``` +```bash +npm install -g @aws/agentcore +``` + ## Quick Start Use the terminal UI to walk through all commands interactively, or run each command individually: diff --git a/package-lock.json b/package-lock.json index 253c9aa2..2d600881 100644 --- a/package-lock.json +++ b/package-lock.json @@ -47,7 +47,7 @@ "@typescript-eslint/parser": "^8.50.0", "@vitest/coverage-v8": "^4.0.18", "@xterm/headless": "^6.0.0", - "aws-cdk-lib": "^2.240.0", + "aws-cdk-lib": "^2.243.0", "constructs": "^10.4.4", "esbuild": "^0.27.2", "eslint": "^9.39.4", @@ -73,7 +73,7 @@ "node": ">=20" }, "peerDependencies": { - "aws-cdk-lib": "^2.234.1", + "aws-cdk-lib": "^2.243.0", "constructs": "^10.0.0" } }, diff --git a/scripts/check-old-cli.lib.mjs b/scripts/check-old-cli.lib.mjs new file mode 100644 index 00000000..737cdc21 --- /dev/null +++ b/scripts/check-old-cli.lib.mjs @@ -0,0 +1,97 @@ +/** + * Testable detection logic for the old Bedrock AgentCore Starter Toolkit. + * + * Each function accepts an `execSyncFn` so callers can inject a mock. + */ + +const INSTALLERS = [ + { cmd: 'pip list', label: 'pip', uninstallCmd: 'pip uninstall bedrock-agentcore-starter-toolkit' }, + { cmd: 'pipx list', label: 'pipx', uninstallCmd: 'pipx uninstall bedrock-agentcore-starter-toolkit' }, + { cmd: 'uv tool list', label: 'uv', uninstallCmd: 'uv tool uninstall bedrock-agentcore-starter-toolkit' }, +]; + +/** + * Run a package-manager list command and check whether the old toolkit appears. + * Returns `{ installer, uninstallCmd }` when found, or `null`. + */ +export function probeInstaller(cmd, label, uninstallCmd, execSyncFn) { + try { + const output = execSyncFn(cmd); + if (output.includes('bedrock-agentcore-starter-toolkit')) { + return { installer: label, uninstallCmd }; + } + } catch { + // Command not found or non-zero exit — ignore. + } + return null; +} + +/** + * PATH-based fallback: locate an `agentcore` binary and check whether it's + * the old Python CLI (which doesn't support --version). + * Returns `{ installer, uninstallCmd }` when the old CLI is found, or `null`. + */ +export function probePath(execSyncFn) { + try { + execSyncFn('command -v agentcore'); + } catch { + return null; // no agentcore binary on PATH + } + try { + execSyncFn('agentcore --version'); + return null; // --version succeeded — this is the new CLI + } catch { + // --version failed — likely the old Python CLI + return { + installer: 'PATH', + uninstallCmd: 'pip uninstall bedrock-agentcore-starter-toolkit', + }; + } +} + +/** + * Probe pip, pipx, and uv for the old toolkit, then fall back to PATH-based + * detection. Returns an array of matches. + */ +export function detectOldToolkit(execSyncFn) { + const results = []; + for (const { cmd, label, uninstallCmd } of INSTALLERS) { + const match = probeInstaller(cmd, label, uninstallCmd, execSyncFn); + if (match) results.push(match); + } + // If package-manager queries found nothing, fall back to PATH-based check + if (results.length === 0) { + const pathMatch = probePath(execSyncFn); + if (pathMatch) results.push(pathMatch); + } + return results; +} + +/** + * Format a user-facing error message listing per-installer uninstall commands. + */ +export function formatErrorMessage(detected) { + const lines = [ + '', + '\x1b[31mError: The old Bedrock AgentCore Starter Toolkit is installed and conflicts with @aws/agentcore.\x1b[0m', + '', + 'Uninstall it first, then re-run the install:', + '', + ]; + + for (const { installer, uninstallCmd } of detected) { + lines.push(` ${uninstallCmd} # installed via ${installer}`); + } + + lines.push( + '', + 'Then re-run:', + '', + ' npm install -g @aws/agentcore', + '', + 'To bypass this check, set AGENTCORE_SKIP_CONFLICT_CHECK=1', + '', + ); + + return lines.join('\n'); +} diff --git a/scripts/check-old-cli.mjs b/scripts/check-old-cli.mjs index 8a1b28b2..59e2e088 100644 --- a/scripts/check-old-cli.mjs +++ b/scripts/check-old-cli.mjs @@ -1,26 +1,13 @@ import { execSync } from 'node:child_process'; -import { platform } from 'node:os'; +import { detectOldToolkit, formatErrorMessage } from './check-old-cli.lib.mjs'; -try { - // Check if an `agentcore` binary exists on PATH - const whichCmd = platform() === 'win32' ? 'where agentcore' : 'command -v agentcore'; - execSync(whichCmd, { stdio: 'ignore' }); +if (process.env.AGENTCORE_SKIP_CONFLICT_CHECK === '1') process.exit(0); - // Binary exists — check if it supports --version (the new CLI does, the old Python one does not) - try { - execSync('agentcore --version', { stdio: 'ignore' }); - } catch { - // --version failed → likely the old Python CLI - console.warn( - [ - '', - '\x1b[33m⚠ WARNING: We detected an older version of the AgentCore CLI.\x1b[0m', - '\x1b[33mFor the best experience, we recommend uninstalling it using:\x1b[0m', - '\x1b[33m pip uninstall bedrock-agentcore-starter-toolkit\x1b[0m', - '', - ].join('\n') - ); - } -} catch { - // No agentcore binary found or unexpected error — nothing to do +const detected = detectOldToolkit((cmd) => + execSync(cmd, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }) +); + +if (detected.length > 0) { + console.error(formatErrorMessage(detected)); + process.exit(1); } diff --git a/src/cli/external-requirements/__tests__/check-old-cli.test.ts b/src/cli/external-requirements/__tests__/check-old-cli.test.ts new file mode 100644 index 00000000..eb0a459f --- /dev/null +++ b/src/cli/external-requirements/__tests__/check-old-cli.test.ts @@ -0,0 +1,224 @@ +import { + detectOldToolkit, + formatErrorMessage, + probeInstaller, + probePath, +} from '../../../../scripts/check-old-cli.lib.mjs'; +import { execSync } from 'node:child_process'; +import * as path from 'node:path'; +import { describe, expect, it } from 'vitest'; + +// --------------------------------------------------------------------------- +// probeInstaller +// --------------------------------------------------------------------------- +describe('probeInstaller', () => { + it('returns match when output contains the old toolkit', () => { + const exec = () => 'bedrock-agentcore-starter-toolkit 0.1.0\nsome-other-pkg 1.0.0'; + const result = probeInstaller('pip list', 'pip', 'pip uninstall bedrock-agentcore-starter-toolkit', exec); + expect(result).toEqual({ + installer: 'pip', + uninstallCmd: 'pip uninstall bedrock-agentcore-starter-toolkit', + }); + }); + + it('returns null when the old toolkit is not in output', () => { + const exec = () => 'some-other-pkg 1.0.0'; + const result = probeInstaller('pip list', 'pip', 'pip uninstall bedrock-agentcore-starter-toolkit', exec); + expect(result).toBeNull(); + }); + + it('returns null when the command throws', () => { + const exec = () => { + throw new Error('command not found'); + }; + const result = probeInstaller('pip list', 'pip', 'pip uninstall bedrock-agentcore-starter-toolkit', exec); + expect(result).toBeNull(); + }); +}); + +// --------------------------------------------------------------------------- +// probePath +// --------------------------------------------------------------------------- +describe('probePath', () => { + it('returns match when agentcore exists but --version fails (old Python CLI)', () => { + const exec = (cmd: string) => { + if (cmd === 'command -v agentcore') return '/usr/local/bin/agentcore'; + if (cmd === 'agentcore --version') throw new Error('exit code 1'); + return ''; + }; + const result = probePath(exec); + expect(result).toEqual({ + installer: 'PATH', + uninstallCmd: 'pip uninstall bedrock-agentcore-starter-toolkit', + }); + }); + + it('returns null when agentcore exists and --version succeeds (new CLI)', () => { + const exec = (cmd: string) => { + if (cmd === 'command -v agentcore') return '/usr/local/bin/agentcore'; + if (cmd === 'agentcore --version') return '1.0.0'; + return ''; + }; + expect(probePath(exec)).toBeNull(); + }); + + it('returns null when no agentcore binary is on PATH', () => { + const exec = () => { + throw new Error('command not found'); + }; + expect(probePath(exec)).toBeNull(); + }); +}); + +// --------------------------------------------------------------------------- +// detectOldToolkit +// --------------------------------------------------------------------------- +describe('detectOldToolkit', () => { + it('returns empty array when no installer has the old toolkit', () => { + const exec = () => 'some-pkg 1.0.0'; + expect(detectOldToolkit(exec)).toEqual([]); + }); + + it('returns single match for pip only', () => { + const exec = (cmd: string) => { + if (cmd === 'pip list') return 'bedrock-agentcore-starter-toolkit 0.1.0'; + return 'clean-output'; + }; + const result = detectOldToolkit(exec); + expect(result).toHaveLength(1); + expect(result[0]!.installer).toBe('pip'); + }); + + it('returns single match for pipx only', () => { + const exec = (cmd: string) => { + if (cmd === 'pipx list') return 'bedrock-agentcore-starter-toolkit 0.1.0'; + return 'clean-output'; + }; + const result = detectOldToolkit(exec); + expect(result).toHaveLength(1); + expect(result[0]!.installer).toBe('pipx'); + }); + + it('returns single match for uv only', () => { + const exec = (cmd: string) => { + if (cmd === 'uv tool list') return 'bedrock-agentcore-starter-toolkit 0.1.0'; + return 'clean-output'; + }; + const result = detectOldToolkit(exec); + expect(result).toHaveLength(1); + expect(result[0]!.installer).toBe('uv'); + }); + + it('returns multiple matches when installed via pip and pipx', () => { + const exec = () => 'bedrock-agentcore-starter-toolkit 0.1.0'; + const result = detectOldToolkit(exec); + expect(result).toHaveLength(3); + }); + + it('handles mixed results: one found, one missing command, one clean', () => { + const exec = (cmd: string) => { + if (cmd === 'pip list') return 'bedrock-agentcore-starter-toolkit 0.1.0'; + if (cmd === 'pipx list') throw new Error('command not found'); + return 'clean-output'; + }; + const result = detectOldToolkit(exec); + expect(result).toHaveLength(1); + expect(result[0]!.installer).toBe('pip'); + }); + + it('falls back to PATH detection when no package manager finds the toolkit', () => { + const exec = (cmd: string) => { + // All package-manager list commands return clean output + if (cmd.includes('list')) return 'clean-output'; + // PATH check: binary exists but --version fails + if (cmd === 'command -v agentcore') return '/usr/local/bin/agentcore'; + if (cmd === 'agentcore --version') throw new Error('exit code 1'); + return ''; + }; + const result = detectOldToolkit(exec); + expect(result).toHaveLength(1); + expect(result[0]!.installer).toBe('PATH'); + }); + + it('skips PATH fallback when a package manager already found the toolkit', () => { + const calls: string[] = []; + const exec = (cmd: string) => { + calls.push(cmd); + if (cmd === 'pip list') return 'bedrock-agentcore-starter-toolkit 0.1.0'; + return 'clean-output'; + }; + const result = detectOldToolkit(exec); + expect(result).toHaveLength(1); + expect(result[0]!.installer).toBe('pip'); + expect(calls).not.toContain('command -v agentcore'); + }); +}); + +// --------------------------------------------------------------------------- +// formatErrorMessage +// --------------------------------------------------------------------------- +describe('formatErrorMessage', () => { + it('shows correct uninstall command for a single installer', () => { + const msg = formatErrorMessage([ + { installer: 'pip', uninstallCmd: 'pip uninstall bedrock-agentcore-starter-toolkit' }, + ]); + expect(msg).toContain('pip uninstall bedrock-agentcore-starter-toolkit'); + expect(msg).toContain('installed via pip'); + }); + + it('shows all uninstall commands for multiple installers', () => { + const msg = formatErrorMessage([ + { installer: 'pip', uninstallCmd: 'pip uninstall bedrock-agentcore-starter-toolkit' }, + { installer: 'pipx', uninstallCmd: 'pipx uninstall bedrock-agentcore-starter-toolkit' }, + ]); + expect(msg).toContain('pip uninstall bedrock-agentcore-starter-toolkit'); + expect(msg).toContain('pipx uninstall bedrock-agentcore-starter-toolkit'); + }); + + it('contains bypass env var instruction', () => { + const msg = formatErrorMessage([ + { installer: 'pip', uninstallCmd: 'pip uninstall bedrock-agentcore-starter-toolkit' }, + ]); + expect(msg).toContain('AGENTCORE_SKIP_CONFLICT_CHECK=1'); + }); + + it('contains re-run instruction', () => { + const msg = formatErrorMessage([ + { installer: 'pip', uninstallCmd: 'pip uninstall bedrock-agentcore-starter-toolkit' }, + ]); + expect(msg).toContain('npm install -g @aws/agentcore'); + }); +}); + +// --------------------------------------------------------------------------- +// Entry-point integration (subprocess) +// --------------------------------------------------------------------------- +describe('check-old-cli.mjs entry point', () => { + const scriptPath = path.resolve(__dirname, '../../../../scripts/check-old-cli.mjs'); + + it('exits 0 when AGENTCORE_SKIP_CONFLICT_CHECK=1', () => { + // Should not throw (exit code 0) + execSync(`node ${scriptPath}`, { + env: { ...process.env, AGENTCORE_SKIP_CONFLICT_CHECK: '1' }, + stdio: 'pipe', + }); + }); + + it('exits 1 with error when old toolkit is detected', () => { + // If the old toolkit happens to be installed, verify exit 1 + stderr message. + // If not installed, verify exit 0 silently. + try { + execSync(`node ${scriptPath}`, { + env: { ...process.env, AGENTCORE_SKIP_CONFLICT_CHECK: undefined }, + stdio: 'pipe', + encoding: 'utf-8', + }); + // Exited 0 — old toolkit not present, that's fine + } catch (err: any) { + // Exited non-zero — old toolkit was detected + expect(err.status).toBe(1); + expect(err.stderr).toContain('bedrock-agentcore-starter-toolkit'); + expect(err.stderr).toContain('AGENTCORE_SKIP_CONFLICT_CHECK'); + } + }); +}); From ef7380605d5ed9bbce26aa91c6f029201c23ff67 Mon Sep 17 00:00:00 2001 From: Tejas Kashinath Date: Fri, 20 Mar 2026 12:02:40 -0400 Subject: [PATCH 2/6] style: fix prettier formatting in check-old-cli scripts --- scripts/check-old-cli.lib.mjs | 2 +- scripts/check-old-cli.mjs | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/scripts/check-old-cli.lib.mjs b/scripts/check-old-cli.lib.mjs index 737cdc21..c51f0db6 100644 --- a/scripts/check-old-cli.lib.mjs +++ b/scripts/check-old-cli.lib.mjs @@ -90,7 +90,7 @@ export function formatErrorMessage(detected) { ' npm install -g @aws/agentcore', '', 'To bypass this check, set AGENTCORE_SKIP_CONFLICT_CHECK=1', - '', + '' ); return lines.join('\n'); diff --git a/scripts/check-old-cli.mjs b/scripts/check-old-cli.mjs index 59e2e088..8dc7db52 100644 --- a/scripts/check-old-cli.mjs +++ b/scripts/check-old-cli.mjs @@ -1,11 +1,9 @@ -import { execSync } from 'node:child_process'; import { detectOldToolkit, formatErrorMessage } from './check-old-cli.lib.mjs'; +import { execSync } from 'node:child_process'; if (process.env.AGENTCORE_SKIP_CONFLICT_CHECK === '1') process.exit(0); -const detected = detectOldToolkit((cmd) => - execSync(cmd, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }) -); +const detected = detectOldToolkit(cmd => execSync(cmd, { encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] })); if (detected.length > 0) { console.error(formatErrorMessage(detected)); From 29e3fafcf4f0f37b5fd5bbced847d529ee246e70 Mon Sep 17 00:00:00 2001 From: Tejas Kashinath Date: Fri, 20 Mar 2026 12:15:40 -0400 Subject: [PATCH 3/6] fix: restore Windows support in probePath fallback detection Add platform parameter to probePath() so it uses `where agentcore` on Windows and `command -v agentcore` elsewhere, matching the original behavior. Add tests for both platforms. --- scripts/check-old-cli.lib.mjs | 5 ++-- .../__tests__/check-old-cli.test.ts | 28 +++++++++++++++++++ 2 files changed, 31 insertions(+), 2 deletions(-) diff --git a/scripts/check-old-cli.lib.mjs b/scripts/check-old-cli.lib.mjs index c51f0db6..75291276 100644 --- a/scripts/check-old-cli.lib.mjs +++ b/scripts/check-old-cli.lib.mjs @@ -31,9 +31,10 @@ export function probeInstaller(cmd, label, uninstallCmd, execSyncFn) { * the old Python CLI (which doesn't support --version). * Returns `{ installer, uninstallCmd }` when the old CLI is found, or `null`. */ -export function probePath(execSyncFn) { +export function probePath(execSyncFn, platform = process.platform) { + const whichCmd = platform === 'win32' ? 'where agentcore' : 'command -v agentcore'; try { - execSyncFn('command -v agentcore'); + execSyncFn(whichCmd); } catch { return null; // no agentcore binary on PATH } diff --git a/src/cli/external-requirements/__tests__/check-old-cli.test.ts b/src/cli/external-requirements/__tests__/check-old-cli.test.ts index eb0a459f..99381994 100644 --- a/src/cli/external-requirements/__tests__/check-old-cli.test.ts +++ b/src/cli/external-requirements/__tests__/check-old-cli.test.ts @@ -68,6 +68,34 @@ describe('probePath', () => { }; expect(probePath(exec)).toBeNull(); }); + + it('uses "where agentcore" on Windows', () => { + const calls: string[] = []; + const exec = (cmd: string) => { + calls.push(cmd); + if (cmd === 'where agentcore') return 'C:\\Python\\Scripts\\agentcore'; + if (cmd === 'agentcore --version') throw new Error('exit code 1'); + return ''; + }; + const result = probePath(exec, 'win32'); + expect(calls[0]).toBe('where agentcore'); + expect(result).toEqual({ + installer: 'PATH', + uninstallCmd: 'pip uninstall bedrock-agentcore-starter-toolkit', + }); + }); + + it('uses "command -v agentcore" on non-Windows', () => { + const calls: string[] = []; + const exec = (cmd: string) => { + calls.push(cmd); + if (cmd === 'command -v agentcore') return '/usr/local/bin/agentcore'; + if (cmd === 'agentcore --version') throw new Error('exit code 1'); + return ''; + }; + probePath(exec, 'linux'); + expect(calls[0]).toBe('command -v agentcore'); + }); }); // --------------------------------------------------------------------------- From 017a0d5ef72067e07eda9c9b2c9515e732d289cf Mon Sep 17 00:00:00 2001 From: Tejas Kashinath Date: Fri, 20 Mar 2026 13:00:37 -0400 Subject: [PATCH 4/6] fix: resolve XML entity expansion limit breaking container deploys Override @aws-sdk/xml-builder to 3.972.14 which sets maxTotalExpansions: Infinity when creating its XMLParser. The previous fast-xml-parser 5.5.7 override (from PR #577) introduced a default limit of 1000 entities, but large CloudFormation responses for container stacks exceed this (1175 entities), causing CDK deploy to fail with "Entity expansion limit exceeded". Also add deploy retry logic for container e2e tests (up to 3 attempts with 30s delay) and include stdout in error assertions since the CLI returns errors as JSON on stdout when using --json. --- e2e-tests/e2e-helper.ts | 29 ++++++++++++++++++++--------- package-lock.json | 28 ++++++++++++++-------------- package.json | 6 ++++-- 3 files changed, 38 insertions(+), 25 deletions(-) diff --git a/e2e-tests/e2e-helper.ts b/e2e-tests/e2e-helper.ts index 0ec88fb6..8eb9e28d 100644 --- a/e2e-tests/e2e-helper.ts +++ b/e2e-tests/e2e-helper.ts @@ -121,24 +121,35 @@ export function createE2ESuite(cfg: E2EConfig) { if (testDir) await rm(testDir, { recursive: true, force: true, maxRetries: 3, retryDelay: 1000 }); }, 600000); + // Container builds go through CodeBuild which is slower and more prone to transient failures. + const isContainerBuild = cfg.build === 'Container'; + const deployRetries = isContainerBuild ? 3 : 1; + const deployTimeout = isContainerBuild ? 900000 : 600000; + it.skipIf(!canRun)( 'deploys to AWS successfully', async () => { expect(projectPath, 'Project should have been created').toBeTruthy(); - const result = await runCLI(['deploy', '--yes', '--json'], projectPath, false); + await retry( + async () => { + const result = await runCLI(['deploy', '--yes', '--json'], projectPath, false); - if (result.exitCode !== 0) { - console.log('Deploy stdout:', result.stdout); - console.log('Deploy stderr:', result.stderr); - } + if (result.exitCode !== 0) { + console.log('Deploy stdout:', result.stdout); + console.log('Deploy stderr:', result.stderr); + } - expect(result.exitCode, `Deploy failed: ${result.stderr}`).toBe(0); + expect(result.exitCode, `Deploy failed (stderr: ${result.stderr}, stdout: ${result.stdout})`).toBe(0); - const json = parseJsonOutput(result.stdout) as { success: boolean }; - expect(json.success, 'Deploy should report success').toBe(true); + const json = parseJsonOutput(result.stdout) as { success: boolean }; + expect(json.success, 'Deploy should report success').toBe(true); + }, + deployRetries, + 30000 + ); }, - 600000 + deployTimeout ); it.skipIf(!canRun)( diff --git a/package-lock.json b/package-lock.json index 2d600881..ed36c6fb 100644 --- a/package-lock.json +++ b/package-lock.json @@ -2671,13 +2671,13 @@ } }, "node_modules/@aws-sdk/xml-builder": { - "version": "3.972.10", - "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.10.tgz", - "integrity": "sha512-OnejAIVD+CxzyAUrVic7lG+3QRltyja9LoNqCE/1YVs8ichoTbJlVSaZ9iSMcnHLyzrSNtvaOGjSDRP+d/ouFA==", + "version": "3.972.14", + "resolved": "https://registry.npmjs.org/@aws-sdk/xml-builder/-/xml-builder-3.972.14.tgz", + "integrity": "sha512-G/Yd8Bnnyh8QrqLf8jWJbixEnScUFW24e/wOBGYdw1Cl4r80KX/DvHyM2GVZ2vTp7J4gTEr8IXJlTadA8+UfuQ==", "license": "Apache-2.0", "dependencies": { - "@smithy/types": "^4.13.0", - "fast-xml-parser": "5.4.1", + "@smithy/types": "^4.13.1", + "fast-xml-parser": "5.5.6", "tslib": "^2.6.2" }, "engines": { @@ -4986,9 +4986,9 @@ } }, "node_modules/@smithy/types": { - "version": "4.13.0", - "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.0.tgz", - "integrity": "sha512-COuLsZILbbQsdrwKQpkkpyep7lCsByxwj7m0Mg5v66/ZTyenlfBc40/QFQ5chO0YN/PNEH1Bi3fGtfXPnYNeDw==", + "version": "4.13.1", + "resolved": "https://registry.npmjs.org/@smithy/types/-/types-4.13.1.tgz", + "integrity": "sha512-787F3yzE2UiJIQ+wYW1CVg2odHjmaWLGksnKQHUrK/lYZSEcy1msuLVvxaR/sI2/aDe9U+TBuLsXnr3vod1g0g==", "license": "Apache-2.0", "dependencies": { "tslib": "^2.6.2" @@ -11638,9 +11638,9 @@ } }, "node_modules/path-expression-matcher": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.1.3.tgz", - "integrity": "sha512-qdVgY8KXmVdJZRSS1JdEPOKPdTiEK/pi0RkcT2sw1RhXxohdujUlJFPuS1TSkevZ9vzd3ZlL7ULl1MHGTApKzQ==", + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/path-expression-matcher/-/path-expression-matcher-1.2.0.tgz", + "integrity": "sha512-DwmPWeFn+tq7TiyJ2CxezCAirXjFxvaiD03npak3cRjlP9+OjTmSy1EpIrEbh+l6JgUundniloMLDQ/6VTdhLQ==", "funding": [ { "type": "github", @@ -13146,9 +13146,9 @@ } }, "node_modules/strnum": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.0.tgz", - "integrity": "sha512-Y7Bj8XyJxnPAORMZj/xltsfo55uOiyHcU2tnAVzHUnSJR/KsEX+9RoDeXEnsXtl/CX4fAcrt64gZ13aGaWPeBg==", + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/strnum/-/strnum-2.2.1.tgz", + "integrity": "sha512-BwRvNd5/QoAtyW1na1y1LsJGQNvRlkde6Q/ipqqEaivoMdV+B1OMOTVdwR+N/cwVUcIt9PYyHmV8HyexCZSupg==", "funding": [ { "type": "github", diff --git a/package.json b/package.json index 6354176c..4ce2d0e0 100644 --- a/package.json +++ b/package.json @@ -132,11 +132,13 @@ }, "overridesComments": { "minimatch": "GHSA-7r86-cg39-jmmj, GHSA-23c5-xmqv-rm74: minimatch 10.0.0-10.2.2 has ReDoS vulnerabilities. Multiple transitive deps (eslint, typescript-eslint, eslint-plugin-import, eslint-plugin-react, prettier-plugin-sort-imports, aws-cdk-lib) pin older versions. Remove this override once upstream packages update their minimatch dependency to >=10.2.3.", - "fast-xml-parser": "GHSA-8gc5-j5rx-235r, GHSA-jp2q-39xq-3w4g: fast-xml-parser <=5.5.6 has entity expansion bypass (CVE-2026-33036, CVE-2026-33349). Transitive via @aws-sdk/xml-builder. Remove once @aws-sdk updates to fast-xml-parser >=5.5.7." + "fast-xml-parser": "GHSA-8gc5-j5rx-235r, GHSA-jp2q-39xq-3w4g: fast-xml-parser <=5.5.6 has entity expansion bypass (CVE-2026-33036, CVE-2026-33349). Transitive via @aws-sdk/xml-builder. Remove once @aws-sdk updates to fast-xml-parser >=5.5.7.", + "@aws-sdk/xml-builder": "@aws-sdk/xml-builder <3.972.14 uses fast-xml-parser without configuring processEntities.maxTotalExpansions, so the 5.5.7 default of 1000 breaks large CloudFormation responses. 3.972.14+ sets maxTotalExpansions: Infinity (safe for trusted AWS API responses). Remove once @aws-sdk/core updates to >=3.973.22." }, "overrides": { "minimatch": "10.2.4", - "fast-xml-parser": "5.5.7" + "fast-xml-parser": "5.5.7", + "@aws-sdk/xml-builder": "3.972.14" }, "engines": { "node": ">=20" From 7add9304498db35b1c02dd9a6f9c61c915cc7846 Mon Sep 17 00:00:00 2001 From: Tejas Kashinath Date: Fri, 20 Mar 2026 13:12:11 -0400 Subject: [PATCH 5/6] docs: clarify install error behavior and bypass in README Let customers know that npm install will fail with a clear error if the old toolkit is detected, and mention the AGENTCORE_SKIP_CONFLICT_CHECK env var for CI environments. --- README.md | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 7dbe586c..97f25dfa 100644 --- a/README.md +++ b/README.md @@ -17,14 +17,17 @@ AgentCore with minimal configuration. ## Installation -> **Upgrading from the Bedrock AgentCore Starter Toolkit?** The old Python CLI conflicts with this package. Uninstall it -> first using whichever tool you used to install it: +> **Upgrading from the Bedrock AgentCore Starter Toolkit?** The old Python CLI conflicts with this package. If it is +> still installed, `npm install` will fail with an error telling you which package manager has it. Uninstall it first +> using whichever tool you originally used: > > ```bash > pip uninstall bedrock-agentcore-starter-toolkit # if installed via pip > pipx uninstall bedrock-agentcore-starter-toolkit # if installed via pipx > uv tool uninstall bedrock-agentcore-starter-toolkit # if installed via uv > ``` +> +> If you need to bypass the check (for example, in CI), set `AGENTCORE_SKIP_CONFLICT_CHECK=1` before installing. ```bash npm install -g @aws/agentcore From 3cdba8b8600e80f20e4bef043f46b833eb2de96e Mon Sep 17 00:00:00 2001 From: Tejas Kashinath Date: Fri, 20 Mar 2026 17:36:20 -0400 Subject: [PATCH 6/6] fix: address code review feedback on conflict detection - Use regex /^bedrock-agentcore-starter-toolkit\s/m instead of includes() to avoid matching superstring package names - Skip node_modules-installed binaries in PATH fallback to prevent false positives from broken new CLI installs - Replace environment-dependent integration test with deterministic stub that always exercises the exit-1 path --- scripts/check-old-cli.lib.mjs | 10 +++- .../__tests__/check-old-cli.test.ts | 47 +++++++++++++++---- 2 files changed, 45 insertions(+), 12 deletions(-) diff --git a/scripts/check-old-cli.lib.mjs b/scripts/check-old-cli.lib.mjs index 75291276..3401d8d7 100644 --- a/scripts/check-old-cli.lib.mjs +++ b/scripts/check-old-cli.lib.mjs @@ -17,7 +17,7 @@ const INSTALLERS = [ export function probeInstaller(cmd, label, uninstallCmd, execSyncFn) { try { const output = execSyncFn(cmd); - if (output.includes('bedrock-agentcore-starter-toolkit')) { + if (/^bedrock-agentcore-starter-toolkit\s/m.test(output)) { return { installer: label, uninstallCmd }; } } catch { @@ -33,11 +33,17 @@ export function probeInstaller(cmd, label, uninstallCmd, execSyncFn) { */ export function probePath(execSyncFn, platform = process.platform) { const whichCmd = platform === 'win32' ? 'where agentcore' : 'command -v agentcore'; + let binaryPath; try { - execSyncFn(whichCmd); + binaryPath = execSyncFn(whichCmd).trim(); } catch { return null; // no agentcore binary on PATH } + // Skip binaries installed via npm/node — a broken new CLI install would also + // fail --version, and we don't want to block reinstallation. + if (/node_modules|\/npm\/|\/nvm\/|\/fnm\/|\\npm\\/.test(binaryPath)) { + return null; + } try { execSyncFn('agentcore --version'); return null; // --version succeeded — this is the new CLI diff --git a/src/cli/external-requirements/__tests__/check-old-cli.test.ts b/src/cli/external-requirements/__tests__/check-old-cli.test.ts index 99381994..86a1c656 100644 --- a/src/cli/external-requirements/__tests__/check-old-cli.test.ts +++ b/src/cli/external-requirements/__tests__/check-old-cli.test.ts @@ -5,6 +5,7 @@ import { probePath, } from '../../../../scripts/check-old-cli.lib.mjs'; import { execSync } from 'node:child_process'; +import * as fs from 'node:fs'; import * as path from 'node:path'; import { describe, expect, it } from 'vitest'; @@ -27,6 +28,12 @@ describe('probeInstaller', () => { expect(result).toBeNull(); }); + it('does not match a package whose name is a superstring of the toolkit', () => { + const exec = () => 'bedrock-agentcore-starter-toolkit-extra 1.0.0'; + const result = probeInstaller('pip list', 'pip', 'pip uninstall bedrock-agentcore-starter-toolkit', exec); + expect(result).toBeNull(); + }); + it('returns null when the command throws', () => { const exec = () => { throw new Error('command not found'); @@ -85,6 +92,15 @@ describe('probePath', () => { }); }); + it('returns null when binary is inside node_modules (broken new CLI)', () => { + const exec = (cmd: string) => { + if (cmd === 'command -v agentcore') return '/usr/local/lib/node_modules/@aws/agentcore/bin/agentcore'; + if (cmd === 'agentcore --version') throw new Error('exit code 1'); + return ''; + }; + expect(probePath(exec)).toBeNull(); + }); + it('uses "command -v agentcore" on non-Windows', () => { const calls: string[] = []; const exec = (cmd: string) => { @@ -232,21 +248,32 @@ describe('check-old-cli.mjs entry point', () => { }); }); - it('exits 1 with error when old toolkit is detected', () => { - // If the old toolkit happens to be installed, verify exit 1 + stderr message. - // If not installed, verify exit 0 silently. + it('exits 1 with actionable error when old toolkit is detected', () => { + // Use a wrapper script that stubs pip to report the old toolkit, guaranteeing + // the exit-1 path is always exercised regardless of the test machine. + const scriptsDir = path.resolve(__dirname, '../../../../scripts'); + const wrapperPath = path.join(scriptsDir, '_test-stub-detect.mjs'); + fs.writeFileSync( + wrapperPath, + [ + `import { detectOldToolkit, formatErrorMessage } from './check-old-cli.lib.mjs';`, + `const detected = detectOldToolkit((cmd) => {`, + ` if (cmd === 'pip list') return 'bedrock-agentcore-starter-toolkit 0.1.0';`, + ` throw new Error('not found');`, + `});`, + `if (detected.length > 0) { console.error(formatErrorMessage(detected)); process.exit(1); }`, + ].join('\n') + ); try { - execSync(`node ${scriptPath}`, { - env: { ...process.env, AGENTCORE_SKIP_CONFLICT_CHECK: undefined }, - stdio: 'pipe', - encoding: 'utf-8', - }); - // Exited 0 — old toolkit not present, that's fine + execSync(`node ${wrapperPath}`, { stdio: 'pipe', encoding: 'utf-8' }); + expect.unreachable('Should have exited with code 1'); } catch (err: any) { - // Exited non-zero — old toolkit was detected expect(err.status).toBe(1); expect(err.stderr).toContain('bedrock-agentcore-starter-toolkit'); expect(err.stderr).toContain('AGENTCORE_SKIP_CONFLICT_CHECK'); + expect(err.stderr).toContain('pip uninstall'); + } finally { + fs.unlinkSync(wrapperPath); } }); });