{"record":{"id":"885ba415404a50eb","repo":"github/spec-kit","slug":"could-not-safely-write-download-file-exc","errorCode":null,"errorMessage":"Could not safely write download file: {exc}","messagePattern":"Could not safely write download file: (.+?)","errorType":"exception","errorClass":"ExtensionError","httpStatus":null,"severity":"error","filePath":"src/specify_cli/extensions/_commands.py","lineNumber":181,"sourceCode":"    download_file = None\n    try:\n        try:\n            download_fd = _safe_open_download_zip(\n                project_root, download_dir, archive_filename\n            )\n        except OSError as exc:\n            raise ExtensionError(\n                f\"Could not safely create download file: {exc}\"\n            ) from exc\n\n        try:\n            download_file = os.fdopen(download_fd, \"w+b\")\n            download_fd = -1\n            download_file.write(archive_data)\n            download_file.flush()\n            download_file.seek(0)\n        except OSError as exc:\n            raise ExtensionError(\n                f\"Could not safely write download file: {exc}\"\n            ) from exc\n\n        format_source = (\n            final_url\n            if archive_format_from_name(final_url) is not None\n            else url\n        )\n        try:\n            detect_archive_format(\n                archive_path,\n                archive_file=download_file,\n                source_name=format_source,\n                content_type=content_type,\n                error_type=ExtensionError,\n            )\n        except ExtensionError as exc:\n            raise ExtensionError(","sourceCodeStart":163,"sourceCodeEnd":199,"githubUrl":"https://github.com/github/spec-kit/blob/bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c/src/specify_cli/extensions/_commands.py#L163-L199","documentation":"The transient download file was opened successfully, but writing the archive bytes, flushing, or rewinding it raised OSError — wrapped as 'Could not safely write download file'. The fd is wrapped with os.fdopen(download_fd, 'w+b') and the 50 MiB payload is written in one go before detection runs.","triggerScenarios":"`specify extension install <url>` when the filesystem fills up mid-write (ENOSPC), a quota is exceeded, the file is on a tmpfs that overflows, or the container/OS kills the handle (EIO on failing storage).","commonSituations":"Small CI container with a tiny /tmp or workspace disk; tmpfs-backed project dir in RAM-constrained environments; flaky network storage. The 50 MiB cap makes single-archive sizes predictable, so this is almost always capacity, not code.","solutions":["Check available space where the project lives: `df -h .` and free space if the download (up to 50 MiB) doesn't fit","Raise container disk size or move the project off tmpfs if applicable","Retry after cleaning stale files under the extension download cache area in .specify","If storage is genuinely unreliable, download elsewhere (`curl -fL`) and install from the local archive path"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"import shutil\n\ndef disk_fits_download(path: str, cap_bytes: int = 50 * 1024 * 1024) -> bool:\n    usage = shutil.disk_usage(path)\n    return usage.free > cap_bytes * 2  # archive + extraction headroom","typeGuard":null,"tryCatchPattern":"from specify_cli.extensions import ExtensionError\n\ntry:\n    install_from_url_cmd(project_root, url, speckit_version)\nexcept ExtensionError as e:\n    if 'Could not safely write download file' in str(e):\n        # errno ENOSPC in __cause__: free space or install from local archive","preventionTips":["Check df before installs in size-constrained CI containers","Avoid tmpfs-backed project dirs for extension installs"],"tags":["extensions","filesystem","disk-full","download"],"backgroundTag":null,"analyzedSha":"bf88c9f9a82fa370c7a7257aa2b3cf10b457b65c","analyzedAt":"2026-08-14T19:43:37.150Z","schemaVersion":2},"datasetVersion":"2026-08-15T22:17:37.221Z"}