From 4432af0aa4b4d2d13a6983629972e00df7ccfdc8 Mon Sep 17 00:00:00 2001 From: "Charles P. Cross" Date: Tue, 18 Nov 2025 20:52:49 -0500 Subject: [PATCH] fix: improve ResolvePartialID / ResolveID handling for `bd show` (issue #336) - Add fast path for exact ID matches in ResolvePartialID - Properly unmarshal ResolveID RPC responses used by `bd show` Summary: -------- Fixes regression introduced after v0.23.1 where `bd show `, `bd update `, and `bd close ` commands failed with "operation failed: issue not found" errors even when the issue existed in the database. Root Cause: ----------- The daemon's ResolveID RPC handler (handleResolveID in internal/rpc/server_issues_epics.go) returns a JSON-encoded string containing the resolved issue ID. For example, when resolving "ao-izl", the RPC response contains the JSON string: "ao-izl" (with quotes). After v0.23.1, the show/update/close commands in cmd/bd/show.go were changed to use string(resp.Data) to extract the resolved ID, which treats the raw bytes as a string without decoding the JSON. This caused the resolved ID to literally be "ao-izl" (including the quotes), which then failed to match the actual issue ID ao-izl (without quotes) when passed to subsequent Show/Update/Close RPC calls. In v0.23.1 (commit 77dcf55), the code correctly unmarshaled the JSON: var resolvedID string if err := json.Unmarshal(resp.Data, &resolvedID); err != nil { // handle error } This unmarshal step was removed in later commits, causing the regression. Fix: ---- Restored the JSON unmarshaling step in three places in cmd/bd/show.go: 1. showCmd - line 37-42 2. updateCmd - line 400-405 3. closeCmd - line 723-728 Each now properly decodes the JSON string response before using the resolved ID in subsequent RPC calls. Testing: -------- - Verified `bd show ` works correctly in both daemon and direct modes - Tested with newly created issues to ensure the fix works for all IDs - All Go tests pass (go test ./... -short) - Confirmed behavior matches v0.23.1 working binary The fix ensures that issue ID resolution works correctly regardless of whether the ID prefix matches the configured issue_prefix. --- cmd/bd/show.go | 21 ++++++++++++++++++--- internal/utils/id_parser.go | 10 +++++++++- 2 files changed, 27 insertions(+), 4 deletions(-) diff --git a/cmd/bd/show.go b/cmd/bd/show.go index 785cd8fc..6b17b396 100644 --- a/cmd/bd/show.go +++ b/cmd/bd/show.go @@ -34,7 +34,12 @@ var showCmd = &cobra.Command{ fmt.Fprintf(os.Stderr, "Error resolving ID %s: %v\n", id, err) os.Exit(1) } - resolvedIDs = append(resolvedIDs, string(resp.Data)) + var resolvedID string + if err := json.Unmarshal(resp.Data, &resolvedID); err != nil { + fmt.Fprintf(os.Stderr, "Error unmarshaling resolved ID: %v\n", err) + os.Exit(1) + } + resolvedIDs = append(resolvedIDs, resolvedID) } } else { // In direct mode, resolve via storage @@ -392,7 +397,12 @@ var updateCmd = &cobra.Command{ fmt.Fprintf(os.Stderr, "Error resolving ID %s: %v\n", id, err) os.Exit(1) } - resolvedIDs = append(resolvedIDs, string(resp.Data)) + var resolvedID string + if err := json.Unmarshal(resp.Data, &resolvedID); err != nil { + fmt.Fprintf(os.Stderr, "Error unmarshaling resolved ID: %v\n", err) + os.Exit(1) + } + resolvedIDs = append(resolvedIDs, resolvedID) } } else { var err error @@ -710,7 +720,12 @@ var closeCmd = &cobra.Command{ fmt.Fprintf(os.Stderr, "Error resolving ID %s: %v\n", id, err) os.Exit(1) } - resolvedIDs = append(resolvedIDs, string(resp.Data)) + var resolvedID string + if err := json.Unmarshal(resp.Data, &resolvedID); err != nil { + fmt.Fprintf(os.Stderr, "Error unmarshaling resolved ID: %v\n", err) + os.Exit(1) + } + resolvedIDs = append(resolvedIDs, resolvedID) } } else { var err error diff --git a/internal/utils/id_parser.go b/internal/utils/id_parser.go index a1a27bf9..00b1396e 100644 --- a/internal/utils/id_parser.go +++ b/internal/utils/id_parser.go @@ -37,6 +37,14 @@ func ParseIssueID(input string, prefix string) string { // - No issue found matching the ID // - Multiple issues match (ambiguous prefix) func ResolvePartialID(ctx context.Context, store storage.Storage, input string) (string, error) { + // Fast path: if the user typed an exact ID that exists, return it as-is. + // This preserves behavior where issue IDs may not match the configured + // issue_prefix (e.g. cross-repo IDs like "ao-izl"), while still allowing + // prefix-based and hash-based resolution for other inputs. + if issue, err := store.GetIssue(ctx, input); err == nil && issue != nil { + return input, nil + } + // Get the configured prefix prefix, err := store.GetConfig(ctx, "issue_prefix") if err != nil || prefix == "" { @@ -63,7 +71,7 @@ func ResolvePartialID(ctx context.Context, store storage.Storage, input string) normalizedID = prefixWithHyphen + input } - // First try exact match + // First try exact match on normalized ID issue, err := store.GetIssue(ctx, normalizedID) if err == nil && issue != nil { return normalizedID, nil