fix(update): add prefix routing like bd show (bd-618f) (#905)

bd update now uses resolveAndGetIssueWithRouting in direct mode,
matching bd show's prefix routing behavior. This enables cross-rig
issue updates from any directory using prefix-based routing.

Changes:
- Use resolveAndGetIssueWithRouting for ID resolution with routing
- Iterate over original args instead of pre-resolved IDs
- Use routed store (issueStore) and resolved ID throughout
- Remove early ID resolution that was blocking routing in direct mode
- Add proper result.Close() calls for resource cleanup

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
greghughespdx
2026-01-05 19:12:02 -08:00
committed by GitHub
parent e7d543af55
commit e212683103

View File

@@ -12,7 +12,6 @@ import (
"github.com/steveyegge/beads/internal/timeparsing" "github.com/steveyegge/beads/internal/timeparsing"
"github.com/steveyegge/beads/internal/types" "github.com/steveyegge/beads/internal/types"
"github.com/steveyegge/beads/internal/ui" "github.com/steveyegge/beads/internal/ui"
"github.com/steveyegge/beads/internal/utils"
"github.com/steveyegge/beads/internal/validation" "github.com/steveyegge/beads/internal/validation"
) )
@@ -205,13 +204,8 @@ create, update, show, or close operation).`,
} }
resolvedIDs = append(resolvedIDs, resolvedID) resolvedIDs = append(resolvedIDs, resolvedID)
} }
} else {
var err error
resolvedIDs, err = utils.ResolvePartialIDs(ctx, store, args)
if err != nil {
FatalErrorRespectJSON("%v", err)
}
} }
// Note: Direct mode (no daemon) uses resolveAndGetIssueWithRouting in the loop below
// If daemon is running, use RPC // If daemon is running, use RPC
if daemonClient != nil { if daemonClient != nil {
@@ -429,18 +423,32 @@ create, update, show, or close operation).`,
return return
} }
// Direct mode // Direct mode - use routed resolution for cross-repo lookups
updatedIssues := []*types.Issue{} updatedIssues := []*types.Issue{}
var firstUpdatedID string // Track first successful update for last-touched var firstUpdatedID string // Track first successful update for last-touched
for _, id := range resolvedIDs { for _, id := range args {
// Check if issue is a template: templates are read-only // Resolve and get issue with routing (e.g., gt-xyz routes to gastown)
issue, err := store.GetIssue(ctx, id) result, err := resolveAndGetIssueWithRouting(ctx, store, id)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error getting %s: %v\n", id, err) if result != nil {
result.Close()
}
fmt.Fprintf(os.Stderr, "Error resolving %s: %v\n", id, err)
continue continue
} }
if result == nil || result.Issue == nil {
if result != nil {
result.Close()
}
fmt.Fprintf(os.Stderr, "Issue %s not found\n", id)
continue
}
issue := result.Issue
issueStore := result.Store
if err := validateIssueUpdatable(id, issue); err != nil { if err := validateIssueUpdatable(id, issue); err != nil {
fmt.Fprintf(os.Stderr, "%s\n", err) fmt.Fprintf(os.Stderr, "%s\n", err)
result.Close()
continue continue
} }
@@ -449,6 +457,7 @@ create, update, show, or close operation).`,
// Check if already claimed (has non-empty assignee) // Check if already claimed (has non-empty assignee)
if issue.Assignee != "" { if issue.Assignee != "" {
fmt.Fprintf(os.Stderr, "Error claiming %s: already claimed by %s\n", id, issue.Assignee) fmt.Fprintf(os.Stderr, "Error claiming %s: already claimed by %s\n", id, issue.Assignee)
result.Close()
continue continue
} }
// Atomically set assignee and status // Atomically set assignee and status
@@ -456,8 +465,9 @@ create, update, show, or close operation).`,
"assignee": actor, "assignee": actor,
"status": "in_progress", "status": "in_progress",
} }
if err := store.UpdateIssue(ctx, id, claimUpdates, actor); err != nil { if err := issueStore.UpdateIssue(ctx, result.ResolvedID, claimUpdates, actor); err != nil {
fmt.Fprintf(os.Stderr, "Error claiming %s: %v\n", id, err) fmt.Fprintf(os.Stderr, "Error claiming %s: %v\n", id, err)
result.Close()
continue continue
} }
} }
@@ -470,8 +480,9 @@ create, update, show, or close operation).`,
} }
} }
if len(regularUpdates) > 0 { if len(regularUpdates) > 0 {
if err := store.UpdateIssue(ctx, id, regularUpdates, actor); err != nil { if err := issueStore.UpdateIssue(ctx, result.ResolvedID, regularUpdates, actor); err != nil {
fmt.Fprintf(os.Stderr, "Error updating %s: %v\n", id, err) fmt.Fprintf(os.Stderr, "Error updating %s: %v\n", id, err)
result.Close()
continue continue
} }
} }
@@ -488,8 +499,9 @@ create, update, show, or close operation).`,
removeLabels = v removeLabels = v
} }
if len(setLabels) > 0 || len(addLabels) > 0 || len(removeLabels) > 0 { if len(setLabels) > 0 || len(addLabels) > 0 || len(removeLabels) > 0 {
if err := applyLabelUpdates(ctx, store, id, actor, setLabels, addLabels, removeLabels); err != nil { if err := applyLabelUpdates(ctx, issueStore, result.ResolvedID, actor, setLabels, addLabels, removeLabels); err != nil {
fmt.Fprintf(os.Stderr, "Error updating labels for %s: %v\n", id, err) fmt.Fprintf(os.Stderr, "Error updating labels for %s: %v\n", id, err)
result.Close()
continue continue
} }
} }
@@ -498,26 +510,29 @@ create, update, show, or close operation).`,
if newParent, ok := updates["parent"].(string); ok { if newParent, ok := updates["parent"].(string); ok {
// Validate new parent exists (unless empty string to remove parent) // Validate new parent exists (unless empty string to remove parent)
if newParent != "" { if newParent != "" {
parentIssue, err := store.GetIssue(ctx, newParent) parentIssue, err := issueStore.GetIssue(ctx, newParent)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error getting parent %s: %v\n", newParent, err) fmt.Fprintf(os.Stderr, "Error getting parent %s: %v\n", newParent, err)
result.Close()
continue continue
} }
if parentIssue == nil { if parentIssue == nil {
fmt.Fprintf(os.Stderr, "Error: parent issue %s not found\n", newParent) fmt.Fprintf(os.Stderr, "Error: parent issue %s not found\n", newParent)
result.Close()
continue continue
} }
} }
// Find and remove existing parent-child dependency // Find and remove existing parent-child dependency
deps, err := store.GetDependencyRecords(ctx, id) deps, err := issueStore.GetDependencyRecords(ctx, result.ResolvedID)
if err != nil { if err != nil {
fmt.Fprintf(os.Stderr, "Error getting dependencies for %s: %v\n", id, err) fmt.Fprintf(os.Stderr, "Error getting dependencies for %s: %v\n", id, err)
result.Close()
continue continue
} }
for _, dep := range deps { for _, dep := range deps {
if dep.Type == types.DepParentChild { if dep.Type == types.DepParentChild {
if err := store.RemoveDependency(ctx, id, dep.DependsOnID, actor); err != nil { if err := issueStore.RemoveDependency(ctx, result.ResolvedID, dep.DependsOnID, actor); err != nil {
fmt.Fprintf(os.Stderr, "Error removing old parent dependency: %v\n", err) fmt.Fprintf(os.Stderr, "Error removing old parent dependency: %v\n", err)
} }
break break
@@ -527,19 +542,20 @@ create, update, show, or close operation).`,
// Add new parent-child dependency (if not removing parent) // Add new parent-child dependency (if not removing parent)
if newParent != "" { if newParent != "" {
newDep := &types.Dependency{ newDep := &types.Dependency{
IssueID: id, IssueID: result.ResolvedID,
DependsOnID: newParent, DependsOnID: newParent,
Type: types.DepParentChild, Type: types.DepParentChild,
} }
if err := store.AddDependency(ctx, newDep, actor); err != nil { if err := issueStore.AddDependency(ctx, newDep, actor); err != nil {
fmt.Fprintf(os.Stderr, "Error adding parent dependency: %v\n", err) fmt.Fprintf(os.Stderr, "Error adding parent dependency: %v\n", err)
result.Close()
continue continue
} }
} }
} }
// Run update hook // Run update hook
updatedIssue, _ := store.GetIssue(ctx, id) updatedIssue, _ := issueStore.GetIssue(ctx, result.ResolvedID)
if updatedIssue != nil && hookRunner != nil { if updatedIssue != nil && hookRunner != nil {
hookRunner.Run(hooks.EventUpdate, updatedIssue) hookRunner.Run(hooks.EventUpdate, updatedIssue)
} }
@@ -549,13 +565,14 @@ create, update, show, or close operation).`,
updatedIssues = append(updatedIssues, updatedIssue) updatedIssues = append(updatedIssues, updatedIssue)
} }
} else { } else {
fmt.Printf("%s Updated issue: %s\n", ui.RenderPass("✓"), id) fmt.Printf("%s Updated issue: %s\n", ui.RenderPass("✓"), result.ResolvedID)
} }
// Track first successful update for last-touched // Track first successful update for last-touched
if firstUpdatedID == "" { if firstUpdatedID == "" {
firstUpdatedID = id firstUpdatedID = result.ResolvedID
} }
result.Close()
} }
// Set last touched after all updates complete // Set last touched after all updates complete