From 646af273ada422d597978461b9986afc46811893 Mon Sep 17 00:00:00 2001 From: Hweinstock Date: Wed, 29 Jul 2026 20:54:58 +0000 Subject: [PATCH 1/3] fix(error): model config schema failures as InternalValidationErrors --- src/errors/index.tsx | 3 ++- src/globalConfig/accessor.tsx | 14 +++++++++++--- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/errors/index.tsx b/src/errors/index.tsx index 5f96f8766..bab6329e1 100644 --- a/src/errors/index.tsx +++ b/src/errors/index.tsx @@ -1 +1,2 @@ -export { AgentCoreCLIError, InputValidationError } from "./errors"; +export { AgentCoreCLIError, InputValidationError, type AgentCoreCLIErrorOptions } from "./errors"; +export { ERROR_SOURCE } from "./types"; diff --git a/src/globalConfig/accessor.tsx b/src/globalConfig/accessor.tsx index 18ea6d316..a9fb28542 100644 --- a/src/globalConfig/accessor.tsx +++ b/src/globalConfig/accessor.tsx @@ -6,9 +6,9 @@ import { } from "./types"; import type { ReadWriteJson } from "../io"; import type { Logger } from "../logging"; -import z from "zod"; import { globalConfigFileSchema } from "./types"; import { DEFAULT_GLOBAL_CONFIG, applyOverrides } from "./config"; +import { AgentCoreCLIError, ERROR_SOURCE, type AgentCoreCLIErrorOptions } from "../errors"; type DefaultGlobalConfigAccessorConfig = { logger: Logger; @@ -76,8 +76,9 @@ export class DefaultGlobalConfigAccessor implements GlobalConfigAccessor { private async writeToConfigFile(data: GlobalConfigFileData): Promise { const dataParseResult = globalConfigFileSchema.safeParse(data); if (!dataParseResult.success) { - // TODO: mark this as a client-source error. - throw new TypeError(z.prettifyError(dataParseResult.error)); + throw new InternalValidationError("failed to valide config data before writing to file", { + cause: dataParseResult.error, + }); } await this.json.write(this.filePath, dataParseResult.data); @@ -127,3 +128,10 @@ function diff>(a: T, b: T): DeepPartial { function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } + +/** Error raised by invalid data coming from internal sources **/ +export class InternalValidationError extends AgentCoreCLIError { + constructor(message?: string, options?: Omit) { + super(message, { ...options, source: ERROR_SOURCE.INTERNAL }); + } +} From ae27e065e50f5c418a264d18c06359d61cbe3226 Mon Sep 17 00:00:00 2001 From: Hweinstock Date: Wed, 29 Jul 2026 21:04:45 +0000 Subject: [PATCH 2/3] fix(errors): swap tty error to invalidEnvironmentError --- src/globalConfig/accessor.tsx | 2 +- src/tui/index.tsx | 10 +++++++++- src/tui/tui.test.tsx | 6 ++---- 3 files changed, 12 insertions(+), 6 deletions(-) diff --git a/src/globalConfig/accessor.tsx b/src/globalConfig/accessor.tsx index a9fb28542..8ac01e08c 100644 --- a/src/globalConfig/accessor.tsx +++ b/src/globalConfig/accessor.tsx @@ -76,7 +76,7 @@ export class DefaultGlobalConfigAccessor implements GlobalConfigAccessor { private async writeToConfigFile(data: GlobalConfigFileData): Promise { const dataParseResult = globalConfigFileSchema.safeParse(data); if (!dataParseResult.success) { - throw new InternalValidationError("failed to valide config data before writing to file", { + throw new InternalValidationError("failed to validate config data before writing to file", { cause: dataParseResult.error, }); } diff --git a/src/tui/index.tsx b/src/tui/index.tsx index 6fe429e8b..6e423d503 100644 --- a/src/tui/index.tsx +++ b/src/tui/index.tsx @@ -11,6 +11,7 @@ import { import type { AppIO } from "../io"; import type { Core } from "../handlers/types"; import { JsonKey } from "../handlers/keys"; +import { AgentCoreCLIError, ERROR_SOURCE, type AgentCoreCLIErrorOptions } from "../errors"; // renderJson pretty-prints a value as indented JSON. It is the output // counterpart to renderTui: handlers call it to emit machine-readable results @@ -43,7 +44,7 @@ export async function renderTuiAt( io: AppIO, ): Promise { if (!io.stdin.isTTY || !io.stdout.isTTY) { - throw new TypeError("interactive mode requires a TTY on stdin and stdout"); + throw new InvalidEnvironmentError("interactive mode requires a TTY on stdin and stdout"); } // alternateScreen switches the terminal to its alternate buffer so the TUI @@ -74,3 +75,10 @@ export function renderTui(core: Core, io: AppIO): DefaultHandle { await renderTuiAt(ctx.require(PathKey), ctx, core, io); }; } + +/** Error raised when detecting an invalid environment */ +export class InvalidEnvironmentError extends AgentCoreCLIError { + constructor(message?: string, options?: Omit) { + super(message, { ...options, source: ERROR_SOURCE.USER }); + } +} diff --git a/src/tui/tui.test.tsx b/src/tui/tui.test.tsx index 4c261e2f7..aa176c97b 100644 --- a/src/tui/tui.test.tsx +++ b/src/tui/tui.test.tsx @@ -1,6 +1,6 @@ import { test, expect, describe } from "bun:test"; import { createRootHandler } from "../handlers"; -import { renderJson } from "./index"; +import { InvalidEnvironmentError, renderJson } from "./index"; import { createSilentLogger, TestCoreClient, @@ -89,9 +89,7 @@ describe("TUI stream boundary", () => { globalConfigAccessor: new TestGlobalConfigAccessor(), }); - await expect(root.route(["node", "agentcore"])).rejects.toThrow( - "interactive mode requires a TTY on stdin and stdout", - ); + await expect(root.route(["node", "agentcore"])).rejects.toThrow(InvalidEnvironmentError); }, ); From 89257ccfa5aff6f6afc7d08094c9b2af2ec23a82 Mon Sep 17 00:00:00 2001 From: Hweinstock Date: Wed, 29 Jul 2026 22:23:20 +0000 Subject: [PATCH 3/3] fix(errors): swap to input validation error in config --- src/globalConfig/accessor.tsx | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/src/globalConfig/accessor.tsx b/src/globalConfig/accessor.tsx index 8ac01e08c..6210f285b 100644 --- a/src/globalConfig/accessor.tsx +++ b/src/globalConfig/accessor.tsx @@ -8,7 +8,8 @@ import type { ReadWriteJson } from "../io"; import type { Logger } from "../logging"; import { globalConfigFileSchema } from "./types"; import { DEFAULT_GLOBAL_CONFIG, applyOverrides } from "./config"; -import { AgentCoreCLIError, ERROR_SOURCE, type AgentCoreCLIErrorOptions } from "../errors"; +import z from "zod"; +import { InputValidationError } from "../errors"; type DefaultGlobalConfigAccessorConfig = { logger: Logger; @@ -76,7 +77,7 @@ export class DefaultGlobalConfigAccessor implements GlobalConfigAccessor { private async writeToConfigFile(data: GlobalConfigFileData): Promise { const dataParseResult = globalConfigFileSchema.safeParse(data); if (!dataParseResult.success) { - throw new InternalValidationError("failed to validate config data before writing to file", { + throw new InputValidationError(z.prettifyError(dataParseResult.error), { cause: dataParseResult.error, }); } @@ -128,10 +129,3 @@ function diff>(a: T, b: T): DeepPartial { function isRecord(value: unknown): value is Record { return typeof value === "object" && value !== null && !Array.isArray(value); } - -/** Error raised by invalid data coming from internal sources **/ -export class InternalValidationError extends AgentCoreCLIError { - constructor(message?: string, options?: Omit) { - super(message, { ...options, source: ERROR_SOURCE.INTERNAL }); - } -}