util: parseArgs() treats a `-` inside a short option group as the `--` terminator

Open Beginner friendly
#67,226 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

@yohannesjoya is already working on this.

Since Nov 3, 2026.

  • #67228 by @yohannesjoya — open

Assessment

Difficulty
2/5
Estimated time
1-3 hours
Newbie friendliness
75/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
javascript, nodejs
Domain
cli

Research direction

Start with argsToTokens() in lib/internal/util/parse_args/parse_args.js, where a short option group like -a-b is expanded and pushed back onto the argument list. Check how the loop orders the arg === '--' test against the group counter, so a synthesized -- is not treated as the real terminator. Run the reproduction from the issue with strict and non-strict modes, plus tokens: true, and confirm -a-b yields an unknown-option error and each token's index matches its source argument. Done when the existing parseArgs tests pass and new cases cover -a-b and -a-.

Written by the indexing model from the issue text.

Description

Version

v28.0.0-pre (main, d2812af0), also v22.20.0

Platform

Linux 7.0.0-38-generic x86_64

Subsystem

util

What steps will reproduce the bug?
const { parseArgs } = require('node:util');

const options = { a: { type: 'boolean' }, b: { type: 'boolean' } };

// Non-strict, with tokens.
const args = ['-a-b', 'pos'];
const { values, positionals, tokens } = parseArgs({
  args, options, strict: false, allowPositionals: true, tokens: true,
});
console.log(values, positionals);
for (const token of tokens) {
  console.log(token, '-> args[index] =', args[token.index]);
}

// Strict.
for (const arg of ['-a-b', '-a-']) {
  try {
    parseArgs({ args: [arg], options });
    console.log(arg, 'ok');
  } catch (err) {
    console.log(arg, err.code, '-', err.message);
  }
}
How often does it reproduce? Is there a required condition?

Every time. Needs a - character inside a short option group, for example -a-b or -a-.

What is the expected behavior?

- is not a valid option character, so -a-b should be reported the same way as any other unknown short option in a group (compare -ar with r unknown). In strict mode that is ERR_PARSE_ARGS_UNKNOWN_OPTION. With tokens: true, every token's index should point at the argument it came from, as the docs say: "the source argument for a token is args[token.index]".

What do you see instead?
[Object: null prototype] { a: true } [ '-b', 'pos' ]
{ kind: 'option', name: 'a', rawName: '-a', index: 0, value: undefined, inlineValue: undefined } -> args[index] = -a-b
{ kind: 'option-terminator', index: 0 } -> args[index] = -a-b
{ kind: 'positional', index: 1, value: '-b' } -> args[index] = pos
{ kind: 'positional', index: 2, value: 'pos' } -> args[index] = undefined
-a-b ERR_PARSE_ARGS_UNEXPECTED_POSITIONAL - Unexpected argument '-b'. This command does not take positional arguments
-a- ok
  • -b becomes a positional instead of an option.
  • An option-terminator token appears although the user never passed --.
  • The positional indexes are shifted by one, so args[token.index] is the wrong argument or undefined.
  • In strict mode -a-b reports a misleading unexpected-positional error, and -a- is accepted silently.
Additional information

argsToTokens() in lib/internal/util/parse_args/parse_args.js expands a short option group -a-b into -a, --, -b and pushes them back onto the argument list. The loop then checks arg === '--' before it looks at whether the argument came from a group, so the synthesized -- is handled as the real terminator. The group counter (groupCount) already knows the argument was expanded.

For comparison, getopt(3) reports -a-b as invalid option -- '-'.

I have a small fix with tests and can open a PR.

Dominant language
JavaScript
Stars
123k
Forks
39.3k
Avg merge
4d 15h
Merged PRs (30d)
238

Getting set up

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

More from nodejs/node

All issues in nodejs/node

Similar issues

More JavaScript issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.