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
- Treat as an internal invariant violation; verify the `Always*` normalization arms in the first match (lines 489-500) are intact.
- 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.
- 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
- Keep the Always* normalization arms in the first match intact when refactoring.
- Add a debug_assert! that the second match never receives an Always* variant.
- Collapse the two sequential matches into one to make the invariant structural.
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
- on-conflict behaviour is still Ask after prompting the user.
- Failed to get file stem
- Failed to init audioplayer. file
- PatternStyle try_from
- PredefinedFormat try_from
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)