python/cpython · error · ValueError

negative file descriptor

Error message

negative file descriptor

What it means

Raised by FileIO.__init__ when an integer file argument is less than zero. After the float rejection and the bool warning/conversion, an int fd is accepted only if it is >= 0; negative integers can never name an open file, so they fail fast before os.fstat is attempted.

Source

Thrown at Lib/_pyio.py:1555

            # Have to close the existing file first.
            self._stat_atopen = None
            try:
                if self._closefd:
                    os.close(self._fd)
            finally:
                self._fd = -1

        if isinstance(file, float):
            raise TypeError('integer argument expected, got float')
        if isinstance(file, int):
            if isinstance(file, bool):
                import warnings
                warnings.warn("bool is used as a file descriptor",
                              RuntimeWarning, stacklevel=2)
                file = int(file)
            fd = file
            if fd < 0:
                raise ValueError('negative file descriptor')
        else:
            fd = -1

        if not isinstance(mode, str):
            raise TypeError('invalid mode: %s' % (mode,))
        if not set(mode) <= set('xrwab+'):
            raise ValueError('invalid mode: %s' % (mode,))
        if sum(c in 'rwax' for c in mode) != 1 or mode.count('+') > 1:
            raise ValueError('Must have exactly one of create/read/write/append '
                             'mode and at most one plus')

        if 'x' in mode:
            self._created = True
            self._writable = True
            flags = os.O_EXCL | os.O_CREAT
        elif 'r' in mode:
            self._readable = True
            flags = 0

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Check the variable for the sentinel before using it as an fd: if fd < 0: raise/handle
  2. Initialize fd variables to None rather than -1 so misuse fails with a clearer error
  3. Ensure the earlier os.open/socket call actually succeeded and its return value (which raises on failure rather than returning -1 in Python) is what gets passed

Example fix

# before
fd = -1  # sentinel
... 
f = open(fd, 'rb')  # ValueError: negative file descriptor

# after
if fd is None or fd < 0:
    raise RuntimeError('file descriptor was never opened')
f = open(fd, 'rb')
Defensive patterns

Strategy: validation

Validate before calling

if isinstance(fd, bool) or not isinstance(fd, int):
    raise TypeError('fd must be an int')
if fd < 0:
    raise ValueError('negative file descriptor')
f = open(fd, 'rb')

Type guard

def is_valid_fd(fd):
    return isinstance(fd, int) and not isinstance(fd, bool) and fd >= 0

Try / catch

try:
    f = open(fd, 'rb')
except ValueError as e:
    if 'negative' in str(e):
        fd = acquire_fd()  # actually open/accept it
        f = open(fd, 'rb')
    else:
        raise

Prevention

When it happens

Trigger: open(-1, 'rb'); passing a sentinel like -1 that an earlier os.dup()/socket() failure returned, or an uninitialized fd variable defaulting to a negative sentinel; index arithmetic that produced a negative value used as an fd.

Common situations: Code that stores fd = -1 as 'no fd yet' and later passes it to open() without checking whether the real fd was ever assigned; error paths where os.open returned and the -1 default leaked through; porting C code that used -1 sentinels.

Related errors


AI-assisted analysis of python/cpython@bc6749cc3b (2026-08-14). Data as JSON: /api/errors/a4adcbbc6c4ba82a. Report an issue: GitHub.