{"record":{"id":"693309cd6e2fd3b9","repo":"Hmbown/CodeWhale","slug":"turn-output-allowance-does-not-match-its-schema","errorCode":null,"errorMessage":"Turn output allowance does not match its schema","messagePattern":"Turn output allowance does not match its schema","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":1054,"sourceCode":"    /// idempotency bridge between a claimed mail envelope and the existing\n    /// turn queue; ordinary external-user turns leave it unset.\n    #[serde(default, skip_serializing_if = \"Option::is_none\")]\n    pub agent_mail_message_id: Option<String>,\n}\n\nimpl TurnRecord {\n    fn validate_output_token_limit(&self) -> Result<()> {\n        if self.schema_version > MAX_SUPPORTED_RUNTIME_SCHEMA_VERSION {\n            bail!(\n                \"Turn schema v{} is newer than supported v{}\",\n                self.schema_version,\n                MAX_SUPPORTED_RUNTIME_SCHEMA_VERSION\n            );\n        }\n        if self.max_output_tokens.is_some()\n            != (self.schema_version == OUTPUT_LIMIT_RUNTIME_SCHEMA_VERSION)\n        {\n            bail!(\"Turn output allowance does not match its schema\");\n        }\n        Ok(())\n    }\n\n    pub(crate) fn effective_provider_label(&self) -> Option<&str> {\n        self.effective_provider_id\n            .as_deref()\n            .filter(|identity| !identity.trim().is_empty())\n            .or_else(|| {\n                self.effective_provider\n                    .as_deref()\n                    .filter(|provider| !provider.trim().is_empty())\n            })\n    }\n\n    fn persist_effective_route(&mut self, route: &EffectiveRouteEnvelope) {\n        let route = route.sanitized_for_persistence();\n        self.effective_provider = Some(route.provider.as_str().to_string());","sourceCodeStart":1036,"sourceCodeEnd":1072,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/runtime_threads.rs#L1036-L1072","documentation":"A turn record may carry `max_output_tokens` only when its `schema_version` equals `OUTPUT_LIMIT_RUNTIME_SCHEMA_VERSION`; the presence/absence of the field must match the schema exactly. `validate_output_token_limit` enforces this invariant both ways (field without the version, or version without the field).","triggerScenarios":"Loading or constructing a `TurnRecord` where `max_output_tokens.is_some()` disagrees with `schema_version == OUTPUT_LIMIT_RUNTIME_SCHEMA_VERSION`.","commonSituations":"Hand-edited or migrated turn files; code that sets `max_output_tokens` but forgets to bump the schema version (or vice versa) when writing records.","solutions":["Set the record's `schema_version` to `OUTPUT_LIMIT_RUNTIME_SCHEMA_VERSION` when writing `max_output_tokens`.","Set `max_output_tokens: None` for records on other schema versions.","Fix the writer code so version and field are updated together."],"exampleFix":"// before\nTurnRecord { schema_version: 2, max_output_tokens: Some(4096), .. }\n// after\nTurnRecord { schema_version: OUTPUT_LIMIT_RUNTIME_SCHEMA_VERSION, max_output_tokens: Some(4096), .. }","handlingStrategy":"validation","validationCode":"fn output_tokens_valid(r: &TurnRecord) -> bool { r.max_output_tokens.is_some() == (r.schema_version == OUTPUT_LIMIT_RUNTIME_SCHEMA_VERSION) }","typeGuard":null,"tryCatchPattern":"match store.load_turn(&turn_id) { Err(e) if e.contains(\"does not match its schema\") => { quarantine_record(&turn_id); continue_without(e) }, Err(e) => Err(e.into()), Ok(t) => Ok(t) }","preventionTips":["Update schema_version and new fields together in the writer","Add a round-trip test for TurnRecord serialization","Never hand-edit turn record files"],"tags":["schema","invariant","versioning"],"backgroundTag":"schema-validation-failed","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}