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

  1. Pass the bare task id exactly as produced by the task manager (alphanumeric, '_' or '-' only)
  2. Strip file extensions and path components: use the id, not "<id>.json" or a full path
  3. Trim and validate the id in the caller with the same charset rule before invoking read_bound_task
  4. 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

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


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)