Skip to content

Fix: name the unknown subcommand instead of reporting too many arguments (stacked on #597) - #706

Open
AmaadMartin wants to merge 3 commits into
fix/adk-js-bare-invocation-usagefrom
fix/cli-unknown-subcommand-message
Open

Fix: name the unknown subcommand instead of reporting too many arguments (stacked on #597)#706
AmaadMartin wants to merge 3 commits into
fix/adk-js-bare-invocation-usagefrom
fix/cli-unknown-subcommand-message

Conversation

@AmaadMartin

@AmaadMartin AmaadMartin commented Aug 6, 2026

Copy link
Copy Markdown
Owner

Please ensure you have read the contribution guide before creating a pull request.

Link to Issue or Description of Change

  1. Link to an existing issue (if applicable):
    N/A

  2. Or, if no issue exists, describe the change:

Problem: adk bogus answers with error: too many arguments. Expected 0 arguments but got 1., which never names the word the user mistyped. The root command carries an action handler, so commander skips its unknownCommand() branch and falls through to the operand count check. adk deploy bogus already prints error: unknown command 'bogus' because the deploy group has no action handler.

Solution: The root action now reads its leftover operands and raises commander's own unknown-command error for the first one. program.allowExcessArguments(true) lets the operand reach the action; it is set after every .command() call because Command.command() copies that setting into new subcommands. A variadic root argument would also work, but it adds [command...] to the usage line and an Arguments: section to adk --help.

Behaviour before and after:

Invocation before after
adk bogus error: too many arguments. Expected 0 arguments but got 1. error: unknown command 'bogus'
adk bogus extra error: too many arguments. Expected 0 arguments but got 2. error: unknown command 'bogus'
adk deploy bogus error: unknown command 'bogus' unchanged
adk web a b c error: too many arguments for 'web'. unchanged
adk --nope error: unknown option '--nope' unchanged

Exit code stays 1 on every error path.

Four notes for the reviewer:

  • Why the message is hand-built. Commander's own Command.prototype.unknownCommand() emits this exact string, but it is absent from commander/typings/index.d.ts and suggestSimilar is not exported, so reaching either needs a cast. One consequence: the root has no suggestion hint. adk depoy prints error: unknown command 'depoy' while adk deploy cloud_ru still adds (Did you mean cloud_run?). Re-implementing the suggestion is out of scope.
  • Stacked on Fix: print usage for a bare adk invocation instead of only the version #597. That PR rewrites the same root action and adds a test asserting commander.excessArguments for adk bogus — the behaviour this change fixes. I checked all 597 open PRs on the fork; none implements this fix, so I stacked rather than branching from main. This PR targets fix/adk-js-bare-invocation-usage.
  • One existing assertion changed. err.code in 'should exit non-zero for an unknown subcommand' now reads commander.unknownCommand. That assertion came in with Fix: print usage for a bare adk invocation instead of only the version #597 and encoded the old message, so it could not survive this fix. Its other assertions (exitCode === 1, empty stdout) are untouched, and the flip is isolated in commit 1.
  • console.log(version) is pre-existing. I did not add it and did not refactor it.

adk help and adk help web do not work today: commander suppresses its implicit help subcommand when the root has an action handler. They now report error: unknown command 'help' instead of an argument count. Restoring a working help command is separate work and is not in this PR.

Testing Plan

Please describe the tests that you ran to verify your changes. This is required for all PRs that are not small documentation or typo fixes.

Unit Tests:
[x] I have added or updated unit tests for my change.
[x] All unit tests pass locally.

npx vitest run --project unit:dev dev/test/cli/cli_test.ts — 31 passed.

A new describe('unknown subcommand') block adds four cases. It applies exitOverride() and configureOutput() recursively over program.commands, because the file's outer beforeEach sets them on the root only and a subcommand error would otherwise call the real process.exit(1).

  1. adk bogus names the command, throws commander.unknownCommand with exitCode 1, and does not print the version.
  2. adk bogus extra reports bogus and not extra.
  3. Bare adk stays on the non-error path.
  4. adk create x y still throws commander.excessArguments — the regression test for where allowExcessArguments(true) sits.

Each test was run against mutated source and observed to fail:

Mutation Failing tests Message
Delete the guard from the root action 1, 2 and the existing unknown-subcommand test expected commander to reject an unknown subcommand
Delete program.allowExcessArguments(true) 1, 2 and the existing test expected 'error: too many arguments. Expected 0…' to contain 'error: unknown command \'bogus\''
Move program.allowExcessArguments(true) above program.command('web') 4 expected commander to reject excess subcommand arguments
Drop {code: 'commander.unknownCommand'} 1, 2 and the existing test expected 'commander.error' to be 'commander.unknownCommand'
Make the guard fire unconditionally 3 and the bare-invocation tests expected CommanderError: error: unknown command 'u… to be undefined

Coverage of the added lines is 100% line and 100% branch, measured with --coverage.include='dev/src/cli/cli.ts'. The file total is 95.42% line / 75.55% branch; every uncovered range is a pre-existing catch block in the deploy and integration actions.

npm run ts:check reports 281 errors on the base commit and 281 with this change, none in the two files touched here. npx eslint and npx prettier --check are clean on both files.

CI does not run on this PR. .github/workflows/validation.yaml triggers on pull_request: branches: [main], and this PR targets fix/adk-js-bare-invocation-usage. Only auto-assign ran. I ran the workflow's own steps locally on the pushed commit 857457d9395be8eef8f94b3419aede6f48c83413:

Command Result
npm run build pass
npx vitest run --project unit:dev dev/test/cli/cli_test.ts 31 passed
npm run lint pass
npm run format:check pass
npm run docs:check pass
npx secretlint "dev/**/*.ts" pass

Manual End-to-End (E2E) Tests:
Please provide instructions on how to manually test your changes, including any necessary setup or configuration.

npm run build --workspace @google/adk-devtools
node dev/dist/esm/cli_entrypoint.js bogus

Transcript:

$ adk bogus
error: unknown command 'bogus'
exit=1
----
$ adk bogus extra
error: unknown command 'bogus'
exit=1
----
$ adk
Usage: adk [options] [command]

Options:
  -v, --version                      Get ADK CLI version
exit=0
----
$ adk --version
1.5.0
exit=0
----
$ adk deploy bogus
error: unknown command 'bogus'
exit=1
----
$ adk web a b c
error: too many arguments for 'web'. Expected 1 argument but got 3.
exit=1
----
$ adk --nope
error: unknown option '--nope'
exit=1
----
$ adk --help
Usage: adk [options] [command]

Options:
  -v, --version                      Get ADK CLI version
  -h, --help                         display help for command

Commands:
  web [options] [agents_dir]         Start ADK web server
  api_server [options] [agents_dir]  Start ADK API server
  create [options] [agent]           Creates a new agent
  run [options] <agent>              Runs agent
  deploy                             Deploy agent
  integration                        Run ADK integration and conformance tests
exit=0

adk web a b c is the leak check for the allowExcessArguments(true) placement. adk --help shows no Arguments: section and an unchanged usage line.

Checklist

[x] I have read the CONTRIBUTING.md document.
[x] I have performed a self-review of my own code.
[x] I have commented my code, particularly in hard-to-understand areas.
[x] I have added tests that prove my fix is effective or that my feature works.
[x] New and existing unit tests pass locally with my changes.

Amaad Martin added 3 commits August 6, 2026 01:15
The root command carries an action handler, so commander never reaches its
unknownCommand() branch and answers a typo with an argument count instead.
Guard the root action on its leftover operands and raise commander's own
unknown-command error.

The one pre-existing assertion that pinned commander.excessArguments for
`adk bogus` encoded the behaviour this change fixes, so it now pins
commander.unknownCommand.
… guard

Cover both branches of the new root guard, and pin that
allowExcessArguments(true) stays after the subcommand registrations so the
subcommands keep rejecting their own excess arguments.
An unchecked cast reported any unrelated throw as a commander error with an
undefined code, which hid the real stack. Unexpected errors now rethrow.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant