Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions src/cli/__tests__/errors.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { AgentAlreadyExistsError, toError } from '../../lib';
import {
formatError,
getErrorMessage,
isChangesetInProgressError,
isExpiredTokenError,
Expand Down Expand Up @@ -39,6 +40,18 @@ describe('errors', () => {
});
});

describe('formatError', () => {
it('surfaces the cause chain that getErrorMessage drops (CDK synth stderr)', () => {
const causeText = 'AgentCore CDK synthesis failed: memory strategy "foo" is invalid';
const err = new Error('CDK synth failed: node dist/bin/cdk.js: Subprocess exited with error 1', {
cause: new Error(causeText),
});

expect(getErrorMessage(err)).not.toContain(causeText);
expect(formatError(err)).toContain(causeText);
});
});

describe('isExpiredTokenError', () => {
// Test ALL error codes in EXPIRED_TOKEN_ERROR_CODES via error.name
const allErrorCodes = [
Expand Down
4 changes: 2 additions & 2 deletions src/cli/commands/deploy/command.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { ConfigIO, serializeResult } from '../../../lib';
import { COMMAND_DESCRIPTIONS } from '../../constants';
import { getErrorMessage } from '../../errors';
import { formatError, getErrorMessage } from '../../errors';
import { withCommandRunTelemetry } from '../../telemetry/cli-command-run.js';
import { renderTUI } from '../../tui';
import { requireProject, requireTTY } from '../../tui/guards';
Expand Down Expand Up @@ -57,7 +57,7 @@ async function handleDeployCLI(options: DeployOptions): Promise<void> {
if (options.json) {
console.log(JSON.stringify(serializeResult(deployResult)));
} else {
console.error(deployResult.error.message);
console.error(formatError(deployResult.error));
if (deployResult.logPath) {
console.error(`Log: ${deployResult.logPath}`);
}
Expand Down
3 changes: 2 additions & 1 deletion src/cli/commands/import/command.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { ValidationError } from '../../../lib';
import { ANSI } from '../../constants';
import { formatError } from '../../errors';
import { withCommandRunTelemetry } from '../../telemetry/cli-command-run.js';
import { handleImport } from './actions';
import { registerImportEvaluator } from './import-evaluator';
Expand Down Expand Up @@ -144,7 +145,7 @@ export const registerImport = (program: Command) => {
console.log(`Log: ${result.logPath}`);
}
} else {
console.error(`\n${red}[error]${reset} Import failed: ${result.error.message}`);
console.error(`\n${red}[error]${reset} Import failed: ${formatError(result.error)}`);
if (result.logPath) {
console.error(`Log: ${result.logPath}`);
}
Expand Down
3 changes: 2 additions & 1 deletion src/cli/commands/import/import-evaluator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
listAllOnlineEvaluationConfigs,
} from '../../aws/agentcore-control';
import { ANSI } from '../../constants';
import { formatError } from '../../errors';
import { withCommandRunTelemetry } from '../../telemetry/cli-command-run.js';
import { failResult, parseAndValidateArn } from './import-utils';
import { executeResourceImport } from './resource-import';
Expand Down Expand Up @@ -164,7 +165,7 @@ export function registerImportEvaluator(importCmd: Command): void {
console.log(` ID: ${result.resourceId}`);
console.log('');
} else {
console.error(`\n${ANSI.red}[error]${ANSI.reset} ${result.error.message}`);
console.error(`\n${ANSI.red}[error]${ANSI.reset} ${formatError(result.error)}`);
if (result.logPath) {
console.error(`Log: ${result.logPath}`);
}
Expand Down
4 changes: 2 additions & 2 deletions src/cli/commands/import/import-gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
listAllGateways,
} from '../../aws/agentcore-control';
import { ANSI } from '../../constants';
import { isAccessDeniedError } from '../../errors';
import { formatError, isAccessDeniedError } from '../../errors';
import { withCommandRunTelemetry } from '../../telemetry/cli-command-run.js';
import { executeCdkImportPipeline } from './import-pipeline';
import {
Expand Down Expand Up @@ -746,7 +746,7 @@ export function registerImportGateway(importCmd: Command): void {
console.log(` agentcore fetch access ${ANSI.dim}Get gateway URL and token${ANSI.reset}`);
console.log('');
} else {
console.error(`\n${ANSI.red}[error]${ANSI.reset} ${result.error.message}`);
console.error(`\n${ANSI.red}[error]${ANSI.reset} ${formatError(result.error)}`);
if (result.logPath) {
console.error(`Log: ${result.logPath}`);
}
Expand Down
3 changes: 2 additions & 1 deletion src/cli/commands/import/import-memory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { IndexedKeyTypeSchema } from '../../../schema';
import type { MemoryDetail, MemorySummary } from '../../aws/agentcore-control';
import { getMemoryDetail, listAllMemories } from '../../aws/agentcore-control';
import { ANSI } from '../../constants';
import { formatError } from '../../errors';
import { withCommandRunTelemetry } from '../../telemetry/cli-command-run.js';
import { parseAndValidateArn } from './import-utils';
import { executeResourceImport } from './resource-import';
Expand Down Expand Up @@ -142,7 +143,7 @@ export function registerImportMemory(importCmd: Command): void {
console.log(` ID: ${result.resourceId}`);
console.log('');
} else {
console.error(`\n${ANSI.red}[error]${ANSI.reset} ${result.error.message}`);
console.error(`\n${ANSI.red}[error]${ANSI.reset} ${formatError(result.error)}`);
if (result.logPath) {
console.error(`Log: ${result.logPath}`);
}
Expand Down
3 changes: 2 additions & 1 deletion src/cli/commands/import/import-online-eval.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import {
} from '../../aws/agentcore-control';
import { arnPrefix } from '../../aws/partition';
import { ANSI } from '../../constants';
import { formatError } from '../../errors';
import { withCommandRunTelemetry } from '../../telemetry/cli-command-run.js';
import { failResult, findResourceInDeployedState, parseAndValidateArn } from './import-utils';
import { executeResourceImport } from './resource-import';
Expand Down Expand Up @@ -219,7 +220,7 @@ export function registerImportOnlineEval(importCmd: Command): void {
console.log(` ID: ${result.resourceId}`);
console.log('');
} else {
console.error(`\n${ANSI.red}[error]${ANSI.reset} ${result.error.message}`);
console.error(`\n${ANSI.red}[error]${ANSI.reset} ${formatError(result.error)}`);
if (result.logPath) {
console.error(`Log: ${result.logPath}`);
}
Expand Down
3 changes: 2 additions & 1 deletion src/cli/commands/import/import-runtime.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { AgentEnvSpec } from '../../../schema';
import type { AgentRuntimeDetail, AgentRuntimeSummary } from '../../aws/agentcore-control';
import { getAgentRuntimeDetail, listAllAgentRuntimes } from '../../aws/agentcore-control';
import { ANSI } from '../../constants';
import { formatError } from '../../errors';
import { withCommandRunTelemetry } from '../../telemetry/cli-command-run.js';
import { copyAgentSource, failResult, parseAndValidateArn } from './import-utils';
import { executeResourceImport } from './resource-import';
Expand Down Expand Up @@ -227,7 +228,7 @@ export function registerImportRuntime(importCmd: Command): void {
console.log(` agentcore invoke ${ANSI.dim}Test your agent${ANSI.reset}`);
console.log('');
} else {
console.error(`\n${ANSI.red}[error]${ANSI.reset} ${result.error.message}`);
console.error(`\n${ANSI.red}[error]${ANSI.reset} ${formatError(result.error)}`);
if (result.logPath) {
console.error(`Log: ${result.logPath}`);
}
Expand Down
27 changes: 27 additions & 0 deletions src/cli/errors.ts
Original file line number Diff line number Diff line change
@@ -1,11 +1,38 @@
/**
* Converts an unknown error to a string message.
* Handles Error instances and other thrown values consistently.
*
* Note: this intentionally returns only `err.message` and does NOT walk the
* cause chain. Use `formatError` when displaying an error to the user so the
* underlying cause (e.g. CDK synth child stderr) is surfaced.
*/
export function getErrorMessage(err: unknown): string {
return err instanceof Error ? err.message : String(err);
}

/**
* Format an error for user display. Shows the message and the full cause chain, but NOT the raw JS
* stack trace — dumping `err.stack` (minified dist frames) to users is noise for the common case
* (config/validation errors). Set AGENTCORE_DEBUG=1 to include the stack trace for debugging.
*
* Unlike `getErrorMessage`, this recurses into `err.cause`, so actionable detail attached by lower
* layers (e.g. the CDK toolkit wrapper preserving the synth child's stderr on `err.cause`) reaches
* the user instead of being dropped behind a generic top-level message.
*/
export function formatError(err: unknown): string {
if (err instanceof Error) {
const lines = [err.message];
if (err.stack && process.env.AGENTCORE_DEBUG) {
lines.push('', 'Stack trace:', err.stack);
}
if (err.cause) {
lines.push('', 'Caused by:', formatError(err.cause));
}
return lines.join('\n');
}
return String(err);
}

/**
* Checks if an error is an AWS access denied error.
* Returns true for AccessDeniedException or AccessDenied error codes.
Expand Down
23 changes: 5 additions & 18 deletions src/cli/operations/deploy/preflight.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { validateAwsCredentials } from '../../aws/account';
import { LocalCdkProject } from '../../cdk/local-cdk-project';
import { CdkToolkitWrapper, createCdkToolkitWrapper, silentIoHost } from '../../cdk/toolkit-lib';
import { checkBootstrapStatus, checkStacksStatus, formatCdkEnvironment } from '../../cloudformation';
import { formatError } from '../../errors';
import { cleanupStaleLockFiles } from '../../tui/utils';
import type { IIoHost } from '@aws-cdk/toolkit-lib';
import { existsSync, readFileSync } from 'node:fs';
Expand Down Expand Up @@ -39,24 +40,10 @@ export interface StackStatusCheckResult {
message?: string;
}

/**
* Format an error for user display. Shows the message and the cause chain, but NOT the raw JS stack
* trace — dumping `err.stack` (minified dist/cli/index.mjs frames) to users is noise for the common
* case (config/validation errors). Set AGENTCORE_DEBUG=1 to include the stack trace for debugging.
*/
export function formatError(err: unknown): string {
if (err instanceof Error) {
const lines = [err.message];
if (err.stack && process.env.AGENTCORE_DEBUG) {
lines.push('', 'Stack trace:', err.stack);
}
if (err.cause) {
lines.push('', 'Caused by:', formatError(err.cause));
}
return lines.join('\n');
}
return String(err);
}
// Re-exported from src/cli/errors.ts so existing importers (TUI hooks, operations/deploy/index.ts,
// preflight tests) keep their import path. Lives in errors.ts so the thin command/display layers can
// reuse it without pulling in this heavy deploy module.
export { formatError };

/**
* Validates the CDK project and loads configuration.
Expand Down
Loading