From 9088b678e46e4c04ccbaacf0de29389ed534c6e4 Mon Sep 17 00:00:00 2001 From: Wasim Date: Sat, 25 Jul 2026 16:32:23 +0530 Subject: [PATCH 1/4] fix(cli): fail when excess positional arguments are provided --- packages/effect/src/unstable/cli/CliError.ts | 29 +++++++++++++++++++ .../src/unstable/cli/internal/command.ts | 6 ++++ 2 files changed, 35 insertions(+) diff --git a/packages/effect/src/unstable/cli/CliError.ts b/packages/effect/src/unstable/cli/CliError.ts index 2ff929399d3..9ea56bdea14 100644 --- a/packages/effect/src/unstable/cli/CliError.ts +++ b/packages/effect/src/unstable/cli/CliError.ts @@ -93,6 +93,7 @@ export type CliError = | UnknownSubcommand | ShowHelp | UserError + | ValidationError /** * Error thrown when an unrecognized option is encountered. @@ -489,6 +490,34 @@ export class UserError extends Schema.TaggedErrorClass( readonly [TypeId] = TypeId } +/** + * Error thrown when a validation fails (e.g. excess positional arguments). + * + * @category models + * @since 4.0.0 + */ +export class ValidationError extends Schema.TaggedErrorClass( + `${TypeId}/ValidationError` +)("ValidationError", { + error: Schema.String +}) { + /** + * Marks this value as a CLI parsing error for runtime guards. + * + * @since 4.0.0 + */ + readonly [TypeId] = TypeId + + /** + * Retrieves the underlying error. + * + * @since 4.0.0 + */ + override get message() { + return this.error + } +} + /** * Schema for concrete CLI errors that can be reported together with help output. * diff --git a/packages/effect/src/unstable/cli/internal/command.ts b/packages/effect/src/unstable/cli/internal/command.ts index b35279b1c59..d634ade3f01 100644 --- a/packages/effect/src/unstable/cli/internal/command.ts +++ b/packages/effect/src/unstable/cli/internal/command.ts @@ -291,6 +291,12 @@ const parseParams: (parsedArgs: Param.ParsedArgs, params: ReadonlyArray 0) { + return yield* new CliError.ValidationError({ + error: `Excess arguments: ${currentArguments.join(", ")}` + }) + } + return results }) From ba865ba8dd3925efbe949d74ab4fc2ef8dcb0e61 Mon Sep 17 00:00:00 2001 From: Wasim Date: Sat, 25 Jul 2026 17:39:00 +0530 Subject: [PATCH 2/4] fix(cli): rename ValidationError to UnrecognizedArgument --- packages/effect/src/unstable/cli/CliError.ts | 12 +++++++----- packages/effect/src/unstable/cli/internal/command.ts | 2 +- 2 files changed, 8 insertions(+), 6 deletions(-) diff --git a/packages/effect/src/unstable/cli/CliError.ts b/packages/effect/src/unstable/cli/CliError.ts index 9ea56bdea14..6dc8013b524 100644 --- a/packages/effect/src/unstable/cli/CliError.ts +++ b/packages/effect/src/unstable/cli/CliError.ts @@ -93,7 +93,7 @@ export type CliError = | UnknownSubcommand | ShowHelp | UserError - | ValidationError + | UnrecognizedArgument /** * Error thrown when an unrecognized option is encountered. @@ -491,14 +491,14 @@ export class UserError extends Schema.TaggedErrorClass( } /** - * Error thrown when a validation fails (e.g. excess positional arguments). + * Error thrown when an unrecognized argument is encountered. * * @category models * @since 4.0.0 */ -export class ValidationError extends Schema.TaggedErrorClass( - `${TypeId}/ValidationError` -)("ValidationError", { +export class UnrecognizedArgument extends Schema.TaggedErrorClass( + `${TypeId}/UnrecognizedArgument` +)("UnrecognizedArgument", { error: Schema.String }) { /** @@ -537,6 +537,7 @@ export const NonShowHelpErrors: Schema.Union< typeof MissingArgument, typeof InvalidValue, typeof UnknownSubcommand, + typeof UnrecognizedArgument, typeof UserError ] > = Schema.Union([ @@ -546,6 +547,7 @@ export const NonShowHelpErrors: Schema.Union< MissingArgument, InvalidValue, UnknownSubcommand, + UnrecognizedArgument, UserError ]) diff --git a/packages/effect/src/unstable/cli/internal/command.ts b/packages/effect/src/unstable/cli/internal/command.ts index d634ade3f01..651722f8d87 100644 --- a/packages/effect/src/unstable/cli/internal/command.ts +++ b/packages/effect/src/unstable/cli/internal/command.ts @@ -292,7 +292,7 @@ const parseParams: (parsedArgs: Param.ParsedArgs, params: ReadonlyArray 0) { - return yield* new CliError.ValidationError({ + return yield* new CliError.UnrecognizedArgument({ error: `Excess arguments: ${currentArguments.join(", ")}` }) } From 1c87f5c47c7c193868e64b71241d4f939141d4ff Mon Sep 17 00:00:00 2001 From: Wasim Date: Sat, 25 Jul 2026 17:39:08 +0530 Subject: [PATCH 3/4] docs: add changeset --- .changeset/unrecognized-arguments.md | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .changeset/unrecognized-arguments.md diff --git a/.changeset/unrecognized-arguments.md b/.changeset/unrecognized-arguments.md new file mode 100644 index 00000000000..3bd1b05e17c --- /dev/null +++ b/.changeset/unrecognized-arguments.md @@ -0,0 +1,5 @@ +--- +"effect": patch +--- + +Added UnrecognizedArgument error to effect/cli for excess positional arguments. From adee9f1b6ed07a73a2eb27b0be5a191c29341b2a Mon Sep 17 00:00:00 2001 From: Wasim Date: Sat, 25 Jul 2026 17:47:13 +0530 Subject: [PATCH 4/4] test(cli): add regression tests for excess arguments --- .../effect/test/unstable/cli/Command.test.ts | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) diff --git a/packages/effect/test/unstable/cli/Command.test.ts b/packages/effect/test/unstable/cli/Command.test.ts index 5dac07ac127..40af8d8bb1a 100644 --- a/packages/effect/test/unstable/cli/Command.test.ts +++ b/packages/effect/test/unstable/cli/Command.test.ts @@ -722,6 +722,34 @@ describe("Command", () => { const result = yield* Effect.flip(Cli.run(["test-failing", "--input", "test"])) assert.strictEqual(result, "Handler error") }).pipe(Effect.provide(TestLayer))) + it.effect("should fail with UnrecognizedArgument when excess positional arguments are provided to a command with no arguments", () => + Effect.gen(function*() { + const command = Command.make("test", {}, () => Effect.void) + const runCommand = Command.runWith(command, { version: "1.0.0" }) + const error = yield* Effect.flip(runCommand(["bogus1", "bogus2"])) + assert.strictEqual((error as any)._tag, "UnrecognizedArgument") + assert.isTrue(String((error as any).error).includes("Excess arguments: bogus1, bogus2")) + }).pipe(Effect.provide(TestLayer))) + + it.effect("should fail with UnrecognizedArgument when excess positional arguments are provided to a command with fixed arguments", () => + Effect.gen(function*() { + const command = Command.make("test", { + arg1: Argument.string("arg1") + }, () => Effect.void) + const runCommand = Command.runWith(command, { version: "1.0.0" }) + const error = yield* Effect.flip(runCommand(["valid", "bogus1", "bogus2"])) + assert.strictEqual((error as any)._tag, "UnrecognizedArgument") + assert.isTrue(String((error as any).error).includes("Excess arguments: bogus1, bogus2")) + }).pipe(Effect.provide(TestLayer))) + + it.effect("should not fail with UnrecognizedArgument when a command has variadic arguments", () => + Effect.gen(function*() { + const command = Command.make("test", { + args: Argument.string("args").pipe(Argument.repeated) + }, () => Effect.void) + const runCommand = Command.runWith(command, { version: "1.0.0" }) + yield* runCommand(["valid1", "valid2", "valid3"]) + }).pipe(Effect.provide(TestLayer))) }) describe("withSubcommands", () => {