diff --git a/src/cli/issue/links.rs b/src/cli/issue/links.rs index 4830fb3a..cc1df54f 100644 --- a/src/cli/issue/links.rs +++ b/src/cli/issue/links.rs @@ -3,6 +3,7 @@ use anyhow::{Context, Result, bail}; use crate::api::client::JiraClient; use crate::cli::{IssueCommand, OutputFormat}; +use crate::error::JrError; use crate::output; use crate::partial_match::{self, MatchResult}; @@ -65,11 +66,12 @@ pub(super) async fn handle_link( MatchResult::ExactMultiple(name) => name, MatchResult::Ambiguous(matches) => { if no_input { - bail!( + return Err(JrError::UserError(format!( "Ambiguous link type \"{}\". Matches: {}", link_type_name, matches.join(", ") - ); + )) + .into()); } let selection = dialoguer::Select::new() .with_prompt(format!("Multiple types match \"{link_type_name}\"")) @@ -135,11 +137,12 @@ pub(super) async fn handle_unlink( MatchResult::ExactMultiple(name) => name, MatchResult::Ambiguous(matches) => { if no_input { - bail!( + return Err(JrError::UserError(format!( "Ambiguous link type \"{}\". Matches: {}", type_name, matches.join(", ") - ); + )) + .into()); } let selection = dialoguer::Select::new() .with_prompt(format!("Multiple types match \"{type_name}\"")) diff --git a/src/cli/issue/workflow.rs b/src/cli/issue/workflow.rs index 24b18b30..8e4b72d6 100644 --- a/src/cli/issue/workflow.rs +++ b/src/cli/issue/workflow.rs @@ -156,11 +156,12 @@ pub(super) async fn handle_move( } MatchResult::Ambiguous(matches) => { if no_input { - bail!( + return Err(JrError::UserError(format!( "Ambiguous transition \"{}\". Matches: {}", target_status, matches.join(", ") - ); + )) + .into()); } // Interactive disambiguation eprintln!( diff --git a/tests/issue_commands.rs b/tests/issue_commands.rs index 6dbcc935..8cabb83c 100644 --- a/tests/issue_commands.rs +++ b/tests/issue_commands.rs @@ -1742,13 +1742,10 @@ async fn test_move_single_substring_rejected_no_input() { !output.status.success(), "Expected failure on ambiguous substring, stderr: {stderr}" ); - // workflow.rs uses `anyhow::bail!` which doesn't inject a JrError into - // the cause chain → main.rs falls back to exit 1. Pinning this lets a - // future refactor to JrError::UserError (exit 64) flag the test for review. assert_eq!( output.status.code(), - Some(1), - "Ambiguous transition currently exits 1 via anyhow::bail!, got: {:?}", + Some(64), + "Ambiguous transition should exit 64 (UserError), got: {:?}", output.status.code() ); assert!( @@ -1802,11 +1799,10 @@ async fn test_link_single_substring_rejected_no_input() { !output.status.success(), "Expected failure on ambiguous substring, stderr: {stderr}" ); - // links.rs uses `anyhow::bail!` → exit 1 (see move test for rationale). assert_eq!( output.status.code(), - Some(1), - "Ambiguous link type currently exits 1 via anyhow::bail!, got: {:?}", + Some(64), + "Ambiguous link type should exit 64 (UserError), got: {:?}", output.status.code() ); assert!(