From 1e976e6097f6733a8ce6dfd0f9aea39195702914 Mon Sep 17 00:00:00 2001 From: rvaccone Date: Sat, 18 Jul 2026 17:22:01 -0400 Subject: [PATCH 1/5] fix(cli): remove doubled Expected prefix from InvalidValue messages --- .../fix-invalid-value-doubled-expected.md | 25 ++++++++++++++++ packages/effect/src/unstable/cli/CliError.ts | 15 ++++++++-- packages/effect/src/unstable/cli/Primitive.ts | 2 +- .../test/unstable/cli/Arguments.test.ts | 2 +- .../effect/test/unstable/cli/Command.test.ts | 21 +++++++++++++ .../effect/test/unstable/cli/Errors.test.ts | 30 +++++++++++++++++++ .../test/unstable/cli/Primitive.test.ts | 6 ++-- 7 files changed, 93 insertions(+), 8 deletions(-) create mode 100644 .changeset/fix-invalid-value-doubled-expected.md diff --git a/.changeset/fix-invalid-value-doubled-expected.md b/.changeset/fix-invalid-value-doubled-expected.md new file mode 100644 index 00000000000..d35d1d99592 --- /dev/null +++ b/.changeset/fix-invalid-value-doubled-expected.md @@ -0,0 +1,25 @@ +--- +"effect": patch +--- + +Fix doubled `Expected: Expected ...` prefixes in CLI `InvalidValue` error messages, closes #6312. + +`Primitive.choice` now fails with a bare description of the accepted values, and `CliError.InvalidValue` no longer prepends its own `Expected:` label when the underlying failure message (e.g. a schema decode message) already reads as a complete `Expected ...` sentence. + +Before: + +``` +Invalid value for flag --size: "bogus". Expected: Expected "small" | "medium" | "large", got "bogus" +``` + +After: + +``` +Invalid value for flag --size: "bogus". Expected: "small" | "medium" | "large" +``` + +Schema-backed primitives (`integer`, `float`, `boolean`, `date`) keep their full schema decode sentence, so the decoded actual is still shown after `got`: + +``` +Invalid value for flag --count: "3.14". Expected an integer, got 3.14 +``` diff --git a/packages/effect/src/unstable/cli/CliError.ts b/packages/effect/src/unstable/cli/CliError.ts index ae58094514c..64b406353f3 100644 --- a/packages/effect/src/unstable/cli/CliError.ts +++ b/packages/effect/src/unstable/cli/CliError.ts @@ -359,16 +359,25 @@ export class InvalidValue extends Schema.TaggedErrorClass( /** * Formats the invalid flag or argument value with the expected input. * + * **Details** + * + * When `expected` is already a complete `Expected ...` sentence (e.g. a + * schema decode message), it is appended as-is instead of being prefixed + * with another `Expected:` label. + * * @since 4.0.0 */ override get message() { + const expectation = this.expected.startsWith("Expected ") + ? this.expected + : `Expected: ${this.expected}` if (this.kind === "argument") { - return `Invalid value for argument <${this.option}>: "${this.value}". Expected: ${this.expected}` + return `Invalid value for argument <${this.option}>: "${this.value}". ${expectation}` } if (this.value.length === 0) { - return `Missing value for flag --${this.option}. Expected: ${this.expected}` + return `Missing value for flag --${this.option}. ${expectation}` } - return `Invalid value for flag --${this.option}: "${this.value}". Expected: ${this.expected}` + return `Invalid value for flag --${this.option}: "${this.value}". ${expectation}` } } diff --git a/packages/effect/src/unstable/cli/Primitive.ts b/packages/effect/src/unstable/cli/Primitive.ts index a4c59201f14..4526a81af1e 100644 --- a/packages/effect/src/unstable/cli/Primitive.ts +++ b/packages/effect/src/unstable/cli/Primitive.ts @@ -304,7 +304,7 @@ export const choice = ( if (choiceMap.has(value)) { return Effect.succeed(choiceMap.get(value)!) } - return Effect.fail(`Expected ${validChoices}, got ${format(value)}`) + return Effect.fail(validChoices) }) return Object.assign(primitive, { choiceKeys: choices.map(([key]) => key) }) } diff --git a/packages/effect/test/unstable/cli/Arguments.test.ts b/packages/effect/test/unstable/cli/Arguments.test.ts index 7314bf37551..5c0f37788dc 100644 --- a/packages/effect/test/unstable/cli/Arguments.test.ts +++ b/packages/effect/test/unstable/cli/Arguments.test.ts @@ -156,7 +156,7 @@ describe("Command arguments", () => { expect(errorText).toMatchInlineSnapshot(` " ERROR - Invalid value for argument : "not-a-number". Expected: Failed to parse integer: Expected an integer, got NaN" + Invalid value for argument : "not-a-number". Expected a string representing a finite number, got "not-a-number"" `) }).pipe(Effect.provide(TestLayer))) diff --git a/packages/effect/test/unstable/cli/Command.test.ts b/packages/effect/test/unstable/cli/Command.test.ts index 756ae223aaf..5dac07ac127 100644 --- a/packages/effect/test/unstable/cli/Command.test.ts +++ b/packages/effect/test/unstable/cli/Command.test.ts @@ -271,6 +271,27 @@ describe("Command", () => { }).pipe(Effect.provide(TestLayer))) } + it.effect("should render invalid choice values without doubling the Expected prefix", () => + Effect.gen(function*() { + let invoked = false + const command = Command.make("demo", { + size: Flag.choice("size", ["small", "medium", "large"]) + }, () => + Effect.sync(() => { + invoked = true + })) + + yield* Command.runWith(command, { version: "1.0.0" })(["--size", "bogus"]).pipe(Effect.ignore) + + const stderr = yield* TestConsole.errorLines + assert.isFalse(invoked) + assert.isTrue( + stderr.some((line) => + String(line).includes(`Invalid value for flag --size: "bogus". Expected: "small" | "medium" | "large"`) + ) + ) + }).pipe(Effect.provide(TestLayer))) + it.effect("should execute handler with parsed config", () => Effect.gen(function*() { const path = yield* Path.Path diff --git a/packages/effect/test/unstable/cli/Errors.test.ts b/packages/effect/test/unstable/cli/Errors.test.ts index 933502c431b..1d1257d42a2 100644 --- a/packages/effect/test/unstable/cli/Errors.test.ts +++ b/packages/effect/test/unstable/cli/Errors.test.ts @@ -147,4 +147,34 @@ describe("Command errors", () => { assert.strictEqual(output, "") }) }) + + describe("InvalidValue", () => { + it("labels a bare expected description", () => { + const error = new CliError.InvalidValue({ + option: "size", + value: "bogus", + expected: `"small" | "medium" | "large"`, + kind: "flag" + }) + + assert.strictEqual( + error.message, + `Invalid value for flag --size: "bogus". Expected: "small" | "medium" | "large"` + ) + }) + + it("does not double the prefix for an Expected sentence", () => { + const error = new CliError.InvalidValue({ + option: "count", + value: "3.14", + expected: "Expected an integer, got 3.14", + kind: "argument" + }) + + assert.strictEqual( + error.message, + `Invalid value for argument : "3.14". Expected an integer, got 3.14` + ) + }) + }) }) diff --git a/packages/effect/test/unstable/cli/Primitive.test.ts b/packages/effect/test/unstable/cli/Primitive.test.ts index 0b83b1ee630..f3e3706c5fc 100644 --- a/packages/effect/test/unstable/cli/Primitive.test.ts +++ b/packages/effect/test/unstable/cli/Primitive.test.ts @@ -214,9 +214,9 @@ describe("Primitive", () => { colorChoice, ["yellow", "purple", ""], [ - `Expected "red" | "green" | "blue", got "yellow"`, - `Expected "red" | "green" | "blue", got "purple"`, - `Expected "red" | "green" | "blue", got ""` + `"red" | "green" | "blue"`, + `"red" | "green" | "blue"`, + `"red" | "green" | "blue"` ] )) From 8b3e749b47374074e65e03a0fbca9228e2ce331c Mon Sep 17 00:00:00 2001 From: rvaccone Date: Sat, 18 Jul 2026 17:27:20 -0400 Subject: [PATCH 2/5] fix(cli): recognize an existing Expected: label in InvalidValue.expected --- packages/effect/src/unstable/cli/CliError.ts | 2 +- packages/effect/test/unstable/cli/Errors.test.ts | 12 ++++++++++++ 2 files changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/effect/src/unstable/cli/CliError.ts b/packages/effect/src/unstable/cli/CliError.ts index 64b406353f3..a58baaf053a 100644 --- a/packages/effect/src/unstable/cli/CliError.ts +++ b/packages/effect/src/unstable/cli/CliError.ts @@ -368,7 +368,7 @@ export class InvalidValue extends Schema.TaggedErrorClass( * @since 4.0.0 */ override get message() { - const expectation = this.expected.startsWith("Expected ") + const expectation = this.expected.startsWith("Expected ") || this.expected.startsWith("Expected:") ? this.expected : `Expected: ${this.expected}` if (this.kind === "argument") { diff --git a/packages/effect/test/unstable/cli/Errors.test.ts b/packages/effect/test/unstable/cli/Errors.test.ts index 1d1257d42a2..d292de52dc0 100644 --- a/packages/effect/test/unstable/cli/Errors.test.ts +++ b/packages/effect/test/unstable/cli/Errors.test.ts @@ -175,6 +175,18 @@ describe("Command errors", () => { error.message, `Invalid value for argument : "3.14". Expected an integer, got 3.14` ) + + const labeled = new CliError.InvalidValue({ + option: "count", + value: "x", + expected: "Expected: an integer", + kind: "flag" + }) + + assert.strictEqual( + labeled.message, + `Invalid value for flag --count: "x". Expected: an integer` + ) }) }) }) From 53ea8c0cde30db630fb01bec1c5de3af8a61b14e Mon Sep 17 00:00:00 2001 From: rvaccone Date: Sun, 19 Jul 2026 00:07:56 -0400 Subject: [PATCH 3/5] fix(cli): address review feedback on changeset fences and missing value coverage --- .changeset/fix-invalid-value-doubled-expected.md | 6 +++--- packages/effect/test/unstable/cli/Errors.test.ts | 14 ++++++++++++++ 2 files changed, 17 insertions(+), 3 deletions(-) diff --git a/.changeset/fix-invalid-value-doubled-expected.md b/.changeset/fix-invalid-value-doubled-expected.md index d35d1d99592..ab2a1b3ccae 100644 --- a/.changeset/fix-invalid-value-doubled-expected.md +++ b/.changeset/fix-invalid-value-doubled-expected.md @@ -8,18 +8,18 @@ Fix doubled `Expected: Expected ...` prefixes in CLI `InvalidValue` error messag Before: -``` +```text Invalid value for flag --size: "bogus". Expected: Expected "small" | "medium" | "large", got "bogus" ``` After: -``` +```text Invalid value for flag --size: "bogus". Expected: "small" | "medium" | "large" ``` Schema-backed primitives (`integer`, `float`, `boolean`, `date`) keep their full schema decode sentence, so the decoded actual is still shown after `got`: -``` +```text Invalid value for flag --count: "3.14". Expected an integer, got 3.14 ``` diff --git a/packages/effect/test/unstable/cli/Errors.test.ts b/packages/effect/test/unstable/cli/Errors.test.ts index d292de52dc0..2fcb127de2a 100644 --- a/packages/effect/test/unstable/cli/Errors.test.ts +++ b/packages/effect/test/unstable/cli/Errors.test.ts @@ -188,5 +188,19 @@ describe("Command errors", () => { `Invalid value for flag --count: "x". Expected: an integer` ) }) + + it("does not double the prefix for a missing flag value", () => { + const error = new CliError.InvalidValue({ + option: "count", + value: "", + expected: `Expected a string representing a finite number, got ""`, + kind: "flag" + }) + + assert.strictEqual( + error.message, + `Missing value for flag --count. Expected a string representing a finite number, got ""` + ) + }) }) }) From 495e5f1e29eafcb0e20c18064835e141f0ffeb2c Mon Sep 17 00:00:00 2001 From: Rocco Vaccone <25404382+rvaccone@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:14:05 -0400 Subject: [PATCH 4/5] Update .changeset/fix-invalid-value-doubled-expected.md Co-authored-by: Maxwell Brown --- .../fix-invalid-value-doubled-expected.md | 20 ------------------- 1 file changed, 20 deletions(-) diff --git a/.changeset/fix-invalid-value-doubled-expected.md b/.changeset/fix-invalid-value-doubled-expected.md index ab2a1b3ccae..a8e9deaeedd 100644 --- a/.changeset/fix-invalid-value-doubled-expected.md +++ b/.changeset/fix-invalid-value-doubled-expected.md @@ -3,23 +3,3 @@ --- Fix doubled `Expected: Expected ...` prefixes in CLI `InvalidValue` error messages, closes #6312. - -`Primitive.choice` now fails with a bare description of the accepted values, and `CliError.InvalidValue` no longer prepends its own `Expected:` label when the underlying failure message (e.g. a schema decode message) already reads as a complete `Expected ...` sentence. - -Before: - -```text -Invalid value for flag --size: "bogus". Expected: Expected "small" | "medium" | "large", got "bogus" -``` - -After: - -```text -Invalid value for flag --size: "bogus". Expected: "small" | "medium" | "large" -``` - -Schema-backed primitives (`integer`, `float`, `boolean`, `date`) keep their full schema decode sentence, so the decoded actual is still shown after `got`: - -```text -Invalid value for flag --count: "3.14". Expected an integer, got 3.14 -``` From b8f34bf615a8e2fbb2a5f4e61b388f0ef429e4b9 Mon Sep 17 00:00:00 2001 From: Rocco Vaccone <25404382+rvaccone@users.noreply.github.com> Date: Sun, 19 Jul 2026 16:14:19 -0400 Subject: [PATCH 5/5] Update packages/effect/src/unstable/cli/CliError.ts Co-authored-by: Maxwell Brown --- packages/effect/src/unstable/cli/CliError.ts | 6 ------ 1 file changed, 6 deletions(-) diff --git a/packages/effect/src/unstable/cli/CliError.ts b/packages/effect/src/unstable/cli/CliError.ts index a58baaf053a..2ff929399d3 100644 --- a/packages/effect/src/unstable/cli/CliError.ts +++ b/packages/effect/src/unstable/cli/CliError.ts @@ -359,12 +359,6 @@ export class InvalidValue extends Schema.TaggedErrorClass( /** * Formats the invalid flag or argument value with the expected input. * - * **Details** - * - * When `expected` is already a complete `Expected ...` sentence (e.g. a - * schema decode message), it is appended as-is instead of being prefixed - * with another `Expected:` label. - * * @since 4.0.0 */ override get message() {