dgtlmoon/changedetection.io · critical · OSError

I/O error saving {label}: {e}

Error message

I/O error saving {label}: {e}

What it means

The fallback OSError raised by save_json_atomic for any write error that is neither ENOSPC (28) nor EDQUOT (122). The original exception is chained ('from e') and its message embedded, so the real underlying I/O problem (permissions, path issues, hardware errors) is preserved.

Source

Thrown at changedetectionio/store/file_saving_datastore.py:161

        # Cleanup temp file
        if not fd_closed:
            try:
                os.close(fd)
            except:
                pass
        if os.path.exists(temp_path):
            try:
                os.unlink(temp_path)
            except:
                pass

        # Provide helpful error messages
        if e.errno == 28:  # ENOSPC
            raise OSError(f"Disk full: Cannot save {label}") from e
        elif e.errno == 122:  # EDQUOT
            raise OSError(f"Disk quota exceeded: Cannot save {label}") from e
        else:
            raise OSError(f"I/O error saving {label}: {e}") from e

    except Exception as e:
        # Cleanup temp file
        if not fd_closed:
            try:
                os.close(fd)
            except:
                pass
        if os.path.exists(temp_path):
            try:
                os.unlink(temp_path)
            except:
                pass
        raise e


def save_entity_atomic(entity_dir, uuid, entity_dict, filename, entity_type, max_size_mb):
    """

View on GitHub (pinned to 5d9c7c6da7)

Solutions

  1. Read the appended ': {e}' — it contains errno/strerror pinpointing the cause
  2. Fix ownership/permissions: chown -R <app-user> <datastore-path> && chmod u+w
  3. Confirm the datastore directory exists and the mount is healthy (NFS/SMB not stale)
  4. If disk hardware errors appear (EIO/medium error), move data off and replace/check the disk
Defensive patterns

Strategy: try-catch

Validate before calling

import os
path = datastore_path
assert os.path.isdir(path) and os.access(path, os.W_OK), 'datastore not writable'

Try / catch

try:
    datastore._save_settings()
except OSError as e:
    logger.critical('settings save failed: %s (errno attached via __cause__)', e)
    # inspect e.__cause__ for the original errno (EACCES/EIO/ENOENT)

Prevention

When it happens

Trigger: Atomic save failing with e.g. EACCES (datastore dir not writable / ownership changed after running as a different user), ENOENT (datastore path deleted or never created), EIO (failing disk/NFS stale handle), or EBADF on a closed descriptor during write.

Common situations: Running the container once as root then again as a non-root user, leaving root-owned files; bind-mount misconfiguration where the host dir has wrong perms; NAS/SMB mounts dropping; datastore directory removed while the app ran.

Related errors


AI-assisted analysis of dgtlmoon/changedetection.io@5d9c7c6da7 (2026-08-27). Data as JSON: /api/errors/b936206a6172ca7e. Report an issue: GitHub.