python/cpython · error · ValueError

opener returned {fd}

Error message

opener returned {fd}

What it means

Raised by FileIO.__init__ when a custom opener returns a negative integer (f-string 'opener returned {fd}'). Added for bpo-27066: previously a negative return was silently passed to os.fstat and produced a confusing failure, so it is now an explicit ValueError at the boundary.

Source

Thrown at Lib/_pyio.py:1613

        noinherit_flag = (getattr(os, 'O_NOINHERIT', 0) or
                          getattr(os, 'O_CLOEXEC', 0))
        flags |= noinherit_flag

        owned_fd = None
        try:
            if fd < 0:
                if not closefd:
                    raise ValueError('Cannot use closefd=False with file name')
                if opener is None:
                    fd = os.open(file, flags, 0o666)
                else:
                    fd = opener(file, flags)
                    if not isinstance(fd, int):
                        raise TypeError('expected integer from opener')
                    if fd < 0:
                        # bpo-27066: Raise a ValueError for bad value.
                        raise ValueError(f'opener returned {fd}')
                owned_fd = fd
                if not noinherit_flag:
                    os.set_inheritable(fd, False)

            self._closefd = closefd
            self._stat_atopen = os.fstat(fd)
            try:
                if stat.S_ISDIR(self._stat_atopen.st_mode):
                    raise IsADirectoryError(errno.EISDIR,
                                            os.strerror(errno.EISDIR), file)
            except AttributeError:
                # Ignore the AttributeError if stat.S_ISDIR or errno.EISDIR
                # don't exist.
                pass

            if _setmode:
                # don't translate newlines (\r\n <=> \n)
                _setmode(fd, os.O_BINARY)

View on GitHub (pinned to bc6749cc3b)

Solutions

  1. Raise an OSError (or let the original exception propagate) in the opener instead of returning -1
  2. Validate the return right before handing it back: fd = ...; if fd < 0: raise OSError(...); return fd
  3. Treat the opener contract strictly: it must return a valid non-negative open fd or raise

Example fix

# before
def opener(path, flags):
    try:
        return os.open(path, flags)
    except OSError:
        return -1                       # ValueError: opener returned -1

# after
def opener(path, flags):
    return os.open(path, flags)          # failures raise OSError directly
Defensive patterns

Strategy: validation

Validate before calling

def checked_opener(path, flags):
    fd = opener(path, flags)
    if not isinstance(fd, int):
        raise TypeError('expected integer from opener')
    if fd < 0:
        raise ValueError(f'opener returned {fd}')
    return fd
f = open(path, 'wb', opener=checked_opener)

Try / catch

try:
    f = open(path, 'wb', opener=opener)
except ValueError as e:
    if 'opener returned' in str(e):
        f = open(path, 'wb')  # fall back to default os.open behavior
    else:
        raise

Prevention

When it happens

Trigger: open(path, opener=lambda p, f: -1) or an opener that translates an internal failure into a -1 return (a C-style error convention) instead of raising.

Common situations: Openers wrapping C libraries or subprocess handles that use -1 as an error sentinel; openers that catch an exception internally and return -1 rather than propagating it; partial ports of C open() semantics.

Related errors


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