{"record":{"id":"2690a4677b6096ec","repo":"Hmbown/CodeWhale","slug":"settlement-turn-turn-id-already-belongs-to-another-thread","errorCode":null,"errorMessage":"settlement turn {turn_id} already belongs to another thread","messagePattern":"settlement turn (.+?) already belongs to another thread","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/runtime_threads.rs","lineNumber":1273,"sourceCode":"///\n/// The record is terminal on creation and deliberately carries no parent\n/// `usage`, no parent route, and no thread-pointer update: the parent turn was\n/// never accepted, so only the auxiliary call's own frozen route may be\n/// charged. Because no engine `TurnComplete` will ever arrive for it, this\n/// record — unlike an accepted turn — also owns the coverage gap its bounded\n/// ledger could not represent. Every persisted field is re-derived from the\n/// batch, so replaying the same settlement rewrites the same values.\nfn settle_unaccepted_routed_usage(\n    store: &RuntimeThreadStore,\n    thread_id: &str,\n    batch: &crate::cost_status::RuntimeUsageBatch,\n) -> Result<String> {\n    let turn_id = unaccepted_routed_usage_turn_id(thread_id, batch);\n    let _turn_mutation = store.turn_mutation.lock();\n    let mut turn = if store.turn_path(&turn_id)?.exists() {\n        let existing = store.load_turn(&turn_id)?;\n        if existing.thread_id != thread_id {\n            bail!(\"settlement turn {turn_id} already belongs to another thread\");\n        }\n        existing\n    } else {\n        let now = Utc::now();\n        TurnRecord {\n            max_output_tokens: None,\n            schema_version: CURRENT_RUNTIME_SCHEMA_VERSION,\n            id: turn_id.clone(),\n            thread_id: thread_id.to_string(),\n            status: RuntimeTurnStatus::Failed,\n            input_summary: UNACCEPTED_TURN_SUMMARY.to_string(),\n            created_at: now,\n            started_at: Some(now),\n            ended_at: Some(now),\n            duration_ms: Some(0),\n            usage: None,\n            routing_settlement: true,\n            effective_route_usage: None,","sourceCodeStart":1255,"sourceCodeEnd":1291,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/runtime_threads.rs#L1255-L1291","documentation":"When applying routed-usage settlement, the store loads the settlement turn if it already exists on disk. If the existing turn's `thread_id` differs from the thread the settlement is being recorded for, the operation bails rather than re-parenting a turn to another thread — this protects turn/thread ownership invariants.","triggerScenarios":"Calling the settlement save with a `thread_id` different from the one the existing turn file was created under, i.e. the same turn id is claimed by two threads.","commonSituations":"A turn id collision or reuse across threads; a thread id changed after the turn was first written; replaying settlement batches against a re-keyed thread.","solutions":["Use the thread id that owns the existing turn when recording settlement.","Generate a fresh turn id for the new thread instead of reusing the colliding one.","Investigate why `unaccepted_routed_usage_turn_id` produced an id already owned by another thread (id derivation bug or stale batch)."],"exampleFix":"// before\nstore.save_settlement_turn(&wrong_thread_id, &batch)?;\n// after\nlet existing = store.load_turn(&turn_id)?;\nstore.save_settlement_turn(&existing.thread_id, &batch)?; // record under the owning thread","handlingStrategy":"try-catch","validationCode":"fn settlement_thread_ok(existing_thread: &str, target: &str) -> bool { existing_thread == target }","typeGuard":null,"tryCatchPattern":"match store.save_settlement_turn(thread_id, batch) { Err(e) if e.contains(\"already belongs to another thread\") => record_under_owning_thread(e), Err(e) => return Err(e.into()), Ok(id) => Ok(id) }","preventionTips":["Derive turn ids deterministically from the thread id so collisions cannot cross threads","Never reuse a turn id across threads","Verify thread id before replaying settlement batches"],"tags":["state","storage","conflict"],"backgroundTag":"invalid-state-transition","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"}