{"record":{"id":"7a9df0760d85dd49","repo":"neondatabase/neon","slug":"failed-to-read-startup-script-e","errorCode":null,"errorMessage":"Failed to read startup script: {e}","messagePattern":"Failed to read startup script: (.+?)","errorType":"exception","errorClass":"anyhow::Error","httpStatus":null,"severity":"error","filePath":"control_plane/src/storage_controller.rs","lineNumber":506,"sourceCode":"        let database_url = format!(\"postgresql://localhost:{postgres_port}/{DB_NAME}\");\n\n        // We support running a startup SQL script to fiddle with the database before we launch storcon.\n        // This is used by the test suite.\n        let startup_script_path = self\n            .env\n            .base_data_dir\n            .join(\"storage_controller_db.startup.sql\");\n        let startup_script = match tokio::fs::read_to_string(&startup_script_path).await {\n            Ok(script) => {\n                tokio::fs::remove_file(startup_script_path).await?;\n                script\n            }\n            Err(e) => {\n                if e.kind() == std::io::ErrorKind::NotFound {\n                    // always run some startup script so that this code path doesn't bit rot\n                    \"BEGIN; COMMIT;\".to_string()\n                } else {\n                    anyhow::bail!(\"Failed to read startup script: {e}\")\n                }\n            }\n        };\n        let (mut client, conn) = self.connect_to_database(postgres_port).await?;\n        let conn = tokio::spawn(conn);\n        let tx = client.build_transaction();\n        let tx = tx.start().await?;\n        tx.batch_execute(&startup_script).await?;\n        tx.commit().await?;\n        drop(client);\n        conn.await??;\n\n        let addr = format!(\"{host}:{listen_port}\");\n        let address_for_peers = Uri::builder()\n            .scheme(scheme)\n            .authority(addr.clone())\n            .path_and_query(\"\")\n            .build()","sourceCodeStart":488,"sourceCodeEnd":524,"githubUrl":"https://github.com/neondatabase/neon/blob/8f60b04da47ffefe0e52bda2440134b42874eb75/control_plane/src/storage_controller.rs#L488-L524","documentation":"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.","triggerScenarios":"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.","commonSituations":"Test harnesses dropping a startup script with restrictive permissions, root-created files later read by an unprivileged user, half-written files after a crash.","solutions":["Fix permissions/ownership of storage_controller_db.startup.sql, or remove the file if it is not wanted","Ensure the path is a regular file, then retry start"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"let p = env.base_data_dir.join(\"storage_controller_db.startup.sql\");\nif tokio::fs::try_exists(&p).await? {\n    let md = tokio::fs::metadata(&p).await?;\n    anyhow::ensure!(md.is_file(), \"startup.sql path is not a regular file\");\n    let _ = tokio::fs::File::open(&p).await?; // probe readability before start\n}","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Write startup.sql atomically (write to a temp file, then rename)","Fix ownership when scripts run as root but storcon runs unprivileged","Delete the file after use so stale scripts do not accumulate"],"tags":["rust","storage-controller","filesystem","sql","startup"],"backgroundTag":"file-read-io-error","analyzedSha":"8f60b04da47ffefe0e52bda2440134b42874eb75","analyzedAt":"2026-08-16T23:39:28.135Z","schemaVersion":2},"datasetVersion":"2026-08-17T04:17:16.089Z"}