linera-io/linera-protocol · error

Unable to write server config file

Error message

Unable to write server config file

What it means

Panic when `Persist::persist(&mut server)` fails while writing a validator server config created by `server generate`. `File::persist` serializes the value to pretty JSON into a staging file `<path>.json.new` (mode 0600), flushes it, then atomically renames it over `<path>`; the staging file is removed on serialization or write errors. Failure means the staging file could not be opened/written, serialization failed, or the final rename failed.

Source

Thrown at linera-service/src/server.rs:904

        ServerCommand::Generate {
            validators,
            committee,
            testing_prng_seed,
        } => {
            let mut config_validators = Vec::new();
            let mut rng = Box::<dyn CryptoRng>::from(testing_prng_seed);
            for options_path in validators {
                let options_string = fs_err::tokio::read_to_string(options_path)
                    .await
                    .expect("Unable to read validator options file");
                let options: ValidatorOptions = toml::from_str(&options_string)
                    .unwrap_or_else(|_| panic!("Invalid options file format: \n {options_string}"));
                let path = options.server_config_path.clone();
                let mut server = make_server_config(&path, &mut rng, options)
                    .expect("Unable to open server config file");
                Persist::persist(&mut server)
                    .await
                    .expect("Unable to write server config file");
                info!("Wrote server config {}", path.to_str().unwrap());
                println!(
                    "{},{}",
                    server.validator.public_key, server.validator.account_key
                );
                config_validators.push(Persist::into_value(server).validator);
            }
            if let Some(committee) = committee {
                let mut config = persistent::File::new(
                    &committee,
                    CommitteeConfig {
                        validators: config_validators,
                    },
                )
                .expect("Unable to open committee configuration");
                Persist::persist(&mut config)
                    .await
                    .expect("Unable to write committee description");

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Check writability and space: `df -h <dir>` and `touch <dir>/.probe` (then remove the probe).
  2. Remount the volume read-write, or move the config path to a writable directory.
  3. Free disk space or enlarge the volume if the failure is ENOSPC.
  4. Remove stale root-owned `<path>.json.new` leftovers before rerunning.
  5. Replace `.expect` with `.with_context(...)?` in embedded code to surface the underlying io::Error.

Example fix

// before
Persist::persist(&mut server)
    .await
    .expect("Unable to write server config file");
// after
Persist::persist(&mut server)
    .await
    .with_context(|| format!("unable to write server config {}", path.display()))?;
Defensive patterns

Strategy: validation

Validate before calling

let dir = path.parent().unwrap_or(Path::new("."));
anyhow::ensure!(!fs_err::metadata(dir)?.permissions().readonly(), "config dir is read-only: {}", dir.display());
let staging = {
    let mut p = path.clone();
    p.set_extension("json.new");
    p
};
if staging.exists() {
    fs_err::remove_file(&staging)?;
}

Try / catch

if let Err(err) = Persist::persist(&mut server).await {
    eprintln!("error: unable to write server config {}: {err}", path.display());
    std::process::exit(1);
}

Prevention

When it happens

Trigger: Config directory not writable or on a read-only mount so `open("<path>.json.new")` fails with EACCES/EROFS; disk full (ENOSPC) while flushing the staging file; leftover `.json.new` owned by another user; rename failing on exotic filesystems (NFS/FUSE); JSON serialization of ValidatorServerConfig failing after an internal schema change.

Common situations: Kubernetes pod with the config path on a read-only ConfigMap mount; small disks on CI or test VMs; Docker image run once as root then as non-root, leaving a root-owned staging file; network volumes where same-directory rename semantics differ.

Related errors


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/7ed948d4e27b60eb. Report an issue: GitHub.