Crosstalk-Solutions/project-nomad · error · Error

Storage drive not available: cannot write to

Error message

Storage drive not available: cannot write to ${STORAGE_BASE} (${mkdirErr instanceof Error ? mkdirErr.message : String(mkdirErr)})

What it means

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.

Solutions

  1. Check the mount and permissions: ls -ld $STORAGE_BASE and df -h; remount or free the volume.
  2. Fix ownership/permissions so the app user can write: chown/chmod the directory or the parent path.
  3. Verify Docker volume mounts if containerized — ensure the host path exists and is writable by the container user.
  4. If STORAGE_BASE is configurable, confirm it isn't pointing at a stale or unplugged drive path.

Example fix

# shell
ls -ld "$STORAGE_BASE"   # check perms
sudo chown -R appuser:appgroup "$STORAGE_BASE"
# or fix the env
STORAGE_BASE=/data/fda
Defensive patterns

Strategy: validation

Validate before calling

import { access, constants } from 'node:fs/promises'
async function storageWritable(base: string): Promise<boolean> {
  try {
    await access(base, constants.W_OK)
    return true
  } catch {
    try { await mkdir(base, { recursive: true }); return true } catch { return false }
  }
}
// call before enqueuing the download job
if (!(await storageWritable(process.env.STORAGE_BASE!))) {
  // surface UI error instead of enqueuing
}

Try / catch

catch (err) {
  if (err instanceof Error && err.message.startsWith('Storage drive not available')) {
    // show 'check storage drive' to user; do not retry automatically
  }
  throw err
}

Prevention

When it happens

Trigger: 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.

Common situations: 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.

Related errors


AI-assisted analysis of Crosstalk-Solutions/project-nomad@0bd1c6f4f9 (2026-08-27). Data as JSON: /api/errors/202d2743f0f520ee. Report an issue: GitHub.

Appendix: source

Thrown at admin/app/jobs/download_drug_data_job.ts:287

        await IngestDrugDataJob.dispatch(resourceMeta)
        logger.info('[DownloadDrugDataJob] Auto-chained ingest phase')
      }
    }

    return { partIndex, totalParts }
  }

  // ─── Private helpers ───────────────────────────────────────────────────────

  private async verifyStorageAvailable(job: Job): Promise<void> {
    try {
      await access(STORAGE_BASE, constants.W_OK)
    } catch {
      try {
        await mkdir(STORAGE_BASE, { recursive: true })
      } catch (mkdirErr) {
        await job.updateData({ ...job.data, phase: 'failed' })
        throw new Error(
          `Storage drive not available: cannot write to ${STORAGE_BASE} (${
            mkdirErr instanceof Error ? mkdirErr.message : String(mkdirErr)
          })`
        )
      }
    }
  }

  private async fetchManifest(): Promise<DrugLabelManifest> {
    return DownloadDrugDataJob.fetchManifest()
  }

  /**
   * Fetch + parse the openFDA download manifest. The SINGLE source of truth for
   * the openFDA manifest call (Maxim 4): the download job uses it on pass 0, and
   * the freshness check (DrugReferenceService.checkForUpdate, driven by
   * attemptAutoUpdate) reuses it so there is exactly one place that knows the URL and the
   * offline-error translation.

View on GitHub (pinned to 0bd1c6f4f9)