Hmbown/CodeWhale · warning
Runtime turn operation is already being claimed; retry
Error message
Runtime turn operation is already being claimed; retry
What it means
The runtime refuses to claim a turn operation because another process/thread already holds the claim. Claiming is done through an exclusive create (O_CREAT|O_EXCL-style) file binding, and when the OS reports WouldBlock or Interrupted the code converts it into this bail so callers know to retry rather than treat it as corruption. It is a transient contention signal, not a data error.
Solutions
- Retry the turn operation after a short backoff; the claim is transient and the competing holder usually releases it.
- Check for another running Codewhale instance on the same runtime store and close it.
- If a stale claim persists after a crash, use the store's stale-binding removal path (remove a binding left before its turn record by a process crash).
- Ensure only one instance uses the same runtime store directory at a time.
Example fix
// before
let claim = claim_turn_operation(&store)?; // panics/aborts on contention
// after
match claim_turn_operation(&store) {
Err(e) if e.to_string().contains("already being claimed") => retry_with_backoff(),
other => other?,
} Defensive patterns
Strategy: retry
Validate before calling
// No pre-check possible; contention is dynamic.
let claim = loop {
match claim_turn_operation(&store) {
Ok(c) => break c,
Err(e) if e.to_string().contains("already being claimed") => std::thread::sleep(std::time::Duration::from_millis(100)),
Err(e) => return Err(e),
}
}; Try / catch
match claim_turn_operation(&store) {
Err(e) if e.to_string().contains("already being claimed") => retry_with_backoff(), // transient
Err(e) => return Err(e.context("claim failed")),
Ok(claim) => use_claim(claim),
} Prevention
- Run only one Codewhale instance against a given runtime store directory.
- Use per-user/per-project store directories to avoid cross-session contention.
- Always retry with bounded backoff on this specific message; it is designed to be retryable.
- After a crash, clean stale bindings before re-claiming.
When it happens
Trigger: Calling the runtime claim path (claim of a Runtime turn operation binding) while the on-disk binding is locked/held by another process, or the claim syscall is interrupted (EINTR).
Common situations: Two TUI/CLI instances sharing the same runtime store directory; a crashed or hung previous run that still holds the claim; concurrent turn operations racing during startup.
Understand the failure class
Background: "Invalid state transition" errors: "status must be X, actually Y", "already rejected/charging/uninstalled", "cannot ... while running" — what they mean when a library rejects your call — this error's family across 31 libraries.
Related errors
- Another pet recorder is using this output.
- child wall-time budget exhausted
- {}
- Frame lock failed
- Goal changed while preparing the turn; retry
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/6ad617b5076cef23.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/runtime_threads.rs:1910
let mut claim =
fd_lock::RwLock::new(self.open_turn_operation_claim_lock(operation_key_fingerprint)?);
let _guard = self.acquire_turn_operation_claim(&mut claim)?;
operation()
}
fn acquire_turn_operation_claim<'a>(
&self,
claim: &'a mut fd_lock::RwLock<File>,
) -> Result<fd_lock::RwLockWriteGuard<'a, File>> {
match claim.try_write() {
Ok(guard) => Ok(guard),
Err(error)
if matches!(
error.kind(),
std::io::ErrorKind::WouldBlock | std::io::ErrorKind::Interrupted
) =>
{
bail!("Runtime turn operation is already being claimed; retry")
}
Err(error) => Err(error).context("Failed to claim Runtime turn operation"),
}
}
/// Remove a binding left before its turn record by a process crash.
///
/// Bindings are committed before turns, while engine submission happens
/// only after both are durable. A binding with no turn therefore never
/// reached the engine and is safe to discard during startup recovery.
fn recover_incomplete_turn_operations(&self) -> Result<()> {
let operations_dir = checked_existing_runtime_store_dir(&self.turn_operations_dir)?;
for entry in fs::read_dir(&operations_dir)
.with_context(|| format!("Failed to read {}", operations_dir.display()))?
{
let path = entry?.path();
if path.extension().is_none_or(|extension| extension != "json") {
continue;View on GitHub (pinned to 433685b202)