affaan-m/ECC · error · anyhow::Error

Conflict messages require at least one --file

Error message

Conflict messages require at least one --file

What it means

Thrown when building a `Conflict` message type: the `files` vector is empty, but a conflict requires at least one file to attribute the conflict to. The code clones `files.first()` and bails via `ok_or_else` if it is `None`. The conflict payload shape (`{ file, description }`) is structurally single-file, so an empty file list cannot be coerced.

Source

Thrown at ecc2/src/main.rs:3936

    files: Vec<String>,
) -> Result<comms::MessageType> {
    Ok(match kind {
        MessageKindArg::Handoff => comms::MessageType::TaskHandoff {
            task: text,
            context: context.unwrap_or_default(),
            priority: priority.into(),
        },
        MessageKindArg::Query => comms::MessageType::Query { question: text },
        MessageKindArg::Response => comms::MessageType::Response { answer: text },
        MessageKindArg::Completed => comms::MessageType::Completed {
            summary: text,
            files_changed: files,
        },
        MessageKindArg::Conflict => {
            let file = files
                .first()
                .cloned()
                .ok_or_else(|| anyhow::anyhow!("Conflict messages require at least one --file"))?;
            comms::MessageType::Conflict {
                file,
                description: context.unwrap_or(text),
            }
        }
    })
}

fn format_remote_dispatch_action(action: &session::manager::RemoteDispatchAction) -> String {
    match action {
        session::manager::RemoteDispatchAction::SpawnedTopLevel => "spawned top-level".to_string(),
        session::manager::RemoteDispatchAction::Assigned(action) => match action {
            session::manager::AssignmentAction::Spawned => "spawned delegate".to_string(),
            session::manager::AssignmentAction::ReusedIdle => "reused idle delegate".to_string(),
            session::manager::AssignmentAction::ReusedActive => {
                "reused active delegate".to_string()
            }
            session::manager::AssignmentAction::DeferredSaturated => {

View on GitHub (pinned to 01e15490f0)

Solutions

  1. Supply at least one `--file <path>` when using `--kind conflict`.
  2. If you have no file to attribute, pick a more appropriate message kind (e.g. `note` or `query`).
  3. In wrapper code, validate that `files.len() >= 1` before constructing a Conflict variant.

Example fix

// before
ecc comms post --kind conflict --text "merge issue"

// after
ecc comms post --kind conflict --file src/main.rs --text "merge issue"
Defensive patterns

Strategy: validation

Validate before calling

// Validate conflict payload before building the message
fn build_message(kind: MessageKindArg, text: String, files: Vec<String>, context: Option<String>)
    -> Result<comms::MessageType>
{
    if matches!(kind, MessageKindArg::Conflict) && files.is_empty() {
        anyhow::bail!("Conflict messages require at least one --file");
    }
    // ... rest of construction
}

// Or at the clap layer: make `--file` required when `--kind conflict`
//   #[arg(required_if_eq("kind", "conflict"))]
//   files: Vec<String>,

Type guard

fn is_valid_conflict(kind: &MessageKindArg, files: &[String]) -> bool {
    !matches!(kind, MessageKindArg::Conflict) || !files.is_empty()
}

Prevention

When it happens

Trigger: Posting a comms message with `--kind conflict` but no `--file` argument. A wrapper that constructs a conflict message and forgets to populate the files list. UI flow that lets users send a conflict without attaching a file.

Common situations: Misunderstanding the conflict message contract (assuming `description` alone is enough). Bug in a higher-level dispatcher that drops the files array. Argument parsing edge case where `--file` is consumed but not forwarded.

Related errors


AI-assisted analysis of affaan-m/ECC@01e15490f0 (2026-08-13). Data as JSON: /api/errors/4eb6942f81728445. Report an issue: GitHub.