{"record":{"id":"92861a9c4c4b95e7","repo":"Hmbown/CodeWhale","slug":"conditional-terminal-append-requires-a-terminal-worker-event","errorCode":null,"errorMessage":"conditional terminal append requires a terminal worker event","messagePattern":"conditional terminal append requires a terminal worker event","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/tui/src/fleet/ledger.rs","lineNumber":916,"sourceCode":"    /// still live. This is the completion side of the same compare-and-set used\n    /// by cancellation: whichever terminal transition acquires the ledger lock\n    /// first wins, and the loser cannot overwrite the task or mint a receipt.\n    pub fn append_terminal_event_if_leased(\n        &self,\n        run_id: &FleetRunId,\n        worker_id: &str,\n        task_id: &str,\n        expected_attempts: u32,\n        timestamp: &str,\n        payload: FleetWorkerEventPayload,\n    ) -> Result<Option<FleetWorkerEvent>> {\n        if !matches!(\n            &payload,\n            FleetWorkerEventPayload::Completed { .. }\n                | FleetWorkerEventPayload::Failed { .. }\n                | FleetWorkerEventPayload::Cancelled { .. }\n        ) {\n            bail!(\"conditional terminal append requires a terminal worker event\");\n        }\n        let appended = self.with_write_lock(|| {\n            let state = self.rebuild_state_unlocked()?;\n            let key = task_key(&run_id.0, task_id);\n            let Some(task) = state.tasks.get(&key) else {\n                return Ok(None);\n            };\n            if task.status != FleetTaskLedgerStatus::Leased\n                || task.leased_to.as_deref() != Some(worker_id)\n                || task.entry.attempts != expected_attempts\n            {\n                return Ok(None);\n            }\n            let event = next_worker_event(&state, run_id, worker_id, task_id, timestamp, payload);\n            self.append_record_unlocked(&FleetLedgerRecord::EventAppended {\n                event: event.clone(),\n            })?;\n            Ok(Some(event))","sourceCodeStart":898,"sourceCodeEnd":934,"githubUrl":"https://github.com/Hmbown/CodeWhale/blob/73e0f67d83c59909b571efdfc88c4bc28c309cb1/crates/tui/src/fleet/ledger.rs#L898-L934","documentation":"FleetLedger::append_terminal_event_if_leased is the compare-and-set API for appending terminal worker events (Completed/Failed/Cancelled) while a lease is still live. The guard at crates/tui/src/fleet/ledger.rs:916 rejects any call whose payload is a non-terminal event (e.g. Progress, Heartbeat, UsageReport). It is an argument precondition, not a ledger-state problem: the method is terminal-only by contract.","triggerScenarios":"Calling FleetLedger::append_terminal_event_if_leased with a non-terminal FleetWorkerEventPayload such as Progress, UsageReport, or Heartbeat — usually because the caller picked the wrong variant of the conditional-append family (the progress-only variants are append_event_if_leased / append_event_if_lease_unchanged).","commonSituations":"Refactors that unify progress and completion paths into one helper and pass the payload straight through; writing a new scheduler/worker loop that reuses the terminal API for intermediate updates; misremembering which conditional-append method owns which payload class.","solutions":["Route non-terminal payloads through FleetLedger::append_event_if_leased or append_event_if_lease_unchanged instead.","Map the payload to a terminal variant (Completed/Failed/Cancelled) before calling append_terminal_event_if_leased.","Add a matches! assertion or match arm upstream so only terminal payloads reach this call site."],"exampleFix":"// before\nledger.append_terminal_event_if_leased(&run_id, &worker_id, &task_id, attempts, &ts, payload)?;\n// after\nif is_terminal_payload(&payload) {\n    ledger.append_terminal_event_if_leased(&run_id, &worker_id, &task_id, attempts, &ts, payload)?;\n} else {\n    ledger.append_event_if_leased(&run_id, &worker_id, &task_id, attempts, &ts, payload)?;\n}","handlingStrategy":"validation","validationCode":"fn is_terminal_payload(p: &FleetWorkerEventPayload) -> bool {\n    matches!(p, FleetWorkerEventPayload::Completed { .. } | FleetWorkerEventPayload::Failed { .. } | FleetWorkerEventPayload::Cancelled { .. })\n}\nassert!(is_terminal_payload(&payload), \"terminal append needs terminal payload\");","typeGuard":"let is_terminal_payload = |p: &FleetWorkerEventPayload| matches!(p, FleetWorkerEventPayload::Completed { .. } | FleetWorkerEventPayload::Failed { .. } | FleetWorkerEventPayload::Cancelled { .. });","tryCatchPattern":"match ledger.append_terminal_event_if_leased(&run_id, &w, &t, attempts, &ts, payload) {\n    Ok(Some(event)) => { /* terminal recorded */ }\n    Ok(None) => { /* lease moved on */ }\n    Err(e) if e.to_string().contains(\"requires a terminal worker event\") => { /* dispatch to progress API */ }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Keep a single helper that dispatches payloads to the progress vs terminal append APIs instead of calling them directly.","Name terminal call sites explicitly so code review spots progress payloads near terminal APIs.","Add a debug_assert on payload terminality at call sites."],"tags":["fleet","ledger","invalid-argument"],"backgroundTag":"invalid-argument-value","analyzedSha":"73e0f67d83c59909b571efdfc88c4bc28c309cb1","analyzedAt":"2026-09-22T01:30:00.501Z","contentChangedAt":"2026-09-22T01:30:00.501Z","schemaVersion":2},"datasetVersion":"2026-09-22T06:17:15.046Z"}