diff --git a/src/sql.rs b/src/sql.rs
index 7e97897205c1eecc2790f3e93414e9bccd0bf4fe..d224de318c5216e0c5e753a2725b444c4faa813e 100644
--- a/src/sql.rs
+++ b/src/sql.rs
@@ -282,7 +282,30 @@ pub fn with_tracer(ctx: Context, tracer_kind: TracerKind) -> Context {
 /// Dispatches a query to the cluster.
 #[proc(packed_args)]
 pub fn dispatch_query(encoded_params: EncodedPatternWithParams) -> traft::Result<Tuple> {
-    dispatch_sql_query(encoded_params)
+    let res = dispatch_sql_query(encoded_params);
+    res.map_err(|e| {
+        match e {
+            Error::Sbroad(SbroadError::ParsingError(entity, message)) if message.contains('\n') => {
+                // Tweak the error message so that tarantool's yaml handler
+                // prints it in human-readable form
+                //
+                // `+ 1` here for one extra '\n' at the end
+                let mut buffer = String::with_capacity(message.len() + 1);
+                for line in message.lines() {
+                    // There must not be any spaces at the end of lines,
+                    // otherwise the string will be formatted incorrectly
+                    buffer.push_str(line.trim_end());
+                    buffer.push('\n');
+                }
+                // There must be at least one empty line so that tarantool
+                // formats the string correctly (it's a special hack they use
+                // for the help feature in the lua console).
+                buffer.push('\n');
+                SbroadError::ParsingError(entity, buffer.into()).into()
+            }
+            e => e,
+        }
+    })
 }
 
 pub fn dispatch_sql_query(encoded_params: EncodedPatternWithParams) -> traft::Result<Tuple> {
diff --git a/src/sql/init.lua b/src/sql/init.lua
index 7700f8b93f290867de1222661673a5642c5a3d6d..963bddadc1be9bcd5383f1427b52593d6beb4ef6 100644
--- a/src/sql/init.lua
+++ b/src/sql/init.lua
@@ -43,7 +43,7 @@ local function sql(...)
     )
 
     if ok == false then
-        return nil, res
+        return nil, tostring(res)
     end
 
     return helper.format_result(res[1])
diff --git a/test/int/test_cli_ux.py b/test/int/test_cli_ux.py
index 3989aa817931d921420e2f415e2f531baeac646f..5612830f07bf5a22695a79a9902870e25701dc41 100644
--- a/test/int/test_cli_ux.py
+++ b/test/int/test_cli_ux.py
@@ -234,3 +234,30 @@ def test_sql_explain_ok(cluster: Cluster):
     cli.expect_exact("execution options:")
     cli.expect_exact("sql_vdbe_max_steps = 45000")
     cli.expect_exact("vtable_max_rows = 5000")
+
+
+def test_lua_console_sql_error_messages(cluster: Cluster):
+    i1 = cluster.add_instance(wait_online=True)
+
+    result = i1.eval(
+        """
+        console = require 'console'
+        return console.eval ' pico.sql [[ create table foo ]] '
+        """
+    )
+
+    assert (
+        result
+        == """---
+- null
+- |+
+  sbroad: rule parsing error:  --> 1:9
+    |
+  1 |  create table foo
+    |         ^---
+    |
+    = expected Unique
+
+...
+"""
+    )
diff --git a/test/int/test_sql.py b/test/int/test_sql.py
index 451d160a779d0e58af2060ab8421398a9676493d..07cd864d03279652e8f8c5dab17109c4d513ada6 100644
--- a/test/int/test_sql.py
+++ b/test/int/test_sql.py
@@ -3466,7 +3466,7 @@ def test_rename_user(cluster: Cluster):
     # Existed name
     with pytest.raises(
         ReturnError,
-        match=f'User with name "{biba}" exists. Unable to rename user "{boba}".',
+        match=f'User with name "{biba}" exists. Unable to rename user "{boba}"',
     ):
         data = i1.sudo_sql(
             f"""
@@ -3479,7 +3479,7 @@ def test_rename_user(cluster: Cluster):
     with pytest.raises(
         ReturnError,
         match="""\
-box error: AccessDenied: Alter access to user 'boba' is denied for user 'biba'.\
+box error: AccessDenied: Alter access to user 'boba' is denied for user 'biba'\
 """,
     ):
         data = i1.sql(