Skip to content

vp staged --no-concurrent silently stalls: tasks never run #2488

Description

@leslieeilsel

Summary

vp staged --no-concurrent never executes any task. Depending on the runtime
context, the process either hangs indefinitely or exits with code 13 and a
cryptic unsettled top-level await warning — in both cases with no output
indicating that nothing ran. In a pre-commit hook, the exit-13 variant blocks
the commit without explanation.

Reproduction

mkdir repro && cd repro
git init
cat > vite.config.ts <<'EOF'
export default { staged: { '*.txt': 'echo linted' } }
EOF
echo hi > a.txt
git add -A .

vp staged --no-concurrent

Expected: serial execution (same as --concurrent false).
Actual: tasks never start; the run hangs or exits 13.

Root cause

packages/cli/src/staged/bin.ts parses argv with mri, with concurrent
declared in the string list. mri returns a boolean false for
--no-<flag> regardless of the string list:

mri(['--no-concurrent'], { string: ['concurrent'] }) // → { concurrent: false }

The strict comparison val === 'false' only matches the string spelling, so
the boolean falls through to the else branch, where Number(false) coerces
to 0. lint-staged's task queue (listr2 Concurrency) never starts a task
when the concurrency limit is 0 (this.count < this.concurrency is 0 < 0),
so the run deadlocks without output.

Note this was an accident, not a design choice: serial mode via false has
been documented in --help since the initial commit (#674), and the parsing
mirrors lint-staged's own CLI (JSON.parse of the value). The --no-
spelling was simply mishandled from day one.

Related

The same mri quirk leaks boolean false into the other string options in the
same file, so these also misbehave (exit 1 with confusing errors):

  • vp staged --no-cwdFailed to load vite.config: paths[0] argument must be of type string. Received type boolean (false)
  • vp staged --no-diffFailed to get staged files! (and the backup stash is skipped)

Explicit -p 0 / --concurrent=0 reach the same concurrency-0 deadlock via
the else branch, since 0 passes the Number.isNaN(num) || val === ''
guard.

Environment

Metadata

Metadata

Assignees

Type

No type

Fields

Priority

None yet

Projects

No projects

Milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions