From 329070a661de23737d0d2e99054a7f7a7daf3c37 Mon Sep 17 00:00:00 2001 From: Hweinstock Date: Fri, 11 Sep 2026 17:31:26 +0000 Subject: [PATCH 1/4] fix(project): raise ResourceNotFoundError for absent project resources --- src/core/project/manager.tsx | 16 ++++++++-------- .../project/add/gateway-target/index.test.ts | 4 ++-- src/handlers/project/add/gateway-target/index.ts | 4 ++-- src/handlers/project/add/gateway/index.test.ts | 2 +- src/handlers/project/add/gateway/index.ts | 6 +++--- .../project/add/payment-connector/index.test.ts | 4 ++-- .../project/add/payment-connector/index.ts | 6 +++--- .../project/add/policy-engine/index.test.ts | 2 +- src/handlers/project/add/policy/index.test.ts | 2 +- 9 files changed, 23 insertions(+), 23 deletions(-) diff --git a/src/core/project/manager.tsx b/src/core/project/manager.tsx index 7495190d4..bda377b96 100644 --- a/src/core/project/manager.tsx +++ b/src/core/project/manager.tsx @@ -308,8 +308,8 @@ export class FsProjectManager implements ProjectManager { (candidate) => candidate.name === input.managerName, ); if (!manager) { - throw new InputValidationError( - `payment manager '${input.managerName}' does not exist in this project`, + throw new ResourceNotFoundError( + `no payment-manager named '${input.managerName}' exists in this project`, ); } if (manager.connectors.some((connector) => connector.name === input.resourceConfig.name)) { @@ -432,8 +432,8 @@ export class FsProjectManager implements ProjectManager { (candidate) => candidate.name === gatewayName, ); if (!gateway) { - throw new InputValidationError( - `gateway '${gatewayName}' does not exist in this project; check agentCoreGateways in agentcore.json`, + throw new ResourceNotFoundError( + `no gateway named '${gatewayName}' exists in this project`, ); } gateway.policyEngineConfiguration = { @@ -448,8 +448,8 @@ export class FsProjectManager implements ProjectManager { (candidate) => candidate.name === input.engineName, ); if (!engine) { - throw new InputValidationError( - `policy engine '${input.engineName}' does not exist in this project; check policyEngines in agentcore.json`, + throw new ResourceNotFoundError( + `no policy-engine named '${input.engineName}' exists in this project`, ); } engine.policies.push(parseResource(PolicySchema, input.resourceConfig)); @@ -460,8 +460,8 @@ export class FsProjectManager implements ProjectManager { (gateway) => gateway.name === input.gatewayName, ); if (gatewayIndex < 0) { - throw new InputValidationError( - `gateway '${input.gatewayName}' does not exist in this project; check agentCoreGateways in agentcore.json`, + throw new ResourceNotFoundError( + `no gateway named '${input.gatewayName}' exists in this project`, ); } projectSpec.agentCoreGateways[gatewayIndex]!.targets.push(input.resourceConfig); diff --git a/src/handlers/project/add/gateway-target/index.test.ts b/src/handlers/project/add/gateway-target/index.test.ts index 33b231566..9faf0f41c 100644 --- a/src/handlers/project/add/gateway-target/index.test.ts +++ b/src/handlers/project/add/gateway-target/index.test.ts @@ -316,7 +316,7 @@ describe("project add gateway-target", () => { [ "unknown credential", endpointFlags("--outbound-auth", "oauth", "--credential-name", "missing"), - "does not exist in credentials[]", + "no credential named 'missing' exists in this project", ], [ "credential with wrong type", @@ -326,7 +326,7 @@ describe("project add gateway-target", () => { [ "unknown Gateway", ["--gateway", "missing", "--name", "target", "--endpoint", ENDPOINT], - "does not exist in this project; check agentCoreGateways in agentcore.json", + "no gateway named 'missing' exists in this project", ], ])("rejects %s", async (_label, flags, message) => { await projectWithCredentials(); diff --git a/src/handlers/project/add/gateway-target/index.ts b/src/handlers/project/add/gateway-target/index.ts index 04863b667..09472404d 100644 --- a/src/handlers/project/add/gateway-target/index.ts +++ b/src/handlers/project/add/gateway-target/index.ts @@ -1,5 +1,5 @@ import z from "zod"; -import { InputValidationError } from "../../../../errors"; +import { InputValidationError, ResourceNotFoundError } from "../../../../errors"; import { SourceResolver } from "../../../../io"; import type { Credential } from "../../../../projectSchemas/credential"; import { @@ -180,7 +180,7 @@ function validateTargetCredential(project: Project, target: AgentCoreGatewayTarg function requireCredential(project: Project, name: string): Credential { const credential = project.spec.credentials.find((candidate) => candidate.name === name); if (!credential) { - throw new InputValidationError(`credential '${name}' does not exist in credentials[]`); + throw new ResourceNotFoundError(`no credential named '${name}' exists in this project`); } return credential; } diff --git a/src/handlers/project/add/gateway/index.test.ts b/src/handlers/project/add/gateway/index.test.ts index 33eb811b5..feb1670a1 100644 --- a/src/handlers/project/add/gateway/index.test.ts +++ b/src/handlers/project/add/gateway/index.test.ts @@ -179,7 +179,7 @@ describe("project add gateway", () => { "--policy-engine-mode", "enforce", ], - "does not exist in policyEngines[]", + "no policy-engine named 'Missing' exists in this project", ], [ "CUSTOM_JWT without configuration", diff --git a/src/handlers/project/add/gateway/index.ts b/src/handlers/project/add/gateway/index.ts index 507b7eb5c..5fe2c702d 100644 --- a/src/handlers/project/add/gateway/index.ts +++ b/src/handlers/project/add/gateway/index.ts @@ -1,5 +1,5 @@ import z from "zod"; -import { InputValidationError } from "../../../../errors"; +import { InputValidationError, ResourceNotFoundError } from "../../../../errors"; import { SourceResolver } from "../../../../io"; import { GatewayAuthorizerConfigSchema } from "../../../../projectSchemas/auth"; import type { AgentCoreGateway } from "../../../../projectSchemas/gateway"; @@ -84,8 +84,8 @@ export const createAddGatewayHandler = (config: AddProjectResourceConfig) => flags["policy-engine-name"] && !project.spec.policyEngines.some((engine) => engine.name === flags["policy-engine-name"]) ) { - throw new InputValidationError( - `policy engine '${flags["policy-engine-name"]}' does not exist in policyEngines[]`, + throw new ResourceNotFoundError( + `no policy-engine named '${flags["policy-engine-name"]}' exists in this project`, ); } diff --git a/src/handlers/project/add/payment-connector/index.test.ts b/src/handlers/project/add/payment-connector/index.test.ts index a4c9ab704..e2bbcebd8 100644 --- a/src/handlers/project/add/payment-connector/index.test.ts +++ b/src/handlers/project/add/payment-connector/index.test.ts @@ -107,7 +107,7 @@ describe("project add payment-connector", () => { "connector", "--quick-create", ]), - ).rejects.toThrow("does not exist"); + ).rejects.toThrow("no payment-manager named 'missing' exists in this project"); await expect( run([ "add", @@ -119,7 +119,7 @@ describe("project add payment-connector", () => { "--credential", "missing", ]), - ).rejects.toThrow("does not exist in credentials[]"); + ).rejects.toThrow("no credential named 'missing' exists in this project"); expect((await projectSpec(projectRoot)).payments[0].connectors).toEqual([]); }); diff --git a/src/handlers/project/add/payment-connector/index.ts b/src/handlers/project/add/payment-connector/index.ts index 522e71cb5..46541ad6a 100644 --- a/src/handlers/project/add/payment-connector/index.ts +++ b/src/handlers/project/add/payment-connector/index.ts @@ -1,5 +1,5 @@ import z from "zod"; -import { InputValidationError } from "../../../../errors"; +import { InputValidationError, ResourceNotFoundError } from "../../../../errors"; import { createHandler, flag, ProjectKey } from "../../../../router"; import type { AddProjectResourceConfig } from "../types"; import { addProjectResource } from "../shared"; @@ -37,8 +37,8 @@ export const createAddPaymentConnectorHandler = (config: AddProjectResourceConfi (candidate) => candidate.name === credentialName, ); if (!credential) { - throw new InputValidationError( - `credential '${credentialName}' does not exist in credentials[]`, + throw new ResourceNotFoundError( + `no credential named '${credentialName}' exists in this project`, ); } if (credential.authorizerType !== "PaymentCredentialProvider") { diff --git a/src/handlers/project/add/policy-engine/index.test.ts b/src/handlers/project/add/policy-engine/index.test.ts index 2738aa6e0..27e5ec3e4 100644 --- a/src/handlers/project/add/policy-engine/index.test.ts +++ b/src/handlers/project/add/policy-engine/index.test.ts @@ -109,7 +109,7 @@ describe("project add policy-engine", () => { const projectRoot = await inProject(); await expect( run(["add", "policy-engine", "--name", "Guardrails", "--attach-to-gateways", "missing"]), - ).rejects.toThrow("gateway 'missing' does not exist"); + ).rejects.toThrow("no gateway named 'missing' exists in this project"); expect((await projectSpec(projectRoot)).policyEngines ?? []).toEqual([]); }); diff --git a/src/handlers/project/add/policy/index.test.ts b/src/handlers/project/add/policy/index.test.ts index 3d13bdb7a..4358b974d 100644 --- a/src/handlers/project/add/policy/index.test.ts +++ b/src/handlers/project/add/policy/index.test.ts @@ -120,7 +120,7 @@ describe("project add policy", () => { [ "unknown engine", ["add", "policy", "--engine", "Missing", "--name", "P", "--statement", FORBID_ALL], - "policy engine 'Missing' does not exist", + "no policy-engine named 'Missing' exists in this project", ], ])("rejects %s", async (_label, args, message) => { await withEngine(); From 3ef5c528841303d795d5bb8b0fffae4347c09514 Mon Sep 17 00:00:00 2001 From: Hweinstock Date: Fri, 11 Sep 2026 17:31:31 +0000 Subject: [PATCH 2/4] fix(dev): raise ResourceNotFoundError for missing build inputs --- src/core/dev/codezip.test.ts | 2 +- src/core/dev/codezip.ts | 6 +++--- src/core/dev/container.test.ts | 10 +++++----- src/core/dev/container.ts | 6 +++--- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/src/core/dev/codezip.test.ts b/src/core/dev/codezip.test.ts index 297f8a4e5..4be2380d5 100644 --- a/src/core/dev/codezip.test.ts +++ b/src/core/dev/codezip.test.ts @@ -102,7 +102,7 @@ describe("CodeZipDevRunner", () => { tempDirectories.push(root); await expect(collect(harness().runner.run(input(root, runtime())))).rejects.toThrow( - /runtime code directory not found/, + /no runtime code directory exists at/, ); }); diff --git a/src/core/dev/codezip.ts b/src/core/dev/codezip.ts index b3562f206..c91dc77d4 100644 --- a/src/core/dev/codezip.ts +++ b/src/core/dev/codezip.ts @@ -1,6 +1,6 @@ import { existsSync } from "node:fs"; import { delimiter, join, resolve } from "node:path"; -import { InputValidationError } from "../../errors"; +import { ResourceNotFoundError } from "../../errors"; import type { DevEvent, DevRunner, DevServerInput } from "../../handlers/project/dev/types"; import { runProcess, @@ -32,7 +32,7 @@ export class CodeZipDevRunner implements DevRunner { public async *run(input: DevServerInput): AsyncGenerator { const directory = resolve(input.projectRoot, input.runtime.codeLocation); if (!isDirectory(directory)) { - throw new InputValidationError(`runtime code directory not found: ${directory}`); + throw new ResourceNotFoundError(`no runtime code directory exists at ${directory}`); } resolvePathWithinProject(input.projectRoot, directory, "runtime code directory"); @@ -46,7 +46,7 @@ export class CodeZipDevRunner implements DevRunner { : entrypoint!; const entrypointPath = resolve(directory, devEntrypoint); if (!isFile(entrypointPath)) { - throw new InputValidationError(`runtime entrypoint not found: ${entrypointPath}`); + throw new ResourceNotFoundError(`no runtime entrypoint exists at ${entrypointPath}`); } resolvePathWithinProject(input.projectRoot, entrypointPath, "runtime entrypoint"); diff --git a/src/core/dev/container.test.ts b/src/core/dev/container.test.ts index accbc223c..c26250c28 100644 --- a/src/core/dev/container.test.ts +++ b/src/core/dev/container.test.ts @@ -4,7 +4,7 @@ import { mkdir, mkdtemp, readFile, rm, stat, symlink, writeFile } from "node:fs/ import { tmpdir } from "node:os"; import { join, resolve } from "node:path"; import { parseEnv } from "node:util"; -import { InputValidationError, InvalidEnvironmentError } from "../../errors"; +import { InputValidationError, InvalidEnvironmentError, ResourceNotFoundError } from "../../errors"; import type { DevEvent, DevServerInput } from "../../handlers/project/dev/types"; import { MissingToolError, @@ -518,8 +518,8 @@ describe("ContainerDevRunner", () => { const promise = collect(runner.run(input(root, runtime()))); - await expect(promise).rejects.toBeInstanceOf(InputValidationError); - await expect(promise).rejects.toThrow(/build context directory not found/); + await expect(promise).rejects.toBeInstanceOf(ResourceNotFoundError); + await expect(promise).rejects.toThrow(/no container build context directory exists at/); expect(probes).toHaveLength(0); expect(calls).toHaveLength(0); }); @@ -539,8 +539,8 @@ describe("ContainerDevRunner", () => { const promise = collect(runner.run(input(root, projectRuntime))); - await expect(promise).rejects.toBeInstanceOf(InputValidationError); - await expect(promise).rejects.toThrow(/Dockerfile not found/); + await expect(promise).rejects.toBeInstanceOf(ResourceNotFoundError); + await expect(promise).rejects.toThrow(/no container Dockerfile exists at/); expect(probes).toHaveLength(0); expect(calls).toHaveLength(0); }); diff --git a/src/core/dev/container.ts b/src/core/dev/container.ts index fdb9fd323..8f3d32977 100644 --- a/src/core/dev/container.ts +++ b/src/core/dev/container.ts @@ -3,7 +3,7 @@ import { existsSync, writeFileSync } from "node:fs"; import { rm, writeFile } from "node:fs/promises"; import { homedir, tmpdir } from "node:os"; import { join, resolve } from "node:path"; -import { InputValidationError, InvalidEnvironmentError } from "../../errors"; +import { InputValidationError, InvalidEnvironmentError, ResourceNotFoundError } from "../../errors"; import type { DevEvent, DevRunner, DevServerInput } from "../../handlers/project/dev/types"; import { MissingToolError, @@ -80,7 +80,7 @@ export class ContainerDevRunner implements DevRunner { input.runtime.buildContextPath ?? input.runtime.codeLocation, ); if (!isDirectory(context)) { - throw new InputValidationError(`container build context directory not found: ${context}`); + throw new ResourceNotFoundError(`no container build context directory exists at ${context}`); } resolvePathWithinProject(input.projectRoot, context, "container build context"); @@ -88,7 +88,7 @@ export class ContainerDevRunner implements DevRunner { const dockerfile = input.runtime.dockerfile ?? DOCKERFILE_NAME; const dockerfilePath = join(context, dockerfile); if (!isFile(dockerfilePath)) { - throw new InputValidationError(`container Dockerfile not found: ${dockerfilePath}`); + throw new ResourceNotFoundError(`no container Dockerfile exists at ${dockerfilePath}`); } const hasAwsCredentials = Boolean( From 08b28a147c1c13f598e82290e8a6b89b70e9e9b5 Mon Sep 17 00:00:00 2001 From: Hweinstock Date: Fri, 11 Sep 2026 17:31:32 +0000 Subject: [PATCH 3/4] fix(templates): raise ResourceNotFoundError for missing template files --- src/core/project/manager.test.ts | 2 +- src/core/project/templates/fsTree.ts | 4 ++-- src/core/project/templates/harness.ts | 4 ++-- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/core/project/manager.test.ts b/src/core/project/manager.test.ts index e08bbf8ef..e6489229d 100644 --- a/src/core/project/manager.test.ts +++ b/src/core/project/manager.test.ts @@ -271,7 +271,7 @@ describe("FsProjectManager.create", () => { dockerfile, }, }), - ).rejects.toThrow(`dockerfile not found: '${dockerfile}'`); + ).rejects.toThrow(`no dockerfile exists at ${dockerfile}`); expect(existsSync(join(directory, "example"))).toBe(false); }); diff --git a/src/core/project/templates/fsTree.ts b/src/core/project/templates/fsTree.ts index c1281ebc3..73650fcfd 100644 --- a/src/core/project/templates/fsTree.ts +++ b/src/core/project/templates/fsTree.ts @@ -3,7 +3,7 @@ import { mkdir, readFile, writeFile } from "node:fs/promises"; import { join } from "node:path"; import type { AssetSource } from "../source"; import { AgentCoreCLIError, ERROR_SOURCE } from "../../../errors"; -import { InputValidationError, ProjectStateError } from "../../../errors/errors"; +import { ProjectStateError, ResourceNotFoundError } from "../../../errors/errors"; /** * FsTreeNode represents a tree of directories and files. @@ -64,7 +64,7 @@ export class FsTreeNode { static fromTextFile(name: string, sourcePath: string): FsTreeNode { return FsTreeNode.createFile(name, async () => { if (!existsSync(sourcePath)) { - throw new InputValidationError(`file not found: '${sourcePath}'`); + throw new ResourceNotFoundError(`no source file exists at ${sourcePath}`); } return readFile(sourcePath, "utf-8"); }); diff --git a/src/core/project/templates/harness.ts b/src/core/project/templates/harness.ts index ab97f6163..152a30157 100644 --- a/src/core/project/templates/harness.ts +++ b/src/core/project/templates/harness.ts @@ -2,7 +2,7 @@ import { existsSync } from "node:fs"; import { ZodError, z } from "zod"; import { HarnessSpecSchema } from "../../../projectSchemas/harness"; import { FsTreeNode } from "./fsTree"; -import { InputValidationError } from "../../../errors/errors"; +import { InputValidationError, ResourceNotFoundError } from "../../../errors/errors"; import type { TemplateResolver } from "./types"; const DEFAULT_SYSTEM_PROMPT = "You are a helpful assistant"; @@ -43,7 +43,7 @@ export function getHarnessTemplateResolver(): TemplateResolver): void { if (spec.dockerfile && !existsSync(spec.dockerfile)) { - throw new InputValidationError(`dockerfile not found: '${spec.dockerfile}'`); + throw new ResourceNotFoundError(`no dockerfile exists at ${spec.dockerfile}`); } } From 945b3a554a665b7bff16923078f4255c8a9209ac Mon Sep 17 00:00:00 2001 From: Hweinstock Date: Fri, 11 Sep 2026 17:31:33 +0000 Subject: [PATCH 4/4] fix(eval): raise ResourceNotFoundError for absent service resources --- src/core/eval.tsx | 2 +- src/core/project/bedrockAgentImport/loader.ts | 10 +++++++--- src/handlers/project/export/harness.ts | 4 ++-- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/core/eval.tsx b/src/core/eval.tsx index bf9080d51..cf07b74ca 100644 --- a/src/core/eval.tsx +++ b/src/core/eval.tsx @@ -1961,7 +1961,7 @@ async function resolveAgentToNameAndId( return harnessRuntimeFromResponse(agent, harness); } catch (error) { if (!(error instanceof InputValidationError)) throw error; - throw new InputValidationError(`"${agent}" does not exist as a runtime or a harness`, { + throw new ResourceNotFoundError(`no runtime or harness named '${agent}' exists`, { cause: error, meta: { agent }, }); diff --git a/src/core/project/bedrockAgentImport/loader.ts b/src/core/project/bedrockAgentImport/loader.ts index 25f485e89..eeb76c188 100644 --- a/src/core/project/bedrockAgentImport/loader.ts +++ b/src/core/project/bedrockAgentImport/loader.ts @@ -10,7 +10,11 @@ import { type AgentActionGroup, type AgentVersion, } from "@aws-sdk/client-bedrock-agent"; -import { InputValidationError, MalformedServiceResponseError } from "../../../errors"; +import { + InputValidationError, + MalformedServiceResponseError, + ResourceNotFoundError, +} from "../../../errors"; import type { BedrockAgentImportNote, BedrockAgentSnapshot, @@ -140,8 +144,8 @@ export class BedrockAgentSnapshotLoader { )); } catch (error) { if (isNamedError(error, "ResourceNotFoundException")) { - throw new InputValidationError( - `Bedrock Agent '${input.agentId}' has no version '${input.agentVersion}' in ${input.region}`, + throw new ResourceNotFoundError( + `no version '${input.agentVersion}' exists for Bedrock Agent '${input.agentId}' in ${input.region}`, { cause: error }, ); } diff --git a/src/handlers/project/export/harness.ts b/src/handlers/project/export/harness.ts index faa47d751..4bf495baa 100644 --- a/src/handlers/project/export/harness.ts +++ b/src/handlers/project/export/harness.ts @@ -1,5 +1,5 @@ import z from "zod"; -import { InputValidationError } from "../../../errors"; +import { InputValidationError, ResourceNotFoundError } from "../../../errors"; import { createHandler, flag, ProjectKey } from "../../../router"; import { JsonRendererKey } from "../../../tui"; import { JsonKey } from "../../keys"; @@ -45,7 +45,7 @@ export const createExportHarnessHandler = (config: ExportProjectResourceConfig) const region = regionFromHarnessArn(flags.arn); const response = await config.core.harness.getHarness(harnessId, { ...coreOpts, region }); if (!response.harness) { - throw new InputValidationError(`the service returned no harness for "${flags.arn}"`); + throw new ResourceNotFoundError(`no harness exists for '${flags.arn}'`); } const { spec, systemPrompt, notes } = mapServiceHarnessToSpec(response.harness); input = {