Hmbown/CodeWhale · error
conditional terminal append requires a terminal worker event
Error message
conditional terminal append requires a terminal worker event
What it means
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.
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.
Example fix
// before
ledger.append_terminal_event_if_leased(&run_id, &worker_id, &task_id, attempts, &ts, payload)?;
// after
if is_terminal_payload(&payload) {
ledger.append_terminal_event_if_leased(&run_id, &worker_id, &task_id, attempts, &ts, payload)?;
} else {
ledger.append_event_if_leased(&run_id, &worker_id, &task_id, attempts, &ts, payload)?;
} Defensive patterns
Strategy: validation
Validate before calling
fn is_terminal_payload(p: &FleetWorkerEventPayload) -> bool {
matches!(p, FleetWorkerEventPayload::Completed { .. } | FleetWorkerEventPayload::Failed { .. } | FleetWorkerEventPayload::Cancelled { .. })
}
assert!(is_terminal_payload(&payload), "terminal append needs terminal payload"); Type guard
let is_terminal_payload = |p: &FleetWorkerEventPayload| matches!(p, FleetWorkerEventPayload::Completed { .. } | FleetWorkerEventPayload::Failed { .. } | FleetWorkerEventPayload::Cancelled { .. }); Try / catch
match ledger.append_terminal_event_if_leased(&run_id, &w, &t, attempts, &ts, payload) {
Ok(Some(event)) => { /* terminal recorded */ }
Ok(None) => { /* lease moved on */ }
Err(e) if e.to_string().contains("requires a terminal worker event") => { /* dispatch to progress API */ }
Err(e) => return Err(e.into()),
} Prevention
- 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.
When it happens
Trigger: 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).
Common situations: 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.
Understand the failure class
Background: "Must be a positive integer", "Invalid value", "Unsupported": the invalid-argument-value error family, when a library rejects the value you pass — this error's family across 35 libraries.
Related errors
- attempt finalization requires a terminal worker event
- attempt finalization status must be terminal
- attempt receipt generation does not match its lease
- attempt receipt identity does not match its terminal event
- conditional progress append does not accept terminal worker…
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/92861a9c4c4b95e7.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/fleet/ledger.rs:916
/// still live. This is the completion side of the same compare-and-set used
/// by cancellation: whichever terminal transition acquires the ledger lock
/// first wins, and the loser cannot overwrite the task or mint a receipt.
pub fn append_terminal_event_if_leased(
&self,
run_id: &FleetRunId,
worker_id: &str,
task_id: &str,
expected_attempts: u32,
timestamp: &str,
payload: FleetWorkerEventPayload,
) -> Result<Option<FleetWorkerEvent>> {
if !matches!(
&payload,
FleetWorkerEventPayload::Completed { .. }
| FleetWorkerEventPayload::Failed { .. }
| FleetWorkerEventPayload::Cancelled { .. }
) {
bail!("conditional terminal append requires a terminal worker event");
}
let appended = self.with_write_lock(|| {
let state = self.rebuild_state_unlocked()?;
let key = task_key(&run_id.0, task_id);
let Some(task) = state.tasks.get(&key) else {
return Ok(None);
};
if task.status != FleetTaskLedgerStatus::Leased
|| task.leased_to.as_deref() != Some(worker_id)
|| task.entry.attempts != expected_attempts
{
return Ok(None);
}
let event = next_worker_event(&state, run_id, worker_id, task_id, timestamp, payload);
self.append_record_unlocked(&FleetLedgerRecord::EventAppended {
event: event.clone(),
})?;
Ok(Some(event))View on GitHub (pinned to 73e0f67d83)