diff --git a/src/cli/operations/dev/__tests__/load-dev-env.test.ts b/src/cli/operations/dev/__tests__/load-dev-env.test.ts new file mode 100644 index 000000000..a5e8d5b54 --- /dev/null +++ b/src/cli/operations/dev/__tests__/load-dev-env.test.ts @@ -0,0 +1,75 @@ +import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; + +const { mockReadDeployedState, mockReadAWSDeploymentTargets, mockReadProjectSpec, mockFindConfigRoot, mockReadEnvFile } = + vi.hoisted(() => ({ + mockReadDeployedState: vi.fn(), + mockReadAWSDeploymentTargets: vi.fn(), + mockReadProjectSpec: vi.fn(), + mockFindConfigRoot: vi.fn(), + mockReadEnvFile: vi.fn(), + })); + +const libMock = { + ConfigIO: class { + readDeployedState = mockReadDeployedState; + readAWSDeploymentTargets = mockReadAWSDeploymentTargets; + readProjectSpec = mockReadProjectSpec; + }, + findConfigRoot: mockFindConfigRoot, + readEnvFile: mockReadEnvFile, +}; + +vi.mock('../../../../lib', () => libMock); +vi.mock('../../../../lib/index.js', () => libMock); + +const { loadDevEnv } = await import('../load-dev-env.js'); + +const deployedStateInUsWest2 = { + targets: { default: { resources: { memories: {} } } }, +}; +const usWest2Targets = [{ name: 'default', region: 'us-west-2' }]; + +describe('loadDevEnv region injection (issue #1457)', () => { + beforeEach(() => { + mockFindConfigRoot.mockReturnValue('/project/agentcore'); + mockReadEnvFile.mockResolvedValue({}); + mockReadProjectSpec.mockResolvedValue({ agentCoreGateways: [] }); + }); + + afterEach(() => { + vi.restoreAllMocks(); + delete process.env.AWS_REGION; + delete process.env.AWS_DEFAULT_REGION; + }); + + it('injects the deployed target region even when the shell region differs', async () => { + process.env.AWS_REGION = 'us-east-1'; + mockReadDeployedState.mockResolvedValue(deployedStateInUsWest2); + mockReadAWSDeploymentTargets.mockResolvedValue(usWest2Targets); + + const { envVars } = await loadDevEnv('/project'); + + expect(envVars.AWS_REGION).toBe('us-west-2'); + expect(envVars.AWS_DEFAULT_REGION).toBe('us-west-2'); + }); + + it('lets an explicit AWS_REGION in the project .env override the target region', async () => { + mockReadDeployedState.mockResolvedValue(deployedStateInUsWest2); + mockReadAWSDeploymentTargets.mockResolvedValue(usWest2Targets); + mockReadEnvFile.mockResolvedValue({ AWS_REGION: 'eu-west-1' }); + + const { envVars } = await loadDevEnv('/project'); + + expect(envVars.AWS_REGION).toBe('eu-west-1'); + }); + + it('omits region vars (and does not throw) when aws-targets is unreadable', async () => { + mockReadDeployedState.mockRejectedValue(new Error('no state')); + mockReadAWSDeploymentTargets.mockRejectedValue(new Error('no targets')); + + const { envVars } = await loadDevEnv('/project'); + + expect(envVars.AWS_REGION).toBeUndefined(); + expect(envVars.AWS_DEFAULT_REGION).toBeUndefined(); + }); +}); diff --git a/src/cli/operations/dev/load-dev-env.ts b/src/cli/operations/dev/load-dev-env.ts index d68204231..b2653f42f 100644 --- a/src/cli/operations/dev/load-dev-env.ts +++ b/src/cli/operations/dev/load-dev-env.ts @@ -3,6 +3,7 @@ import type { AgentEnvSpec } from '../../../schema'; import { getGatewayEnvVars } from './gateway-env.js'; import { getMemoryEnvVars } from './memory-env.js'; import { getPaymentEnvVars } from './payment-env.js'; +import { getRegionEnvVars } from './region-env.js'; export interface DevEnv { /** Merged env vars: deployed-state (gateway + memory + payment) first, then .env overrides */ @@ -21,12 +22,13 @@ export interface DevEnv { export async function loadDevEnv(workingDir: string, runtime?: AgentEnvSpec): Promise { const configRoot = findConfigRoot(workingDir); const dotEnvVars = configRoot ? await readEnvFile(configRoot) : {}; + const regionEnvVars = await getRegionEnvVars(); const gatewayEnvVars = await getGatewayEnvVars(); const memoryEnvVars = await getMemoryEnvVars(); const paymentEnvVars = await getPaymentEnvVars(runtime); return { - envVars: { ...gatewayEnvVars, ...memoryEnvVars, ...paymentEnvVars, ...dotEnvVars }, + envVars: { ...regionEnvVars, ...gatewayEnvVars, ...memoryEnvVars, ...paymentEnvVars, ...dotEnvVars }, deployedMemoryCount: Object.keys(memoryEnvVars).length, }; } diff --git a/src/cli/operations/dev/region-env.ts b/src/cli/operations/dev/region-env.ts new file mode 100644 index 000000000..685c9d301 --- /dev/null +++ b/src/cli/operations/dev/region-env.ts @@ -0,0 +1,31 @@ +import { ConfigIO } from '../../../lib/index.js'; + +/** + * Resolve the deployed target's region as AWS_REGION / AWS_DEFAULT_REGION env + * vars for dev mode. Deployed resources (e.g. AgentCore Memory) live in the + * region recorded in aws-targets.json, but the local agent's AWS SDK falls back + * to the shell's AWS_REGION otherwise — so a shell region that is unset or + * differs from the deployed region makes calls like ListEvents hit the wrong + * region and fail with "Memory not found" (see issue #1457). + * + * Uses the same target the dev path already resolves: the first target in + * deployed state, matched against aws-targets.json by name. + */ +export async function getRegionEnvVars(): Promise> { + const configIO = new ConfigIO(); + + try { + const deployedState = await configIO.readDeployedState(); + const targetName = Object.keys(deployedState?.targets ?? {})[0]; + if (!targetName) return {}; + + const awsTargets = await configIO.readAWSDeploymentTargets(); + const region = awsTargets.find(t => t.name === targetName)?.region; + if (!region) return {}; + + return { AWS_REGION: region, AWS_DEFAULT_REGION: region }; + } catch { + // No deployed state or targets — leave region resolution to the SDK default chain + return {}; + } +}