Hmbown/CodeWhale · error

Task record identity differs from its path

Error message

Task record identity differs from its path

What it means

Each task file must be named <task_id>.json, and the record inside must carry that same id. After validating the id is a safe storage id, the manager compares the file stem to task.id; a mismatch means the durable identity and the record disagree, which would corrupt id-based lookups, so it fails.

Solutions

  1. Rename the file so its stem equals the record's `task.id` (or vice versa: edit record.id to match the filename)
  2. Regenerate the task properly instead of copying files manually
  3. Re-run recovery after fixing, and avoid hand-editing task store files

Example fix

// before: file 9f2e....json contains id task_00ff...
cp task_aaaa000000000000.json task_bbbb000000000000.json
// after: also update the inner id
sed -i 's/task_aaaa000000000000/task_bbbb000000000000/' task_bbbb000000000000.json
Defensive patterns

Strategy: validation

Validate before calling

let stem = path.file_stem().and_then(|s| s.to_str()).unwrap_or_default();
if stem != record.id { return Err(anyhow!("filename {stem} does not match record id {}", record.id)); }

Type guard

fn file_matches_record(path: &Path, record: &TaskRecord) -> bool {
    path.file_stem().and_then(|s| s.to_str()) == Some(record.id.as_str())
}

Try / catch

match manager.load_task_file(&path).await {
    Err(e) if e.to_string().contains("identity differs from its path") => {
        eprintln!("repairing: rename file to match record id");
        std::fs::rename(&path, dir.join(format!("{}.json", record.id)))?;
        manager.load_task_file(&path).await?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: Loading a task file whose filename stem (e.g. other_id from other_id.json) does not equal the decoded record's `task.id` — typically after a manual rename, copy, or hand-edited record.

Common situations: Manually renaming a .json task file; copying a task file to seed a new task without editing the inner id; a restore/sync tool that renamed files but not contents; a merge of two stores.

Understand the failure class

Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.

Related errors


AI-assisted analysis of Hmbown/CodeWhale@73e0f67d83 (2026-09-22). Data as JSON: /api/errors/321d2d120ed8483b. Report an issue: GitHub.

Appendix: source

Thrown at crates/tui/src/task_manager.rs:3170

            let entry = entry?;
            let path = entry.path();
            if path.extension().is_none_or(|ext| ext != "json") {
                continue;
            }
            let content = fs::read_to_string(&path)
                .with_context(|| format!("Failed to read task file {}", path.display()))?;
            let task: TaskRecord = serde_json::from_str(&content)
                .with_context(|| format!("Failed to parse task file {}", path.display()))?;
            if task.schema_version > CURRENT_TASK_SCHEMA_VERSION {
                bail!(
                    "Task schema v{} is newer than supported v{}",
                    task.schema_version,
                    CURRENT_TASK_SCHEMA_VERSION
                );
            }
            ensure_safe_storage_id("task id", &task.id)?;
            if path.file_stem().and_then(|stem| stem.to_str()) != Some(task.id.as_str()) {
                bail!("Task record identity differs from its path");
            }
            if let Some(scope) = &task.execution_scope {
                validate_execution_id(scope, 64)?;
            }
            if let Some(generation) = &task.execution_generation {
                validate_execution_id(generation, 32)?;
                if task.execution_scope.is_none() {
                    bail!("Task generation has no execution scope");
                }
            }
            tasks.insert(task.id.clone(), task);
        }
    }

    let mut queue = if queue_path.exists() {
        let content = fs::read_to_string(queue_path)
            .with_context(|| format!("Failed to read queue file {}", queue_path.display()))?;
        let parsed: QueueFile = serde_json::from_str(&content)

View on GitHub (pinned to 73e0f67d83)