diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml
index b24cdb8c..2a1950df 100644
--- a/.github/workflows/build.yml
+++ b/.github/workflows/build.yml
@@ -131,3 +131,23 @@ jobs:
             || (echo "Run 'cargo xtask docgen', commit the changes and push again" \
             && exit 1)
 
+  queries:
+    name: Tree-sitter queries
+    runs-on: ubuntu-latest
+    steps:
+      - name: Checkout sources
+        uses: actions/checkout@v3
+
+      - name: Install stable toolchain
+        uses: helix-editor/rust-toolchain@v1
+        with:
+          profile: minimal
+          override: true
+
+      - uses: Swatinem/rust-cache@v1
+
+      - name: Generate docs
+        uses: actions-rs/cargo@v1
+        with:
+          command: xtask
+          args: query-check
diff --git a/Cargo.lock b/Cargo.lock
index 122e0d83..dd3ec09f 100644
--- a/Cargo.lock
+++ b/Cargo.lock
@@ -1476,6 +1476,7 @@ name = "xtask"
 version = "0.6.0"
 dependencies = [
  "helix-core",
+ "helix-loader",
  "helix-term",
  "toml",
 ]
diff --git a/helix-core/src/syntax.rs b/helix-core/src/syntax.rs
index 06e9cbc0..6ec56bde 100644
--- a/helix-core/src/syntax.rs
+++ b/helix-core/src/syntax.rs
@@ -334,7 +334,7 @@ impl TextObjectQuery {
     }
 }
 
-fn read_query(language: &str, filename: &str) -> String {
+pub fn read_query(language: &str, filename: &str) -> String {
     static INHERITS_REGEX: Lazy<Regex> =
         Lazy::new(|| Regex::new(r";+\s*inherits\s*:?\s*([a-z_,()-]+)\s*").unwrap());
 
diff --git a/xtask/Cargo.toml b/xtask/Cargo.toml
index 717530d0..76c38c99 100644
--- a/xtask/Cargo.toml
+++ b/xtask/Cargo.toml
@@ -8,4 +8,5 @@ edition = "2021"
 [dependencies]
 helix-term = { version = "0.6", path = "../helix-term" }
 helix-core = { version = "0.6", path = "../helix-core" }
+helix-loader = { version = "0.6", path = "../helix-loader" }
 toml = "0.5"
diff --git a/xtask/src/main.rs b/xtask/src/main.rs
index f66fb4f4..c7640cd1 100644
--- a/xtask/src/main.rs
+++ b/xtask/src/main.rs
@@ -217,6 +217,41 @@ pub mod tasks {
         Ok(())
     }
 
+    pub fn query_check() -> Result<(), String> {
+        use crate::helpers::lang_config;
+        use helix_core::{syntax::read_query, tree_sitter::Query};
+        use helix_loader::grammar::get_language;
+
+        let query_files = [
+            "highlights.scm",
+            "locals.scm",
+            "injections.scm",
+            "textobjects.scm",
+            "indents.scm",
+        ];
+
+        for language in lang_config().language {
+            let language_name = language.language_id.to_ascii_lowercase();
+            let grammar_name = language.grammar.unwrap_or(language.language_id);
+            for query_file in query_files {
+                let language = get_language(&grammar_name);
+                let query_text = read_query(&language_name, query_file);
+                if !query_text.is_empty() && language.is_ok() {
+                    if let Err(reason) = Query::new(language.unwrap(), &query_text) {
+                        return Err(format!(
+                            "Failed to parse {} queries for {}: {}",
+                            query_file, language_name, reason
+                        ));
+                    }
+                }
+            }
+        }
+
+        println!("Query check succeeded");
+
+        Ok(())
+    }
+
     pub fn print_help() {
         println!(
             "
@@ -224,6 +259,7 @@ Usage: Run with `cargo xtask <task>`, eg. `cargo xtask docgen`.
 
     Tasks:
         docgen: Generate files to be included in the mdbook output.
+        query-check: Check that tree-sitter queries are valid.
 "
         );
     }
@@ -235,6 +271,7 @@ fn main() -> Result<(), DynError> {
         None => tasks::print_help(),
         Some(t) => match t.as_str() {
             "docgen" => tasks::docgen()?,
+            "query-check" => tasks::query_check()?,
             invalid => return Err(format!("Invalid task name: {}", invalid).into()),
         },
     };