Crosstalk-Solutions/project-nomad · error · Error

Storage drive not available: cannot write to ${STORAGE_BASE}

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.

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)

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.