diff --git a/mavlink-bindgen/src/cli.rs b/mavlink-bindgen/src/cli.rs
index 02eb59038fa53cc28f2281c899377c1fd19d17cd..30fee8a843b2e3af12e0fb57b571a8663ce332a5 100644
--- a/mavlink-bindgen/src/cli.rs
+++ b/mavlink-bindgen/src/cli.rs
@@ -1,7 +1,7 @@
 use std::path::PathBuf;
 
 use clap::Parser;
-use mavlink_bindgen::{emit_cargo_build_messages, format_generated_code, generate};
+use mavlink_bindgen::{emit_cargo_build_messages, format_generated_code, generate, BindGenError};
 
 #[derive(Parser)]
 struct Cli {
@@ -13,10 +13,9 @@ struct Cli {
     emit_cargo_build_messages: bool,
 }
 
-pub fn main() {
+pub fn main() -> Result<(), BindGenError> {
     let args = Cli::parse();
-    let result = generate(args.definitions_dir, args.destination_dir)
-        .expect("failed to generate MAVLink Rust bindings");
+    let result = generate(args.definitions_dir, args.destination_dir)?;
 
     if args.format_generated_code {
         format_generated_code(&result);
@@ -25,4 +24,6 @@ pub fn main() {
     if args.emit_cargo_build_messages {
         emit_cargo_build_messages(&result);
     }
+
+    Ok(())
 }
diff --git a/mavlink-bindgen/src/error.rs b/mavlink-bindgen/src/error.rs
index fec283634b91cc4273592cc9307c2e392ec17079..957ec5506d20bf591089ba7c78b4159c4cfac968 100644
--- a/mavlink-bindgen/src/error.rs
+++ b/mavlink-bindgen/src/error.rs
@@ -3,19 +3,25 @@ use thiserror::Error;
 #[derive(Error, Debug)]
 pub enum BindGenError {
     /// Represents a failure to read the MAVLink definitions directory.
-    #[error("Could not read definitions directory {path}")]
+    #[error("Could not read definitions directory {path}: {source}")]
     CouldNotReadDefinitionsDirectory {
         source: std::io::Error,
         path: std::path::PathBuf,
     },
+    /// Represents a failure to read the MAVLink definitions directory.
+    #[error("Could not read definition file {path}: {source}")]
+    CouldNotReadDefinitionFile {
+        source: std::io::Error,
+        path: std::path::PathBuf,
+    },
     /// Represents a failure to read a directory entry in the MAVLink definitions directory.
-    #[error("Could not read MAVLink definitions directory entry {path}")]
+    #[error("Could not read MAVLink definitions directory entry {path}: {source}")]
     CouldNotReadDirectoryEntryInDefinitionsDirectory {
         source: std::io::Error,
         path: std::path::PathBuf,
     },
     /// Represents a failure to create a Rust file for the generated MAVLink bindings.
-    #[error("Could not create Rust bindings file {dest_path}")]
+    #[error("Could not create Rust bindings file {dest_path}: {source}")]
     CouldNotCreateRustBindingsFile {
         source: std::io::Error,
         dest_path: std::path::PathBuf,
diff --git a/mavlink-bindgen/src/lib.rs b/mavlink-bindgen/src/lib.rs
index f5e63da800c261cdc8bf2faa3743a95b074961e6..3448fe88da625a7a4cda8a6020ec140bf4524ebd 100644
--- a/mavlink-bindgen/src/lib.rs
+++ b/mavlink-bindgen/src/lib.rs
@@ -1,4 +1,4 @@
-use crate::error::BindGenError;
+pub use crate::error::BindGenError;
 use std::fs::{read_dir, File};
 use std::io::BufWriter;
 use std::ops::Deref;
@@ -64,7 +64,7 @@ fn _generate(
         })?);
 
         // generate code
-        parser::generate(&definitions_dir, &definition_file, &mut outf);
+        parser::generate(&definitions_dir, &definition_file, &mut outf)?;
 
         bindings.push(GeneratedBinding {
             module_name,
diff --git a/mavlink-bindgen/src/main.rs b/mavlink-bindgen/src/main.rs
index 660ba5ec7f4a74c6d8da32c1fe08ac15b6c9521e..f2c68ba787ff19bfe07328b6c4aedd498568c78c 100644
--- a/mavlink-bindgen/src/main.rs
+++ b/mavlink-bindgen/src/main.rs
@@ -1,11 +1,20 @@
 #![recursion_limit = "256"]
 
+use std::process::ExitCode;
+
 #[cfg(feature = "cli")]
 mod cli;
 
-pub fn main() {
+fn main() -> ExitCode {
     #[cfg(feature = "cli")]
-    cli::main();
+    if let Err(e) = cli::main() {
+        eprintln!("{e}");
+        return ExitCode::FAILURE;
+    }
+
     #[cfg(not(feature = "cli"))]
     panic!("Compiled without cli feature");
+
+    #[cfg(feature = "cli")]
+    ExitCode::SUCCESS
 }
diff --git a/mavlink-bindgen/src/parser.rs b/mavlink-bindgen/src/parser.rs
index 0d273e663d746d38a8ab10e21abb56c03ded05f0..1a6eaba7e3c48d076af6a1f0c17f3025348296fc 100644
--- a/mavlink-bindgen/src/parser.rs
+++ b/mavlink-bindgen/src/parser.rs
@@ -17,6 +17,8 @@ use quote::{format_ident, quote};
 #[cfg(feature = "serde")]
 use serde::{Deserialize, Serialize};
 
+use crate::error::BindGenError;
+
 #[derive(Debug, PartialEq, Clone, Default)]
 #[cfg_attr(feature = "serde", derive(Serialize, Deserialize))]
 pub struct MavProfile {
@@ -1033,7 +1035,7 @@ pub fn parse_profile(
     definitions_dir: &Path,
     definition_file: &Path,
     parsed_files: &mut HashSet<PathBuf>,
-) -> MavProfile {
+) -> Result<MavProfile, BindGenError> {
     let in_path = Path::new(&definitions_dir).join(definition_file);
     parsed_files.insert(in_path.clone()); // Keep track of which files have been parsed
 
@@ -1049,7 +1051,11 @@ pub fn parse_profile(
 
     let mut xml_filter = MavXmlFilter::default();
     let mut events: Vec<Result<Event, quick_xml::Error>> = Vec::new();
-    let mut reader = Reader::from_reader(BufReader::new(File::open(in_path).unwrap()));
+    let file = File::open(&in_path).map_err(|e| BindGenError::CouldNotReadDefinitionFile {
+        source: e,
+        path: in_path.to_path_buf(),
+    })?;
+    let mut reader = Reader::from_reader(BufReader::new(file));
     reader.trim_text(true);
     reader.trim_text_end(true);
 
@@ -1331,7 +1337,7 @@ pub fn parse_profile(
                         let include_file = Path::new(&definitions_dir).join(include.clone());
                         if !parsed_files.contains(&include_file) {
                             let included_profile =
-                                parse_profile(definitions_dir, &include, parsed_files);
+                                parse_profile(definitions_dir, &include, parsed_files)?;
                             for message in included_profile.messages.values() {
                                 profile.add_message(message);
                             }
@@ -1354,18 +1360,24 @@ pub fn parse_profile(
     }
 
     //let profile = profile.update_messages(); //TODO verify no longer needed
-    profile.update_enums()
+    Ok(profile.update_enums())
 }
 
 /// Generate protobuf represenation of mavlink message set
 /// Generate rust representation of mavlink message set with appropriate conversion methods
-pub fn generate<W: Write>(definitions_dir: &Path, definition_file: &Path, output_rust: &mut W) {
+pub fn generate<W: Write>(
+    definitions_dir: &Path,
+    definition_file: &Path,
+    output_rust: &mut W,
+) -> Result<(), BindGenError> {
     let mut parsed_files: HashSet<PathBuf> = HashSet::new();
-    let profile = parse_profile(definitions_dir, definition_file, &mut parsed_files);
+    let profile = parse_profile(definitions_dir, definition_file, &mut parsed_files)?;
 
     // rust file
     let rust_tokens = profile.emit_rust();
     writeln!(output_rust, "{rust_tokens}").unwrap();
+
+    Ok(())
 }
 
 /// CRC operates over names of the message and names of its fields
diff --git a/mavlink/build/main.rs b/mavlink/build/main.rs
index 7206d20658522d66811f139fe443f77fb89a2d6d..4d1cfd4c73a381831575be3d606d3a73a6845262 100644
--- a/mavlink/build/main.rs
+++ b/mavlink/build/main.rs
@@ -3,9 +3,9 @@
 use std::env;
 use std::fs::read_dir;
 use std::path::Path;
-use std::process::Command;
+use std::process::{Command, ExitCode};
 
-pub fn main() {
+fn main() -> ExitCode {
     let src_dir = Path::new(env!("CARGO_MANIFEST_DIR"));
 
     // Update and init submodule
@@ -17,6 +17,7 @@ pub fn main() {
         .status()
     {
         eprintln!("{error}");
+        return ExitCode::FAILURE;
     }
 
     // find & apply patches to XML definitions to avoid crashes
@@ -34,6 +35,7 @@ pub fn main() {
                 .status()
             {
                 eprintln!("{error}");
+                return ExitCode::FAILURE;
             }
         }
     }
@@ -43,11 +45,18 @@ pub fn main() {
 
     let out_dir = env::var("OUT_DIR").unwrap();
 
-    let result = mavlink_bindgen::generate(definitions_dir, out_dir)
-        .expect("Failed to generate Rust MAVLink bindings");
+    let result = match mavlink_bindgen::generate(definitions_dir, out_dir) {
+        Ok(r) => r,
+        Err(e) => {
+            eprintln!("{e}");
+            return ExitCode::FAILURE;
+        }
+    };
 
     #[cfg(feature = "format-generated-code")]
     mavlink_bindgen::format_generated_code(&result);
 
     mavlink_bindgen::emit_cargo_build_messages(&result);
+
+    ExitCode::SUCCESS
 }