{"record":{"id":"3f15bb1f1132c3b8","repo":"flxzt/rnote","slug":"on-conflict-behaviour-is-still-ask-after-prompting","errorCode":null,"errorMessage":"on-conflict behaviour is still Ask after prompting the user.","messagePattern":"on-conflict behaviour is still Ask after prompting the user\\.","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"crates/rnote-cli/src/export.rs","lineNumber":485,"sourceCode":"                    ))\n                    .items(options)\n                    .default(1)\n                    .interact()\n                {\n                    Ok(0) => cli::open_file_default_app(output_file)?,\n                    Ok(c) => on_conflict = options[c],\n                    Err(e) => {\n                        return Err(anyhow::anyhow!(\n                            \"Failed to show select prompt, retry or select the behavior with\\\"--on-conflict\\\", Err: {e:?}\"\n                        ));\n                    }\n                };\n            }\n        }\n    };\n    match on_conflict {\n        OnConflict::Ask => {\n            return Err(anyhow::anyhow!(\n                \"on-conflict behaviour is still Ask after prompting the user.\"\n            ));\n        }\n        OnConflict::AlwaysOverwrite => {\n            on_conflict = OnConflict::Overwrite;\n            *on_conflict_overwrite = Some(on_conflict);\n        }\n        OnConflict::AlwaysSkip => {\n            on_conflict = OnConflict::Skip;\n            *on_conflict_overwrite = Some(on_conflict);\n        }\n        OnConflict::AlwaysSuffix => {\n            on_conflict = OnConflict::Suffix;\n            *on_conflict_overwrite = Some(on_conflict);\n        }\n        OnConflict::Overwrite | OnConflict::Skip | OnConflict::Suffix => (),\n    }\n    match on_conflict {","sourceCodeStart":467,"sourceCodeEnd":503,"githubUrl":"https://github.com/flxzt/rnote/blob/bbc5354502ba2fc83eec2670b535348825e679a6/crates/rnote-cli/src/export.rs#L467-L503","documentation":"In rnote-cli's `file_conflict_prompt_action` (crates/rnote-cli/src/export.rs:485), after resolving the on-conflict policy — either from the remembered `on_conflict_overwrite` value or from an interactive dialoguer prompt — the resolved policy is still `OnConflict::Ask`. Since `Ask` is only a placeholder meaning \"query the user\", reaching the final match with it means the resolution step failed to produce a concrete action (typically because `on_conflict_overwrite` was explicitly seeded with `Some(Ask)`). The function treats this as an internal invariant violation and aborts the export.","triggerScenarios":"Calling `get_output_file_path` or `doc_page_determine_output_file` for a target path that already exists while `on_conflict_overwrite` is `Some(OnConflict::Ask)` (a stale/incorrectly-initialized memoized value), so the prompt loop at line 456 (`while matches!(on_conflict, OnConflict::Ask)`) is skipped entirely and the first match at line 483 sees `Ask`.","commonSituations":"Programmatic/embedded use of the CLI export API where the `on_conflict_overwrite` out-parameter was initialized to `Some(Ask)` instead of `None`; state carried over from a previous session where the user was never asked; running in a non-interactive environment combined with a hand-built `OnConflict` value that a user would normally never select via `--on-conflict`.","solutions":["Initialize `on_conflict_overwrite` to `None` (or `Some(Overwrite)`/`Some(Skip)`/`Some(Suffix)`) before calling the export functions; never seed it with `OnConflict::Ask`.","Pass an explicit concrete `--on-conflict` value (e.g. overwrite, skip, suffix) so `Ask` never enters the resolution path.","If you must keep `Some(Ask)`, ensure the interactive prompt loop runs first (the loop is only entered when `on_conflict_overwrite` is `None`) — remove the pre-seeded value so the user actually gets prompted.","Library fix: normalize `OnConflict::Ask` to a concrete variant whenever it appears in `on_conflict_overwrite`, or treat `Some(Ask)` as `None` at the top of `file_conflict_prompt_action`."],"exampleFix":"// before\nlet mut on_conflict_overwrite: Option<OnConflict> = Some(OnConflict::Ask);\nexport_to_file(..., on_conflict, &mut on_conflict_overwrite, ...).await?;\n// after\nlet mut on_conflict_overwrite: Option<OnConflict> = None; // let the prompt resolve Ask\nexport_to_file(..., on_conflict, &mut on_conflict_overwrite, ...).await?;","handlingStrategy":"validation","validationCode":"// Before invoking the export API, never seed the memo with Ask:\nlet mut on_conflict_overwrite: Option<OnConflict> = match on_conflict {\n    OnConflict::Ask => None, // let the interactive prompt resolve it\n    other => Some(other),\n};\nif output_file.exists() && on_conflict_overwrite == Some(OnConflict::Ask) {\n    anyhow::bail!(\"--on-conflict ask cannot be pre-selected; use overwrite|skip|suffix\");\n}","typeGuard":"fn is_concrete_conflict_policy(p: &OnConflict) -> bool {\n    !matches!(p, OnConflict::Ask | OnConflict::AlwaysOverwrite | OnConflict::AlwaysSkip | OnConflict::AlwaysSuffix)\n}","tryCatchPattern":null,"preventionTips":["Initialize on_conflict_overwrite to None, never Some(Ask).","Require an explicit concrete --on-conflict value in non-interactive/CI runs.","Write a unit test asserting the prompt loop resolves Ask into a concrete variant."],"tags":["cli","rust","interactive-prompt","state-machine"],"backgroundTag":"invalid-state-transition","analyzedSha":"bbc5354502ba2fc83eec2670b535348825e679a6","analyzedAt":"2026-09-08T13:20:33.747Z","contentChangedAt":"2026-09-08T13:20:33.747Z","schemaVersion":2},"datasetVersion":"2026-09-16T04:17:20.429Z"}