neondatabase/neon · error · anyhow::Error

Failed to read startup script: {e}

Error message

Failed to read startup script: {e}

What it means

On each start, storcon reads base_data_dir/storage_controller_db.startup.sql and executes it in one transaction against its database, then deletes the file. A missing file is fine (the code falls back to 'BEGIN; COMMIT;' to keep the path exercised); any other read error — permission denied, path is a directory, I/O error — aborts the start.

Source

Thrown at control_plane/src/storage_controller.rs:506

        let database_url = format!("postgresql://localhost:{postgres_port}/{DB_NAME}");

        // We support running a startup SQL script to fiddle with the database before we launch storcon.
        // This is used by the test suite.
        let startup_script_path = self
            .env
            .base_data_dir
            .join("storage_controller_db.startup.sql");
        let startup_script = match tokio::fs::read_to_string(&startup_script_path).await {
            Ok(script) => {
                tokio::fs::remove_file(startup_script_path).await?;
                script
            }
            Err(e) => {
                if e.kind() == std::io::ErrorKind::NotFound {
                    // always run some startup script so that this code path doesn't bit rot
                    "BEGIN; COMMIT;".to_string()
                } else {
                    anyhow::bail!("Failed to read startup script: {e}")
                }
            }
        };
        let (mut client, conn) = self.connect_to_database(postgres_port).await?;
        let conn = tokio::spawn(conn);
        let tx = client.build_transaction();
        let tx = tx.start().await?;
        tx.batch_execute(&startup_script).await?;
        tx.commit().await?;
        drop(client);
        conn.await??;

        let addr = format!("{host}:{listen_port}");
        let address_for_peers = Uri::builder()
            .scheme(scheme)
            .authority(addr.clone())
            .path_and_query("")
            .build()

View on GitHub (pinned to 8f60b04da4)

Solutions

  1. Fix permissions/ownership of storage_controller_db.startup.sql, or remove the file if it is not wanted
  2. Ensure the path is a regular file, then retry start
Defensive patterns

Strategy: validation

Validate before calling

let p = env.base_data_dir.join("storage_controller_db.startup.sql");
if tokio::fs::try_exists(&p).await? {
    let md = tokio::fs::metadata(&p).await?;
    anyhow::ensure!(md.is_file(), "startup.sql path is not a regular file");
    let _ = tokio::fs::File::open(&p).await?; // probe readability before start
}

Prevention

When it happens

Trigger: The startup.sql path exists but is unreadable (wrong owner/mode), was accidentally created as a directory, or the filesystem returns an I/O error during read.

Common situations: Test harnesses dropping a startup script with restrictive permissions, root-created files later read by an unprivileged user, half-written files after a crash.

Related errors


AI-assisted analysis of neondatabase/neon@8f60b04da4 (2026-08-16). Data as JSON: /api/errors/7a9df0760d85dd49. Report an issue: GitHub.