{"record":{"id":"b936206a6172ca7e","repo":"dgtlmoon/changedetection.io","slug":"i-o-error-saving-label-e","errorCode":null,"errorMessage":"I/O error saving {label}: {e}","messagePattern":"I/O error saving (.+?): (.+?)","errorType":"exception","errorClass":"OSError","httpStatus":null,"severity":"critical","filePath":"changedetectionio/store/file_saving_datastore.py","lineNumber":161,"sourceCode":"        # Cleanup temp file\n        if not fd_closed:\n            try:\n                os.close(fd)\n            except:\n                pass\n        if os.path.exists(temp_path):\n            try:\n                os.unlink(temp_path)\n            except:\n                pass\n\n        # Provide helpful error messages\n        if e.errno == 28:  # ENOSPC\n            raise OSError(f\"Disk full: Cannot save {label}\") from e\n        elif e.errno == 122:  # EDQUOT\n            raise OSError(f\"Disk quota exceeded: Cannot save {label}\") from e\n        else:\n            raise OSError(f\"I/O error saving {label}: {e}\") from e\n\n    except Exception as e:\n        # Cleanup temp file\n        if not fd_closed:\n            try:\n                os.close(fd)\n            except:\n                pass\n        if os.path.exists(temp_path):\n            try:\n                os.unlink(temp_path)\n            except:\n                pass\n        raise e\n\n\ndef save_entity_atomic(entity_dir, uuid, entity_dict, filename, entity_type, max_size_mb):\n    \"\"\"","sourceCodeStart":143,"sourceCodeEnd":179,"githubUrl":"https://github.com/dgtlmoon/changedetection.io/blob/5d9c7c6da76340597243e8163c4f2439237fa0e8/changedetectionio/store/file_saving_datastore.py#L143-L179","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Read the appended ': {e}' — it contains errno/strerror pinpointing the cause","Fix ownership/permissions: chown -R <app-user> <datastore-path> && chmod u+w","Confirm the datastore directory exists and the mount is healthy (NFS/SMB not stale)","If disk hardware errors appear (EIO/medium error), move data off and replace/check the disk"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"import os\npath = datastore_path\nassert os.path.isdir(path) and os.access(path, os.W_OK), 'datastore not writable'","typeGuard":null,"tryCatchPattern":"try:\n    datastore._save_settings()\nexcept OSError as e:\n    logger.critical('settings save failed: %s (errno attached via __cause__)', e)\n    # inspect e.__cause__ for the original errno (EACCES/EIO/ENOENT)","preventionTips":["Run the app under a single fixed user; never mix root/non-root runs on the same datastore","Smoke-test writability at startup with a temp-file create/delete","Mount the datastore on reliable storage; stale NFS mounts surface here as generic I/O errors"],"tags":["io-error","permissions","datastore","errno"],"backgroundTag":"file-write-permission-denied","analyzedSha":"5d9c7c6da76340597243e8163c4f2439237fa0e8","analyzedAt":"2026-08-27T19:41:16.067Z","schemaVersion":2},"datasetVersion":"2026-08-28T00:17:15.603Z"}