Skip to content

v0.9.0 / main: CLI is completely broken on Windows (ModuleNotFoundError: fcntl) — includes --version #128

Description

@KNambiarDJsc

Severity: release-blocking — pip install repo2rlenv is completely non-functional on Windows right now

Every repo2rlenv CLI invocation crashes on Windows before argument parsing even completes, including --version. This is not on a branch — it's in the current PyPI release (confirmed below), so anyone on Windows who installs the package today gets a tool that cannot run a single command.

Reproduction

# Fresh venv, real PyPI install — no git, no fork, no local state
uv venv && uv pip install repo2rlenv
repo2rlenv --version
ModuleNotFoundError: No module named 'fcntl'

Same result from a from-scratch git clone https://github.com/huggingface/Repo2RLEnv.git + uv sync --group dev + uv run repo2rlenv --version, and from the main branch tip directly (23616db) — so it isn't specific to the PyPI packaging step either.

Environment: Windows 11 Pro, Python 3.12.11.

Root cause

cli.py's dispatcher imports several new subsystems unconditionally, before routing to any subcommand — so this runs on every invocation, --version included:

# main() -> _dispatch()
from repo2rlenv.quality.loop.cli import add_quality_parser   # cli.py:1036
...
from repo2rlenv.tasksmith.cli import add_parser as add_tasksmith_parser

Four files reachable from that chain do import fcntl at module level — fcntl is POSIX-only and does not exist in Windows' Python stdlib at all, so no amount of pip install-ing fixes it:

  • src/repo2rlenv/quality/loop/runner.py:5
  • src/repo2rlenv/tasksmith/runner.py:6
  • src/repo2rlenv/tasksmith/batch.py:6
  • src/repo2rlenv/pipelines/recipes/history/worker.py:6

In each case it's used for the same thing — advisory, non-blocking file locking to stop two concurrent controllers/loops from running against the same output directory:

with (self.directory / ".lock").open("a") as lock:
    fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)

Trace confirming the exact import chain (_dispatchquality.loop.cliquality.loop.runnerimport fcntlModuleNotFoundError) is in the repro above; happy to paste the full builtins.__import__ trace if useful.

Timeline

  • be393f9 (feat: consolidate Tasksmith and owned Harbor generation recipes #109, "feat: consolidate Tasksmith and owned Harbor generation recipes") merged 2026-09-15T11:03:56Z — 468 files, +59,156/-187, 0 reviews, self-merged by its author.
  • All CI checks on that PR were green (Tests (py3.12/3.13/3.14), Lint, Build, etc.) — CI runs Ubuntu only, and fcntl exists on Linux, so nothing in the matrix could have caught this.
  • v0.9.0 was tagged and released to PyPI 2026-09-15T11:53:16Z, ~50 minutes later, from a commit that includes be393f9. Confirmed via git merge-base --is-ancestor be393f9 v0.9.0 and by installing repo2rlenv==0.9.0 from PyPI directly (repro above).

Proposed fix

Same shape at all four call sites — guard the import and the lock acquisition by platform, since fcntl.flock has no drop-in Windows equivalent in the stdlib (msvcrt.locking is the nearest analog but has a different API; the filelock package, already a common transitive dep in this kind of stack, is the more portable fix if a third dependency is acceptable):

import sys

if sys.platform != "win32":
    import fcntl

...
with (self.directory / ".lock").open("a") as lock:
    if sys.platform != "win32":
        fcntl.flock(lock, fcntl.LOCK_EX | fcntl.LOCK_NB)
    # else: single-controller-per-directory isn't enforced on Windows yet

Separately, and lower-priority than the crash itself: _dispatch() eagerly importing every subsystem (campaigns, quality.loop, tasksmith, pipelines.recipes) just to register their argument parsers means a bug in any of them (not just this one) takes down the entire CLI, including --version and --help. Lazily importing each subparser's module only when that subcommand is actually invoked (argparse supports this via a thin wrapper, or add_parser(..., func=lambda args: _lazy_import_and_run(args))) would contain future failures to the subcommand that has them, rather than the whole binary.

Happy to send a PR for the four-file fcntl fix if useful — it's mechanical and doesn't touch the locking semantics on POSIX.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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