Hmbown/CodeWhale · error

Task admission replay does not match the bound request

Error message

Task admission replay does not match the bound request

What it means

validate_bound_task_request re-checks a replayed admission request against the durable TaskRecord: title/name, cwd, permission_posture, and auto_approve must match exactly (with the same defaults applied). A mismatch means the request would re-admit a different task than the one durably bound, so it is rejected.

Solutions

  1. Replay the exact original request (same permission_posture, auto_approve, cwd, title) that produced the record
  2. If intent changed, create a new task instead of replaying admission
  3. Compare the record's fields first and reconcile the request before validating

Example fix

// before
let req = NewTaskRequest { auto_approve: Some(true), ..orig };
validate(&task, &req)?; // mismatch
// after
let req = NewTaskRequest { auto_approve: Some(task.auto_approve), ..orig };
validate(&task, &req)?;
Defensive patterns

Strategy: validation

Validate before calling

if request.auto_approve.unwrap_or(false) != task.auto_approve
    || request.permission_posture.as_deref().unwrap_or_default() != task.permission_posture.as_deref().unwrap_or_default() {
    return Err(anyhow!("request would re-admit a different task"));
}

Type guard

fn replay_matches(task: &TaskRecord, req: &NewTaskRequest) -> bool {
    req.permission_posture.as_deref().map(|p| Some(p) == task.permission_posture.as_deref()).unwrap_or(true)
        && req.auto_approve.unwrap_or(false) == task.auto_approve
}

Try / catch

match manager.admit_bound_task(&task, &request).await {
    Err(e) if e.to_string().contains("admission replay does not match") => {
        // intent changed: create a fresh task instead of replaying
        manager.create_task(request.into_fresh()).await?;
    }
    other => other?,
}

Prevention

When it happens

Trigger: Calling validate_bound_task_request (via admission/recovery) with a NewTaskRequest whose permission_posture differs from the record's, whose auto_approve differs from the record's default, or with any other non-matching bound field.

Common situations: Replaying a saved request after the user changed approval posture or cwd; constructing the request manually with defaults that differ from the stored record; resubmitting an old request against a newer record version.

Understand the failure class

Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.

Related errors


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

Appendix: source

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

            .as_ref()
            .is_some_and(|value| value != &task.workspace)
        || request
            .mode
            .as_ref()
            .is_some_and(|value| value != &task.mode)
        || request
            .allow_shell
            .is_some_and(|value| value != task.allow_shell)
        || request
            .trust_mode
            .is_some_and(|value| value != task.trust_mode)
        || request
            .permission_posture
            .as_deref()
            .is_some_and(|value| Some(value) != task.permission_posture.as_deref())
        || task.auto_approve != request.auto_approve.unwrap_or(false)
    {
        bail!("Task admission replay does not match the bound request");
    }
    Ok(())
}

/// A read-only inventory and reconstructed queue. Execution recovery is a
/// separate transaction requiring proof that a particular generation died.
struct LoadedTaskState {
    tasks: HashMap<String, TaskRecord>,
    queue: VecDeque<String>,
}

fn load_state(tasks_dir: &Path, queue_path: &Path) -> Result<LoadedTaskState> {
    let mut tasks = HashMap::new();
    if tasks_dir.exists() {
        for entry in fs::read_dir(tasks_dir)
            .with_context(|| format!("Failed to read tasks dir {}", tasks_dir.display()))?
        {
            let entry = entry?;

View on GitHub (pinned to 73e0f67d83)