{"record":{"id":"202d2743f0f520ee","repo":"Crosstalk-Solutions/project-nomad","slug":"storage-drive-not-available-cannot-write-to-sto","errorCode":null,"errorMessage":"Storage drive not available: cannot write to ${STORAGE_BASE} (${mkdirErr instanceof Error ? mkdirErr.message : String(mkdirErr)})","messagePattern":"Storage drive not available: cannot write to (.+?) \\((.+?)\\)","errorType":"exception","errorClass":"Error","httpStatus":null,"severity":"error","filePath":"admin/app/jobs/download_drug_data_job.ts","lineNumber":287,"sourceCode":"        await IngestDrugDataJob.dispatch(resourceMeta)\n        logger.info('[DownloadDrugDataJob] Auto-chained ingest phase')\n      }\n    }\n\n    return { partIndex, totalParts }\n  }\n\n  // ─── Private helpers ───────────────────────────────────────────────────────\n\n  private async verifyStorageAvailable(job: Job): Promise<void> {\n    try {\n      await access(STORAGE_BASE, constants.W_OK)\n    } catch {\n      try {\n        await mkdir(STORAGE_BASE, { recursive: true })\n      } catch (mkdirErr) {\n        await job.updateData({ ...job.data, phase: 'failed' })\n        throw new Error(\n          `Storage drive not available: cannot write to ${STORAGE_BASE} (${\n            mkdirErr instanceof Error ? mkdirErr.message : String(mkdirErr)\n          })`\n        )\n      }\n    }\n  }\n\n  private async fetchManifest(): Promise<DrugLabelManifest> {\n    return DownloadDrugDataJob.fetchManifest()\n  }\n\n  /**\n   * Fetch + parse the openFDA download manifest. The SINGLE source of truth for\n   * the openFDA manifest call (Maxim 4): the download job uses it on pass 0, and\n   * the freshness check (DrugReferenceService.checkForUpdate, driven by\n   * attemptAutoUpdate) reuses it so there is exactly one place that knows the URL and the\n   * offline-error translation.","sourceCodeStart":269,"sourceCodeEnd":305,"githubUrl":"https://github.com/Crosstalk-Solutions/project-nomad/blob/0bd1c6f4f9888d577fe232de06ac144bb8337131/admin/app/jobs/download_drug_data_job.ts#L269-L305","documentation":"Thrown by verifyStorageAvailable in the FDA drug data download job when the storage directory is neither writable nor creatable. It first tries access(STORAGE_BASE, W_OK); on failure it attempts mkdir recursive, and if that also fails it marks the job phase 'failed' and throws this error with the underlying mkdir failure message.","triggerScenarios":"STORAGE_BASE points to a read-only mount, a path whose parent is not writable, a full disk, or a missing drive (e.g. unplugged USB/external storage). Any run of the DownloadDrugDataJob whose handle() calls verifyStorageAvailable() under these conditions.","commonSituations":"Running the app on a device where the storage volume is unmounted, Docker container with a missing/bind-mounted volume, permission mismatch between app user and directory owner, SELinux/AppArmor denials, disk full.","solutions":["Check the mount and permissions: ls -ld $STORAGE_BASE and df -h; remount or free the volume.","Fix ownership/permissions so the app user can write: chown/chmod the directory or the parent path.","Verify Docker volume mounts if containerized — ensure the host path exists and is writable by the container user.","If STORAGE_BASE is configurable, confirm it isn't pointing at a stale or unplugged drive path."],"exampleFix":"# shell\nls -ld \"$STORAGE_BASE\"   # check perms\nsudo chown -R appuser:appgroup \"$STORAGE_BASE\"\n# or fix the env\nSTORAGE_BASE=/data/fda","handlingStrategy":"validation","validationCode":"import { access, constants } from 'node:fs/promises'\nasync function storageWritable(base: string): Promise<boolean> {\n  try {\n    await access(base, constants.W_OK)\n    return true\n  } catch {\n    try { await mkdir(base, { recursive: true }); return true } catch { return false }\n  }\n}\n// call before enqueuing the download job\nif (!(await storageWritable(process.env.STORAGE_BASE!))) {\n  // surface UI error instead of enqueuing\n}","typeGuard":null,"tryCatchPattern":"catch (err) {\n  if (err instanceof Error && err.message.startsWith('Storage drive not available')) {\n    // show 'check storage drive' to user; do not retry automatically\n  }\n  throw err\n}","preventionTips":["Health-check STORAGE_BASE writability at app startup, not just in the job.","Monitor disk space and mount status for the storage volume.","Ensure the service user owns STORAGE_BASE in deployment scripts."],"tags":["filesystem","permissions","storage","download-job"],"backgroundTag":"storage-permission-denied","analyzedSha":"0bd1c6f4f9888d577fe232de06ac144bb8337131","analyzedAt":"2026-08-27T05:34:15.424Z","schemaVersion":2},"datasetVersion":"2026-08-27T08:17:20.692Z"}