flxzt/rnote · error

on-conflict behaviour is still

Error message

on-conflict behaviour is still {on_conflict} after applying overwrite.

What it means

Final defensive arm of `file_conflict_prompt_action` (crates/rnote-cli/src/export.rs:529): after the first match already normalized every `Always*` variant into its concrete counterpart (`AlwaysOverwrite`→`Overwrite`, `AlwaysSkip`→`Skip`, `AlwaysSuffix`→`Suffix`), the second match still observes an `Always*` value. That is impossible in the current flow, so this arm is an invariant guard that reports the leftover variant via the formatted message `on-conflict behaviour is still {on_conflict} after applying overwrite.`

Solutions

  1. Treat as an internal invariant violation; verify the `Always*` normalization arms in the first match (lines 489-500) are intact.
  2. If you refactored the function, re-add the mapping: `OnConflict::AlwaysOverwrite => { on_conflict = OnConflict::Overwrite; *on_conflict_overwrite = Some(on_conflict); }` and likewise for Skip/Suffix.
  3. Simplify by merging the two matches so normalization and dispatch happen in one place, eliminating the possibility of `Always*` leaking through.

Example fix

// before (refactor removed normalization, leaking Always*)
// after: restore normalization before dispatch
match on_conflict {
    OnConflict::AlwaysOverwrite => { on_conflict = OnConflict::Overwrite; *on_conflict_overwrite = Some(on_conflict); }
    OnConflict::AlwaysSkip => { on_conflict = OnConflict::Skip; *on_conflict_overwrite = Some(on_conflict); }
    OnConflict::AlwaysSuffix => { on_conflict = OnConflict::Suffix; *on_conflict_overwrite = Some(on_conflict); }
    _ => (),
}
Defensive patterns

Strategy: validation

Validate before calling

// Guard callers that construct OnConflict manually: normalize Always* before the API sees them.
let on_conflict = match on_conflict {
    OnConflict::AlwaysOverwrite => OnConflict::Overwrite,
    OnConflict::AlwaysSkip => OnConflict::Skip,
    OnConflict::AlwaysSuffix => OnConflict::Suffix,
    other => other,
};

Type guard

fn is_always_variant(p: &OnConflict) -> bool {
    matches!(p, OnConflict::AlwaysOverwrite | OnConflict::AlwaysSkip | OnConflict::AlwaysSuffix)
}

Prevention

When it happens

Trigger: Unreachable with the current source; only fires if the normalization block (lines 489-500) is deleted or reordered, leaving an `AlwaysOverwrite`/`AlwaysSkip`/`AlwaysSuffix` value to fall through to the second match.

Common situations: Encountered by contributors refactoring the conflict-resolution code or by anyone patching the function and accidentally removing the `Always*` → concrete mapping; end users of stock rnote-cli builds should never see it.

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 flxzt/rnote@bbc5354502 (2026-09-08). Data as JSON: /api/errors/4bd6f152dcd419c4. Report an issue: GitHub.

Appendix: source

Thrown at crates/rnote-cli/src/export.rs:529

            let mut new_path = output_file.to_path_buf();
            let Some(file_stem) = new_path
                .file_stem()
                .map(|s| s.to_string_lossy().to_string())
            else {
                return Err(anyhow::anyhow!("Failed to get file stem"));
            };
            let ext = new_path
                .extension()
                .map(|n| n.to_string_lossy().to_string())
                .unwrap_or_default();
            while new_path.exists() {
                i += 1;
                new_path.set_file_name(format!("{file_stem}_{i}.{ext}"))
            }
            Ok(Some(new_path))
        }
        OnConflict::AlwaysOverwrite | OnConflict::AlwaysSkip | OnConflict::AlwaysSuffix => {
            Err(anyhow::anyhow!(
                "on-conflict behaviour is still {on_conflict} after applying overwrite."
            ))
        }
    }
}

pub(crate) async fn export_to_file(
    engine: &mut Engine,
    rnote_file: impl AsRef<Path>,
    output_file: impl AsRef<Path>,
    export_command: &cli::ExportCommand,
    on_conflict: OnConflict,
    on_conflict_overwrite: &mut Option<OnConflict>,
    open: bool,
) -> anyhow::Result<()> {
    let rnote_bytes = cli::read_bytes_from_file(&rnote_file).await?;
    let engine_snapshot = EngineSnapshot::load_from_rnote_bytes(rnote_bytes).await?;
    let _ = engine.load_snapshot(engine_snapshot);

View on GitHub (pinned to bbc5354502)