Hmbown/CodeWhale · error
Invalid durable task id
Error message
Invalid durable task id
What it means
read_bound_task validates the durable task id before using it to build a file path: the id must be non-empty and contain only ASCII alphanumerics, '_', or '-'. Anything else (slashes, dots, spaces, unicode) is rejected to prevent path traversal or malformed record lookups against the tasks directory.
Solutions
- Pass the bare task id exactly as produced by the task manager (alphanumeric, '_' or '-' only)
- Strip file extensions and path components: use the id, not "<id>.json" or a full path
- Trim and validate the id in the caller with the same charset rule before invoking read_bound_task
- If ids come from external config, regenerate them via the task manager's id generation
Example fix
// before
let record = mgr.read_bound_task(&format!("{id}.json"))?;
// after
let bare = id.trim().trim_end_matches(".json");
if !bare.is_empty() && bare.bytes().all(|b| b.is_ascii_alphanumeric() || matches!(b, b'_' | b'-')) {
let record = mgr.read_bound_task(bare)?;
} Defensive patterns
Strategy: validation
Validate before calling
fn valid_task_id(s: &str) -> bool { !s.is_empty() && s.bytes().all(|b| b.is_ascii_alphanumeric() || matches!(b, b'_' | b'-')) } Try / catch
if let Err(e) = mgr.read_bound_task(id) { if e.to_string().contains("Invalid durable task id") { /* sanitize id and retry */ } } Prevention
- Pass bare ids, never filenames or paths
- Trim ids read from external sources before use
- Persist ids exactly as generated, without escaping or encoding
When it happens
Trigger: Calling read_bound_task with an empty string, an id containing '/' or '..' or '.', a URL-encoded or whitespace-padded id, or an id read from external storage that was serialized differently.
Common situations: Persisting task ids in a UI state file that escapes them; passing a filename ("abc.json") instead of the bare id; user-typed ids from a CLI argument; older records using dotted ids.
Understand the failure class
Background: "invalid id" errors: invalid identifier format — why libraries reject IDs before lookup, and how to fix them — this error's family across 37 libraries.
Related errors
- Invalid session id
- Invalid task execution identity
- A pinned task provider requires an explicit model
- agent profile provider cannot be empty
- agent profile provider must be a simple provider id
AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22).
Data as JSON: /api/errors/573ac85b45a8fb62.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/task_manager.rs:1620
self.add_task_with_id(req, Self::new_task_id()).await
}
/// Allocate the durable owner identity before queue insertion so callers
/// can register graph spawn intent first.
#[must_use]
pub(crate) fn new_task_id() -> String {
format!("task_{}", &Uuid::new_v4().simple().to_string()[..16])
}
/// Read the exact durable task binding without adopting another process's
/// queue. Used by the automation dispatcher while it owns the store claim.
pub(crate) fn read_bound_task(&self, task_id: &str) -> Result<Option<TaskRecord>> {
if task_id.is_empty()
|| !task_id
.bytes()
.all(|byte| byte.is_ascii_alphanumeric() || matches!(byte, b'_' | b'-'))
{
bail!("Invalid durable task id");
}
read_bound_task_file(&self.tasks_dir.join(format!("{task_id}.json")), task_id)
}
/// Recover a persisted automation admission under its cross-process
/// dispatch lock. A promoted task is accepted work, including failed or
/// interrupted work; returning it must never enqueue it again.
pub(crate) async fn recover_task_admission(
&self,
request: NewTaskRequest,
task_id: String,
) -> Result<TaskRecord> {
validate_preallocated_task_id(&task_id)?;
if let Some(task) = self.read_bound_task(&task_id)? {
validate_bound_task_request(&task, &request)?;
return Ok(task);
}
let staged_path = self.tasks_dir.join(format!(".{task_id}.json.pending"));View on GitHub (pinned to 73e0f67d83)