Skip to content

CLI crashes (Arg neither flag name nor flag value) when a positional argument follows a flag #2198

@ihistand

Description

@ihistand

Summary

The global flag parser in common/flags/index.ts runs in a static initializer at module-load time and throws Arg neither flag name nor flag value: <arg> whenever a positional argument appears after a --flag in process.argv. Because it runs before — and independently of — yargs, this crashes the entire CLI for otherwise-valid invocations where the user simply puts a positional after an option.

Reproduction

# Works — positional before the flag:
dataform run my_project --schema-suffix dev

# Crashes — same arguments, positional after the flag:
dataform run --schema-suffix dev my_project

Output:

Error: Arg neither flag name nor flag value: my_project
    at .../common/flags/index.ts:71
    at Module._compile (node:internal/modules/cjs/loader)
    ...

(Any command + any flag-before-positional combination triggers it, since the parser scans process.argv at import time regardless of the command.)

Expected behavior

Both argument orderings should work. yargs already parses options and positionals in any order; the lightweight common/flags parser shouldn't reject a valid ordering — and certainly shouldn't crash the process at load time.

Root cause

Flags.parsedArgv is built in a static initializer that scans process.argv. Once it sees any --flag it sets flagsStarted = true; a later non-flag token that isn't consumed as a flag value hits:

} else if (flagsStarted) {
  throw new Error(`Arg neither flag name nor flag value: ${splitArg}`);
}

This treats positionals-after-flags as fatal. But this flag system coexists with yargs and only needs to extract --flag value pairs; positionals (the command, project dir, etc.) are yargs's responsibility and should be ignored, not fatal. The throw also fires at import time, so it takes down the CLI before any command handler runs.

Proposed fix

Make the parser lenient — ignore tokens that are neither a flag nor a flag value, instead of throwing:

let currentFlagName = "";
for (const splitArg of splitArgv) {
  if (splitArg.startsWith("--")) {
    currentFlagName = splitArg.slice(2);
    parsedArgv[currentFlagName] = "";
  } else if (currentFlagName) {
    parsedArgv[currentFlagName] = splitArg;
    currentFlagName = "";
  }
  // Non-flag args (command, positionals) are ignored — yargs parses those.
}

(Extracting the loop into a small pure function, e.g. parseArgvFlags(argv), also makes it unit-testable.)

Environment

  • Verified present in common/flags/index.ts on main.
  • Happy to open a PR with the fix and a unit test if that's useful.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions