Hmbown/CodeWhale · error · anyhow::Error
Turn {turn_id} has an indeterminate dynamic-tool receipt; re
Error message
Turn {turn_id} has an indeterminate dynamic-tool receipt; refusing to publish turn completion What it means
settle_dynamic_tools_for_terminal_turn found at least one dynamic tool call for this (thread_id, turn_id) with entry.indeterminate == true, so it refuses to publish turn completion. An indeterminate call may already have a terminal receipt in the JSONL log; completing the turn on top of that could freeze an ambiguous state into history.
Source
Thrown at crates/tui/src/runtime_threads.rs:4352
claim: ClaimedDynamicToolSettlement,
timeout: Duration,
) -> Result<()> {
let ack = self
.spawn_dynamic_tool_settlement(claim, DynamicToolTerminalOutcome::Timeout { timeout });
Self::await_dynamic_tool_settlement(ack).await?;
Ok(())
}
async fn settle_dynamic_tools_for_terminal_turn(
&self,
thread_id: &str,
turn_id: &str,
) -> Result<()> {
loop {
let (claims, mut settling, indeterminate) =
self.claim_or_watch_pending_dynamic_tools_for_turn(thread_id, turn_id);
if indeterminate {
bail!(
"Turn {turn_id} has an indeterminate dynamic-tool receipt; refusing to publish turn completion"
);
}
if claims.is_empty() && settling.is_empty() {
return Ok(());
}
let mut first_error = None;
for claim in claims {
let ack = self.spawn_dynamic_tool_settlement(
claim,
DynamicToolTerminalOutcome::Canceled {
reason: "turn_terminal",
terminal: true,
},
);
if let Err(error) = Self::await_dynamic_tool_settlement(ack).await
&& first_error.is_none()View on GitHub (pinned to 0c42157ee5)
Solutions
- Inspect the thread's JSONL log for terminal events of the turn's dynamic tool calls to establish what actually committed
- Repair the storage fault, then reconcile the turn state via runtime restart / storage reconciliation instead of forcing completion
- If the tombstone is provably stale (no terminal line exists), have the operator clear it - do not add a code path that bypasses the guard
- Keep the evidence: this error marks a genuine data-integrity incident
Defensive patterns
Strategy: try-catch
Try / catch
// Turn completion blocked by an indeterminate tool call: escalate.
match manager.complete_turn(thread_id, turn_id).await {
Ok(_) => Ok(()),
Err(e) if e.to_string().contains("indeterminate dynamic-tool receipt") => {
tracing::error!(%thread_id, %turn_id, "turn frozen; inspect Runtime storage");
Err(e)
}
Err(e) => Err(e),
} Prevention
- Ensure every dynamic tool call reaches a terminal outcome (result, timeout, or turn-end cancellation) before the turn closes
- Fix storage faults immediately; indeterminate tombstones come from failed rollbacks
- Never force-complete a frozen turn in code; reconcile storage with an operator
When it happens
Trigger: A turn ends (normally or via cancellation) after one of its dynamic tool calls failed settlement with a failed rollback (mark_dynamic_tool_claim_indeterminate). The loop at runtime_threads.rs:4344-4360 checks before settling or publishing.
Common situations: Storage failure during a tool-result append mid-turn, then the turn finishes; crash-recovery resuming a turn with a tombstoned call; cancellation racing a flaky disk.
Related errors
- User-input request '{}' has an indeterminate terminal receip
- Dynamic tool call '{call_id}' has an indeterminate terminal
- User-input request '{input_id}' has an indeterminate termina
- {error}
- Runtime has reached the pending dynamic tool call limit ({MA
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/e7d57e23ece75ad7.
Report an issue: GitHub.