Skip to content

fix(cli): allow parent options after subcommand arguments#6000

Open
cevr wants to merge 3 commits intoEffect-TS:mainfrom
cevr:fix/cli-parent-options-after-subcommand
Open

fix(cli): allow parent options after subcommand arguments#6000
cevr wants to merge 3 commits intoEffect-TS:mainfrom
cevr:fix/cli-parent-options-after-subcommand

Conversation

@cevr
Copy link
Contributor

@cevr cevr commented Jan 26, 2026

Summary

Follow-up to #5983. Extends parent option parsing to work across subcommand boundaries.

Enables parent/global options to appear anywhere in the command line, including after subcommand names and their arguments. This follows the common CLI pattern used by git, npm, docker, and most modern CLI tools.

Before

cli --global-opt subcommand arg     # works
cli subcommand arg --global-opt     # fails: "unknown option"

After

cli --global-opt subcommand arg     # works  
cli subcommand arg --global-opt     # works ✓

Motivation: The Centralized Flags Pattern

Many CLI applications define global options on the parent command that affect all subcommands:

const app = Command.make("app", {
  verbose: Options.boolean("verbose").pipe(Options.withAlias("v")),
  model: Options.text("model").pipe(Options.withAlias("m")),
}).pipe(
  Command.withSubcommands([process, export, list])
)

Users naturally expect to place these global options at the end of their command:

# Natural user expectation
app process input.txt -m gpt4 --verbose

# What was previously required  
app -m gpt4 --verbose process input.txt

This change removes that friction by extracting known parent options from anywhere in the args.

Implementation

  1. getParentOptionNames() - Collects all option names (including aliases) from a parent command
  2. extractParentOptionsFromArgs() - Scans args after the subcommand for known parent options and extracts them
  3. Modified Subcommands case in parseInternal to use these helpers before splitting args

Uses Set for O(1) lookups on both subcommand names and parent option names.

Test Plan

  • Added tests for parent options after subcommand args
  • Added tests for parent option aliases after subcommand args
  • Verified existing tests still pass (162 tests)
  • Tested with --option=value format
  • Tested mixed positioning (some before, some after subcommand)

🤖 Generated with Claude Code

@cevr cevr requested a review from IMax153 as a code owner January 26, 2026 03:20
@github-project-automation github-project-automation bot moved this to Discussion Ongoing in PR Backlog Jan 26, 2026
@changeset-bot
Copy link

changeset-bot bot commented Jan 26, 2026

🦋 Changeset detected

Latest commit: 9d14d28

The changes in this PR will be included in the next version bump.

This PR includes changesets to release 1 package
Name Type
@effect/cli Patch

Not sure what this means? Click here to learn what changesets are.

Click here if you're a maintainer who wants to add another changeset to this PR

Enables parent/global options to appear anywhere in the command line,
including after subcommand names and their arguments. This follows the
common CLI pattern used by git, npm, docker, and most modern CLI tools.

Before:
  cli --global-opt subcommand arg     # works
  cli subcommand arg --global-opt     # fails: "unknown option"

After:
  cli --global-opt subcommand arg     # works
  cli subcommand arg --global-opt     # works

This is useful for the "centralized flags" pattern where global options
like --verbose, --config, or --model are defined on the parent command
and inherited by all subcommands. Users can now place these options
at the end of the command for better ergonomics.

Implementation:
- Extract parent option names before splitting args at subcommand boundary
- Scan args after subcommand for known parent options
- Pass extracted parent options to parent command parsing

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
@cevr cevr force-pushed the fix/cli-parent-options-after-subcommand branch from ad5704b to c8c5f43 Compare January 26, 2026 03:22
@IMax153
Copy link
Member

IMax153 commented Feb 6, 2026

@cevr - I don't think the PR can be accepted in it's current form.

We would need to document the precise behavior of what occurs when a parent command and subcommand share a common option. We would also need to document that that this behavior is only valid for options (not arguments).

In addition, we would need several more tests to ensure the implemented behavior is what we expect.

cevr and others added 2 commits February 6, 2026 13:04
…on extraction

Child wins for shared options after subcommand (matches CLI conventions).
Boolean parent options no longer steal the next token as a value.
Adds comprehensive descriptor-level and integration tests.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ions

The FAQ and usage sections incorrectly stated options must appear before
positional args and subcommands. Updated to reflect the new behavior and
document shared option resolution (child wins after subcommand).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

Status: Discussion Ongoing

Development

Successfully merging this pull request may close these issues.

2 participants