Hmbown/CodeWhale · error
fleet task '{}' metadata.coordination_contracts must contain
Error message
fleet task '{}' metadata.coordination_contracts must contain only strings What it means
One element of the `metadata.coordination_contracts` array is not a string — `value.as_str()` returned None for a number, boolean, null, or nested array/table. The field accepts only an array of strings; anything else is rejected before length or content checks run.
Source
Thrown at crates/tui/src/fleet/worker_runtime.rs:397
let Some(value) = task_spec.metadata.get("coordination_contracts") else {
return Ok(Vec::new());
};
let Some(values) = value.as_array() else {
bail!(
"fleet task '{}' metadata.coordination_contracts must be an array of strings",
task_spec.id
);
};
if values.len() > 16 {
bail!(
"fleet task '{}' metadata.coordination_contracts accepts at most 16 entries",
task_spec.id
);
}
let mut contracts = Vec::new();
for value in values {
let Some(value) = value.as_str() else {
bail!(
"fleet task '{}' metadata.coordination_contracts must contain only strings",
task_spec.id
);
};
let value = value.trim();
if value.is_empty()
|| value.chars().count() > 128
|| value.chars().any(|ch| matches!(ch, '\0' | '\r' | '\n'))
{
bail!(
"fleet task '{}' coordination contracts must be one non-empty line of at most 128 characters",
task_spec.id
);
}
if !contracts.iter().any(|contract| contract == value) {
contracts.push(value.to_string());
}
}View on GitHub (pinned to 0c42157ee5)
Solutions
- Quote every entry in TOML and ensure JSON emits only strings.
- Omit null/empty optional entries at generation time instead of serializing them.
- After fixing, re-run spec validation to catch any follow-on content errors (length, newlines).
Example fix
# before [metadata] coordination_contracts = ["ledger", 42, true] # after [metadata] coordination_contracts = ["ledger", "counter", "flag"]
Defensive patterns
Strategy: type-guard
Validate before calling
fn contracts_all_strings(values: &[serde_json::Value]) -> bool {
values.iter().all(|v| v.is_string())
} Type guard
fn as_string_array(value: &serde_json::Value) -> Option<&Vec<String>> {
value.as_array()?.iter().all(|v| v.is_string()).then(|| {
value.as_array().unwrap().iter().map(|v| v.as_str().unwrap().to_string()).collect::<Vec<_>>()
})
} Prevention
- Type the field as Vec<String> in any producer struct so serde cannot emit non-strings.
- Skip null/None entries at generation instead of serializing them.
- Quote every entry in TOML lists, including numerals.
When it happens
Trigger: TOML like `coordination_contracts = ["ledger", 42]` or `["ledger", true]`; JSON with `["ledger", null]`; a generator mixing typed values into the list.
Common situations: Unquoted numerals in TOML lists; optional entries serialized as null instead of omitted; heterogeneous data pasted from application config.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- fleet task '{}' metadata.coordination_contracts must be an a
- fleet task '{}' metadata.coordination_contracts accepts at m
- fleet task '{}' coordination contracts must be one non-empty
- running tmux lane `{}` has incomplete pinned session metadat
- running tmux lane `{}` lacks pinned socket/session metadata;
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/39ea6a8203a7ac1b.
Report an issue: GitHub.