dbt-labs/dbt-core · warning
Should be able to serialize job labels
Error message
Should be able to serialize job labels
What it means
In `adbc_execute_with_options`, job labels are collected into a map and serialized with `serde_json::to_string`, unwrapping with `expect("Should be able to serialize job labels")`. Since the labels are plain String keys/values, serialization can practically never fail, but if it did (serde error), the code panics instead of handling it.
Source
Thrown at crates/dbt-adapter/src/engine/adapter_engine.rs:294
}
(Some(state), AdapterType::Bigquery) => {
let mut job_labels =
maybe_query_comment
.as_ref()
.map_or_else(IndexMap::new, |comment| {
engine
.query_comment()
.get_job_labels_from_query_comment(comment)
});
if let Some(invocation_id_label) = state
.lookup("invocation_id", &[])
.and_then(|value| value.as_str().map(|label| label.to_owned()))
{
job_labels.insert("dbt_invocation_id".to_string(), invocation_id_label);
}
let job_label_option =
serde_json::to_string(&job_labels).expect("Should be able to serialize job labels");
options.push((
QUERY_LABELS.to_owned(),
OptionValue::String(job_label_option),
));
}
_ => {}
}
type ExecuteOutput = (Arc<Schema>, Vec<RecordBatch>, Option<i64>);
let do_execute = |conn: &'_ mut dyn Connection| -> Result<
ExecuteOutput,
Cancellable<adbc_core::error::Error>,
> {
use dbt_adbc::statement::Statement as _;
let mut stmt = if engine.has_query_cache() {
let stmt = conn.new_statement()?;
engine.new_query_cache_statement(stmt).map_err(|e| {View on GitHub (pinned to 0267ce9170)
Solutions
- Replace the `expect` with graceful error propagation: map the serde result into the function's error type and skip the label option on failure.
- Keep job_labels restricted to `String` keys and values so serialization is infallible by construction.
- Add a unit test asserting the label JSON serialization path to catch regressions early.
Example fix
// before
let job_label_option =
serde_json::to_string(&job_labels).expect("Should be able to serialize job labels");
// after
let job_label_option = serde_json::to_string(&job_labels)
.map_err(|e| DbtAdapterError::internal(format!("failed to serialize job labels: {e}")))?; Defensive patterns
Strategy: try-catch
Validate before calling
// labels are HashMap<String,String>, which is always JSON-serializable; // guard against refactors introducing non-string values: let ok = job_labels.keys().all(|k| !k.is_empty());
Type guard
fn serializable_labels(labels: &HashMap<String, String>) -> bool {
serde_json::to_string(labels).is_ok()
} Try / catch
match serde_json::to_string(&job_labels) {
Ok(s) => options.push((QUERY_LABELS.to_owned(), OptionValue::String(s))),
Err(e) => log::warn!("skipping job labels: {e}"),
} Prevention
- Keep job label maps strictly String -> String
- Replace expect on serialization with logged fallbacks
- Add a regression test covering the label option path
When it happens
Trigger: Only when `serde_json::to_string(&job_labels)` returns Err — essentially impossible for a `HashMap<String, String>`, but theoretically reachable via a custom serializer issue, poisoned allocator, or future refactors putting non-string-serializable values into `job_labels`.
Common situations: Refactors that add non-JSON-serializable values (e.g. non-string keys, maps with struct values) to job labels; running in an environment where serde_json's fallible path is somehow triggered; debugging unexpected panics during ADBC query execution.
Understand the failure class
Background: "JSON serialization failed", "not JSON serializable", "Failed to serialize": why JSON marshaling errors happen and how to fix them — this error's family across 46 libraries.
Related errors
- Error message always present on ShouldBe::ButIsnt variant
- Error always present on ShouldBe::ButIsnt variant
- Schema not found for canonical FQN: {}
- `EnterGuard` values dropped out of order. Guards returned by
- {e} {:?}
AI-assisted analysis of dbt-labs/dbt-core@0267ce9170 (2026-09-07).
Data as JSON: /api/errors/40a51cd9456a4b51.
Report an issue: GitHub.