Skip to content

Glob pattern behavior differs from standard implementations, causing user confusion #1187

@jasonwbarnett

Description

@jasonwbarnett

Summary

The current glob pattern behavior in lefthook, particularly with **, differs from standard glob implementations and causes confusion for users. While the documentation does mention this, the non-standard behavior creates friction for users coming from other tools and makes patterns harder to reason about.

Current Behavior

In lefthook using gobwas/glob:

  • *.js matches files at all levels (both root and nested): file.js, folder/file.js, a/b/file.js
  • **/*.js matches files only in nested directories (1+ levels deep): folder/file.js, a/b/file.js
  • **/*.js does NOT match file.js at the root level

This means:

glob: "*.js"  # matches both root and nested - unusual behavior!

Expected/Standard Behavior

In most glob implementations (bash, zsh, .gitignore, pre-commit, lint-staged, etc.):

  • *.js matches files only at the root level: file.js
  • **/*.js matches files at all levels (0+ directories): file.js, folder/file.js, a/b/c/file.js

Why This Matters

  1. Unexpected * behavior: The * wildcard recursively matching subdirectories is very unusual and counterintuitive
  2. Inverted semantics: ** requiring at least one directory is the opposite of standard glob behavior
  3. User Expectations: Users coming from other tools expect standard glob behavior and are surprised when patterns don't work as expected
  4. Learning Curve: This adds an unnecessary learning curve and requires users to unlearn their existing glob knowledge
  5. Documentation Burden: Even with documentation, users (and AI agents assisting users) frequently get this wrong
  6. Portability: Patterns can't be easily copied from other tools like .gitignore, pre-commit configs, etc.

Real-World Impact

I've personally experienced this confusion, and when working with AI coding assistants (Claude, GitHub Copilot, etc.), they consistently generate incorrect patterns because they're trained on standard glob behavior. This creates a feedback loop where:

  1. AI suggests a pattern based on standard glob behavior
  2. Pattern doesn't work as expected in lefthook
  3. User has to debug and learn the non-standard behavior
  4. This repeats for each new pattern

Proposed Solution

I'd like to suggest one of the following approaches:

Option 1: Switch to Standard Glob Behavior (Breaking Change)

Change glob behavior to match standards in the next major version. This would be a breaking change but would align with user expectations.

Option 2: Add an Opt-in Configuration (My Implementation)

Add a glob_matcher configuration option to allow users to opt-in to standard behavior:

glob_matcher: doublestar  # or "standard"

pre-commit:
  commands:
    lint:
      glob: "**/*.js"  # now matches files at any level including root
      run: eslint {staged_files}

I've actually implemented this approach and would be happy to open a PR. The implementation:

  • Uses github.com/bmatcuk/doublestar/v4 as an alternative matcher
  • Maintains backward compatibility (default behavior unchanged)
  • Allows opt-in via configuration
  • Is fully tested with comparison tests showing both behaviors

Option 3: Better Documentation + Migration Guide

If changing the behavior isn't feasible, perhaps we could:

  • Make the non-standard behavior more prominent in docs
  • Add migration examples for common use cases
  • Include a "glob pattern translator" in the docs

Community Feedback

I'd love to hear from other users if they've experienced similar confusion. I think this is a common pain point but may not be reported often because users assume they're doing something wrong rather than the tool having non-standard behavior.

Context

I'm opening this issue with empathy and appreciation for lefthook - it's a fantastic tool and I use it daily. This isn't a criticism of the design decision, just feedback on a friction point I've encountered repeatedly. I'm happy to contribute a PR for any of the proposed solutions if the maintainers are open to addressing this.

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