Skip to content

[Bug]: an adapter arg named json crashes every webcmd command at startup (official linkedin plugin ships one) #441

Description

@Agnik47

Summary

An adapter that declares an argument named json makes every webcmd
command crash at startup with an unhandled Commander error. The official
LinkedIn plugin ships exactly such an adapter (thread-snapshot), so
installing it bricks the CLI — including plugin uninstall, so there is no
recovery path through webcmd itself.

Environment

  • webcmd 0.7.6, run from source at 37036a6 (clean main, no local changes)
  • Node.js v22.18.0
  • Windows 11

Reproduction

Install the official LinkedIn plugin so it lands at
~/.webcmd/plugins/linkedin/ (I placed the repo's plugins/linkedin there
directly, since I was running from a source checkout — same result as
webcmd plugin install github:agentrhq/webcmd/linkedin), then run any
command:

$ webcmd --version
0.7.6                     # survives: exits before command registration

$ webcmd list
node_modules\commander\lib\command.js:628
      throw new Error(`Cannot add option '${option.flags}'...
            ^

Error: Cannot add option '--json' to command 'thread-snapshot' due to conflicting flag '--json'
-  already used by option '--json [value]'
    at Command._registerOption (node_modules\commander\lib\command.js:628:13)
    at Command.addOption (node_modules\commander\lib\command.js:671:10)
    at Command._optionEx (node_modules\commander\lib\command.js:763:17)
    at Command.option (node_modules\commander\lib\command.js:789:17)
    at addOutputFormatOption (src\command-surface.ts:448:6)
    at configureCommandSurface (src\command-surface.ts:228:3)
    at registerCommandToProgram (src\commanderAdapter.ts:54:3)
    at registerAllCommands (src\commanderAdapter.ts:188:7)
    at createProgram (src\cli.ts:2356:21)
    at runCli (src\cli.ts:2488:19)

Every command that reaches command registration fails identically:

command result
webcmd --version ok
webcmd list crash
webcmd doctor crash
webcmd plugin list crash
webcmd plugin uninstall linkedin crash

Because plugin uninstall crashes too, the only way out is deleting
~/.webcmd/plugins/linkedin by hand.

Two further problems with the failure itself: it is an unhandled exception
that prints a raw Node stack trace rather than a structured webcmd error,
and the process still exits 0, so a script driving the CLI reads the crash
as success.

Root cause

plugins/linkedin/thread-snapshot.js:162 declares an adapter argument named
json:

args: [
  { name: 'thread-url', required: true, ... },
  { name: 'max-scrolls', type: 'number', default: 30, ... },
  { name: 'json', type: 'bool', default: false, help: 'Return only JSON snapshot string in the snapshot_json field' },
],

configureCommandSurface (src/command-surface.ts:212) registers the
adapter's own arguments first, then adds the shared output-format options
unconditionally:

export function addOutputFormatOption(command, defaultFormat = 'table') {
  return command
    .option('-f, --format <fmt>', OUTPUT_FORMAT_HELP, defaultFormat)
    .option('--json', JSON_FORMAT_ALIAS_HELP, false);   // <-- no collision check
}

--json is already taken by the adapter's argument, so Commander throws. The
throw happens inside createProgram(), i.e. before any command runs, which is
why it takes down the whole CLI rather than just linkedin thread-snapshot.

The same file already knows how to do this safely — ensureOutputFormatOptions
(src/command-surface.ts:465) checks the registered flags first, and even
handles a partial -f/--format collision:

const flags = new Set<string>();
for (const option of child.options) {
  if (option.short) flags.add(option.short);
  if (option.long) flags.add(option.long);
}
if (!flags.has('--format')) {
  child.option(flags.has('-f') ? '--format <fmt>' : '-f, --format <fmt>', OUTPUT_FORMAT_HELP, 'table');
}
if (!flags.has('--json')) child.option('--json', JSON_FORMAT_ALIAS_HELP, false);

The adapter-registration path just never got that guard.

Scope

thread-snapshot is the only adapter in the repo that currently collides, but
the same crash is reachable from any adapter argument named after a shared
option that configureCommandSurface adds — format, json, trace,
verbose, and for browser commands window, site-session, keep-tab. Any
community or private adapter declaring one of those bricks the CLI the moment
it is installed.

Expected behaviour

Installing a plugin should never be able to prevent webcmd from starting. At
minimum, a shared option whose flag an adapter already declares should be
skipped rather than registered twice, matching what ensureOutputFormatOptions
already does.

Worth deciding separately: whether an adapter should be allowed to declare an
argument that shadows a reserved flag at all, or whether webcmd validate
should reject those names up front. Happy to send a PR for the crash guard
either way.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions