Hmbown/CodeWhale · error
Workspace already has an active turn
Error message
Workspace already has an active turn (thread {id}); wait for it to finish before restoring files What it means
File restoration for a workspace is refused when another active turn overlaps that workspace — either the busy thread's workspace contains the target workspace or vice versa (path prefix check in both directions). This prevents restoring files that a running turn may be reading or writing.
Solutions
- Wait for all turns in overlapping workspaces (thread {id} named in the message) to finish, then restore.
- Cancel the overlapping thread's turn if immediate restore is required.
- Restore into a workspace that does not nest under or contain any busy thread's workspace.
- Query busy threads first and skip/pause restore for overlapping paths.
Example fix
// before
api.restore_files(&workspace).await?; // may overlap a busy thread
// after
for t in api.list_threads().await? {
if t.active_turn.is_some() && workspaces_overlap(&t.workspace, &workspace) {
return Err(format!("wait for thread {} to finish", t.id));
}
}
api.restore_files(&workspace).await?; Defensive patterns
Strategy: validation
Validate before calling
for t in api.list_threads().await? {
if t.active_turn.is_some() {
let other = t.workspace.canonicalize()?;
if workspace.starts_with(&other) || other.starts_with(&workspace) {
return Err(format!("thread {} is busy in an overlapping workspace", t.id));
}
}
} Try / catch
match api.restore_files(&workspace).await {
Err(e) if e.to_string().contains("already has an active turn") => {
// parse thread id from message, await its completion, then retry
},
other => other?,
} Prevention
- Check all busy threads for workspace path overlap (both directions) before restoring
- Avoid nested workspaces across concurrently active threads
- Schedule file restores for idle windows
When it happens
Trigger: Calling the restore-files API for workspace W while any thread with an active turn has a workspace that is a path-prefix ancestor or descendant of W (after canonicalization).
Common situations: User restores a snapshot into the project root while a subagent turn runs in `project/src`; nested workspaces share a directory tree; an automation restores files on a timer that collides with running turns.
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
- workspace cannot be changed while the thread has an active…
- A saved trace identifier collided. Try saving again.
- a window change is already in progress
- another Codewhale process
- Another pet recorder is using this output.
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/62b87faebd9dc966.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/runtime_threads.rs:8173
let workspace = workspace
.canonicalize()
.unwrap_or_else(|_| workspace.to_path_buf());
// Collect ids under the async lock, then do filesystem and store
// I/O without holding it.
let busy: Vec<String> = {
let active = self.active.lock().await;
active
.engines
.iter()
.filter(|(_, state)| state.active_turn.is_some())
.map(|(id, _)| id.clone())
.collect()
};
for id in busy {
let other = self.store.load_thread(&id)?.workspace;
let other = other.canonicalize().unwrap_or(other);
if workspace.starts_with(&other) || other.starts_with(&workspace) {
bail!(
"Workspace already has an active turn (thread {id}); wait for it to finish before restoring files"
);
}
}
Ok(())
}
/// Test seam: mark or clear an active turn on an installed test engine so
/// route-level tests can exercise restore admission.
#[cfg(test)]
pub(crate) async fn set_active_turn_for_test(
&self,
thread_id: &str,
turn_id: Option<&str>,
) -> Result<()> {
let mut active = self.active.lock().await;
let state = active
.enginesView on GitHub (pinned to 73e0f67d83)