Hmbown/CodeWhale · error
Runtime Chat binding state uses an unsupported schema
Error message
Runtime Chat binding state uses an unsupported schema
What it means
RelayState::validate rejects persisted Runtime Chat binding state whose schema_version does not match the compile-time STATE_SCHEMA_VERSION constant. The on-disk relay state was written by a different (older or newer) build than the one reading it, so the library refuses to interpret it rather than guessing at field meanings. This is a deliberate hard stop to prevent misreading a format that may have changed shape or invariants.
Solutions
- Delete or move aside the stale Runtime Chat state file so a fresh state with the current schema is created
- Rebuild/re-run with the same version of the application that wrote the state file
- Update STATE_SCHEMA_VERSION in the constructed state to match the current constant (only for tests/tooling, never by editing persisted files in place)
- Add a migration path in code if old schemas must be upgraded rather than discarded
Example fix
// before
let state = RelayState { schema_version: 1, .. };
// after
let state = RelayState { schema_version: STATE_SCHEMA_VERSION, .. }; Defensive patterns
Strategy: validation
Validate before calling
fn ensure_schema_current(state: &RelayState) -> Result<()> {
if state.schema_version != STATE_SCHEMA_VERSION {
anyhow::bail!(
"state schema {} != supported {}",
state.schema_version,
STATE_SCHEMA_VERSION
);
}
Ok(())
} Type guard
fn has_current_schema(state: &RelayState) -> bool {
state.schema_version == STATE_SCHEMA_VERSION
} Try / catch
match relay.load_state() {
Ok(state) => state,
Err(e) if e.to_string().contains("unsupported schema") => {
// discard stale state and start fresh
relay.reset_state()?
}
Err(e) => return Err(e),
} Prevention
- Persist STATE_SCHEMA_VERSION in every serialized state and check it right after deserialization
- Ship a migration step on upgrade instead of silently reading old state
- Never hand-construct RelayState without referencing STATE_SCHEMA_VERSION
When it happens
Trigger: Calling persist_state or test persisted_binding_state_rejects_duplicate_or_nonopaque_authority with a RelayState whose schema_version field differs from STATE_SCHEMA_VERSION — typically state loaded from disk that was written by an older binary after a schema bump, or hand-constructed state with a wrong/omitted version.
Common situations: Upgrading or downgrading the application between versions that bumped STATE_SCHEMA_VERSION; restoring relay state files from a backup made by a different build; tests or tooling that construct RelayState literals without updating the version constant.
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
- invalid persisted user image content kind
- Invalid version 1 pet bucket.
- Codewhale terminal receipt contained a non-scalar field
- Duplicate .
- Duplicate policy identity.
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/32a58d532087bab7.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/runtime_chat_relay.rs:205
/// native start was proven to have rejected before accepting provider work.
/// This distinguishes a retryable reservation from an already-projected
/// terminal turn, whose exact replay must remain settled.
#[serde(default)]
start_rejected: bool,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
enum TurnReservationDisposition {
New,
Reopened,
ExistingUnsettled,
ExistingTerminal,
}
impl RelayState {
fn validate(&self) -> Result<()> {
if self.schema_version != STATE_SCHEMA_VERSION {
bail!("Runtime Chat binding state uses an unsupported schema");
}
if let Some(owner) = self.owner_scope_fingerprint.as_deref() {
validate_fingerprint(owner)?;
} else if !self.bindings.is_empty() {
bail!("Runtime Chat binding state has no account owner");
}
let mut binding_ids = HashSet::new();
let mut virtual_threads = HashSet::new();
let mut native_threads = HashSet::new();
for binding in &self.bindings {
validate_relay_id(&binding.run_id, "run id")?;
validate_relay_id(&binding.runtime_binding_id, "binding id")?;
validate_virtual_thread_id(&binding.virtual_thread_id)?;
validate_native_record_id(&binding.native_thread_id, "native thread id")?;
validate_route_id(&binding.model_provider, "provider id")?;
validate_route_id(&binding.model_provider_id, "model-provider id")?;
validate_model_id(&binding.model)?;
validate_fingerprint(&binding.first_operation_fingerprint)?;View on GitHub (pinned to 73e0f67d83)