subframe7536/maple-font · error · Exception

Error reading file: {file_path} - {e}

Error message

Error reading file: {file_path} - {e}

What it means

get_directory_hash walks a directory and hashes each file in 4KB chunks. If any file cannot be read due to an IOError/OSError (permission denied, file vanished mid-walk, too many open files, etc.), it wraps the OS error in this Exception with the file path. It is thrown so update_dir_hash / check_directory_hash callers get one consistent error type naming the offending file.

Source

Thrown at source/py/utils.py:278

    return sha256.hexdigest(), zip_name_without_ext


def get_directory_hash(dir_path: str) -> str:
    hasher = hashlib.sha256()
    for root, _, files in sorted(walk(dir_path)):
        for file in sorted(files):
            file_path = path.join(root, file)
            try:
                with open(file_path, "rb") as f:
                    while True:
                        # 4KB chunk size
                        chunk = f.read(4096)
                        if not chunk:
                            break
                        hasher.update(chunk)

            except (IOError, OSError) as e:
                raise Exception(f"Error reading file: {file_path} - {e}")

    return hasher.hexdigest()


def check_directory_hash(dir_path: str, hash_path: str | None = None) -> bool:
    if not path.exists(dir_path):
        print(f"{dir_path} not exist, skip computing hash")
        return False
    with open(hash_path or f"{dir_path}.sha256", "r") as f:
        return f.readline() == get_directory_hash(dir_path)


def merge_ttfonts(
    base_font_path: str, extra_font_path: str, use_pyftmerge: bool = False
) -> TTFont:
    """
    Merge glyphs from ``source_font`` into ``base_font``, skipping duplicate glyph names.

View on GitHub (pinned to c08fda97fe)

Solutions

  1. Fix permissions on the reported file (chmod/chown) or rerun the command with sufficient privileges.
  2. Remove or exclude unreadable/special files (symlinks, sockets) from the hashed directory.
  3. Rerun the build if the file was transiently deleted; stop processes racing on the directory.
  4. Raise the open-file limit (ulimit -n) if the directory contains very many files.

Example fix

// before
# update_dir_hash('/build/output')  # PermissionError wrapped
// after (shell)
# sudo chmod -R u+rw /build/output && python build.py
Defensive patterns

Strategy: try-catch

Validate before calling

import os

def assert_dir_readable(dir_path):
    for root, _, files in os.walk(dir_path):
        for f in files:
            p = os.path.join(root, f)
            if not os.path.isfile(p) or os.path.islink(p):
                continue
            with open(p, "rb"):
                pass  # raises PermissionError before hashing starts

Type guard

import os, stat

def is_readable_regular_file(path: str) -> bool:
    try:
        st = os.stat(path)
        return stat.S_ISREG(st.st_mode) and os.access(path, os.R_OK)
    except OSError:
        return False

Try / catch

try:
    h = update_dir_hash(dir_path)
except Exception as e:
    if str(e).startswith("Error reading file:"):
        print("Fix permissions or remove unreadable file:", e)
    raise

Prevention

When it happens

Trigger: Calling update_dir_hash(dir_path) or check_directory_hash(dir_path) when any file under dir_path cannot be opened for reading: unreadable permissions, a broken symlink, a file deleted between directory scan and read, or an unreadable special file.

Common situations: Running the build in a container/CI where the output dir contains root-owned files; antivirus or another process deleting files mid-hash; hashing a directory containing sockets/fifos; hitting the open-file limit with many files.


AI-assisted analysis of subframe7536/maple-font@c08fda97fe (2026-08-28). Data as JSON: /api/errors/aaf39366c5e7c115. Report an issue: GitHub.