From 3b199eaba58d7bab8debda511cb68a4e0cf3de0a Mon Sep 17 00:00:00 2001 From: Zious Date: Mon, 20 Apr 2026 22:23:27 -0500 Subject: [PATCH] refactor: exit 64 for ambiguous partial_match input (#237) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit issue move, issue link, and issue unlink reported ambiguous-substring errors via anyhow::bail! which doesn't inject a JrError into the cause chain, so main.rs fell back to exit 1. issue list --status already used JrError::UserError (exit 64) for the same failure class, producing an inconsistency that broke scripts keying off $? == 64. Change the three Ambiguous + no_input branches to return JrError::UserError so all four handlers surface exit 64 uniformly. Error-message strings are byte-identical — no script-visible wording changes. Updates the two tests added in PR #238 to pin Some(64). Closes #237 --- src/cli/issue/links.rs | 11 +++++++---- src/cli/issue/workflow.rs | 5 +++-- tests/issue_commands.rs | 12 ++++-------- 3 files changed, 14 insertions(+), 14 deletions(-) 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!(