facebook/relay · error

LocalPersister: Unable to read the {} file: {}

Error message

LocalPersister: Unable to read the {} file: {}

What it means

LocalPersister::new reads the persisted-operations text file on startup. A NotFound error is treated as a first run (empty map), but any other read/parse-adjacent IO error (permissions, is-a-directory, IO failure) panics because the compiler cannot safely manage persisted operations without reading the existing state.

Source

Thrown at compiler/crates/relay-compiler/src/operation_persister/local_persister.rs:47

/// This struct implements the `OperationPersister` trait, which defines the interface for persisting GraphQL operations.
pub struct LocalPersister {
    /// The configuration for the local persister.
    config: LocalPersistConfig,
    /// A map of query IDs to query texts.
    query_map: DashMap<String, String>,
}

impl LocalPersister {
    pub fn new(config: LocalPersistConfig) -> Self {
        let query_map: DashMap<String, String> = match std::fs::read_to_string(&config.file) {
            Ok(content) => serde_json::from_str(&content).unwrap_or_default(),
            Err(e) if e.kind() == ErrorKind::NotFound => {
                // First run: the file doesn't exist yet. Start with an empty
                // map; `finalize` creates the file.
                Default::default()
            }
            Err(e) => {
                panic!(
                    "LocalPersister: Unable to read the {} file: {}",
                    config.file.display(),
                    e,
                )
            }
        };

        Self { config, query_map }
    }

    fn hash_operation(&self, operation_text: String) -> String {
        match self.config.algorithm {
            LocalPersistAlgorithm::MD5 => {
                let mut md5 = Md5::new();
                md5.update(operation_text);
                hex::encode(md5.finalize())
            }
            LocalPersistAlgorithm::SHA1 => {

View on GitHub (pinned to 668b1b85e0)

Solutions

  1. Fix permissions on the persisted operations file/directory (chmod/chown, especially for root-created Docker artifacts).
  2. Ensure the configured path is a file, not a directory.
  3. Delete the corrupt/unreadable file to re-initialize state (it is recreated on finalize).
  4. Check the filesystem is writable/readable for the user running the compiler.

Example fix

// before
persistConfig: { file: __dirname + '/persisted' } // is a directory
// after
persistConfig: { file: __dirname + '/persisted/queries.json' }
Defensive patterns

Strategy: validation

Validate before calling

const st = fs.statSync(persistFile);
if (!st.isFile()) throw new Error('persist path must be a file');
fs.accessSync(persistFile, fs.constants.R_OK | fs.constants.W_OK);

Type guard

function isReadableFile(p) { try { return fs.statSync(p).isFile() && fs.accessSync(p, fs.constants.R_OK) === undefined; } catch { return false; } }

Try / catch

try { startRelayPersist(config); } catch (e) { if (/LocalPersister: Unable to read/.test(String(e))) { fs.rmSync(persistFile, { force: true }); startRelayPersist(config); } else { throw e; } }

Prevention

When it happens

Trigger: Instantiating LocalPersister with a config.file path that exists but cannot be read: permission denied, path is a directory, or an IO error other than NotFound occurs while opening it.

Common situations: artifactDirectory owned by another user (e.g. created by Docker as root); the persisted-queries path accidentally set to a directory; read-only filesystem in CI.

Related errors


AI-assisted analysis of facebook/relay@668b1b85e0 (2026-09-02). Data as JSON: /api/errors/359a6361e8a591ae. Report an issue: GitHub.