fix: bd update supports cross-rig bead updates via prefix routing (gt-wq1wb)
The bd update command now checks needsRouting() before attempting daemon RPC resolution. When an issue ID (like hq-eggh5) routes to a different beads directory, the update bypasses the daemon and uses direct mode with the routed storage. This enables polecats in gastown to update HQ beads (hq-* prefix) and vice versa. The fix mirrors the routing pattern already used by bd show. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
committed by
Steve Yegge
parent
eb0cc50ce6
commit
628ab911a0
1758
.beads/issues.jsonl
1758
.beads/issues.jsonl
File diff suppressed because one or more lines are too long
107
cmd/bd/update.go
107
cmd/bd/update.go
@@ -143,10 +143,17 @@ create, update, show, or close operation).`,
|
|||||||
|
|
||||||
ctx := rootCtx
|
ctx := rootCtx
|
||||||
|
|
||||||
// Resolve partial IDs first
|
// Resolve partial IDs first, checking for cross-rig routing
|
||||||
var resolvedIDs []string
|
var resolvedIDs []string
|
||||||
|
var routedArgs []string // IDs that need cross-repo routing (bypass daemon)
|
||||||
if daemonClient != nil {
|
if daemonClient != nil {
|
||||||
|
// In daemon mode, resolve via RPC - but check routing first
|
||||||
for _, id := range args {
|
for _, id := range args {
|
||||||
|
// Check if this ID needs routing to a different beads directory
|
||||||
|
if needsRouting(id) {
|
||||||
|
routedArgs = append(routedArgs, id)
|
||||||
|
continue
|
||||||
|
}
|
||||||
resolveArgs := &rpc.ResolveIDArgs{ID: id}
|
resolveArgs := &rpc.ResolveIDArgs{ID: id}
|
||||||
resp, err := daemonClient.ResolveID(resolveArgs)
|
resp, err := daemonClient.ResolveID(resolveArgs)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -252,6 +259,104 @@ create, update, show, or close operation).`,
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Handle routed IDs via direct mode (bypass daemon)
|
||||||
|
for _, id := range routedArgs {
|
||||||
|
result, err := resolveAndGetIssueWithRouting(ctx, store, id)
|
||||||
|
if err != nil {
|
||||||
|
if result != nil {
|
||||||
|
result.Close()
|
||||||
|
}
|
||||||
|
fmt.Fprintf(os.Stderr, "Error resolving %s: %v\n", id, err)
|
||||||
|
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 {
|
||||||
|
fmt.Fprintf(os.Stderr, "%s\n", err)
|
||||||
|
result.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle claim operation atomically
|
||||||
|
if claimFlag {
|
||||||
|
if issue.Assignee != "" {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error claiming %s: already claimed by %s\n", id, issue.Assignee)
|
||||||
|
result.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
claimUpdates := map[string]interface{}{
|
||||||
|
"assignee": actor,
|
||||||
|
"status": "in_progress",
|
||||||
|
}
|
||||||
|
if err := issueStore.UpdateIssue(ctx, result.ResolvedID, claimUpdates, actor); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error claiming %s: %v\n", id, err)
|
||||||
|
result.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply regular field updates if any
|
||||||
|
regularUpdates := make(map[string]interface{})
|
||||||
|
for k, v := range updates {
|
||||||
|
if k != "add_labels" && k != "remove_labels" && k != "set_labels" && k != "parent" {
|
||||||
|
regularUpdates[k] = v
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if len(regularUpdates) > 0 {
|
||||||
|
if err := issueStore.UpdateIssue(ctx, result.ResolvedID, regularUpdates, actor); err != nil {
|
||||||
|
fmt.Fprintf(os.Stderr, "Error updating %s: %v\n", id, err)
|
||||||
|
result.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle label operations
|
||||||
|
var setLabels, addLabels, removeLabels []string
|
||||||
|
if v, ok := updates["set_labels"].([]string); ok {
|
||||||
|
setLabels = v
|
||||||
|
}
|
||||||
|
if v, ok := updates["add_labels"].([]string); ok {
|
||||||
|
addLabels = v
|
||||||
|
}
|
||||||
|
if v, ok := updates["remove_labels"].([]string); ok {
|
||||||
|
removeLabels = v
|
||||||
|
}
|
||||||
|
if len(setLabels) > 0 || len(addLabels) > 0 || len(removeLabels) > 0 {
|
||||||
|
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)
|
||||||
|
result.Close()
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Run update hook
|
||||||
|
updatedIssue, _ := issueStore.GetIssue(ctx, result.ResolvedID)
|
||||||
|
if updatedIssue != nil && hookRunner != nil {
|
||||||
|
hookRunner.Run(hooks.EventUpdate, updatedIssue)
|
||||||
|
}
|
||||||
|
|
||||||
|
if jsonOutput {
|
||||||
|
if updatedIssue != nil {
|
||||||
|
updatedIssues = append(updatedIssues, updatedIssue)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
fmt.Printf("%s Updated issue: %s\n", ui.RenderPass("✓"), result.ResolvedID)
|
||||||
|
}
|
||||||
|
|
||||||
|
if firstUpdatedID == "" {
|
||||||
|
firstUpdatedID = result.ResolvedID
|
||||||
|
}
|
||||||
|
result.Close()
|
||||||
|
}
|
||||||
|
|
||||||
if jsonOutput && len(updatedIssues) > 0 {
|
if jsonOutput && len(updatedIssues) > 0 {
|
||||||
outputJSON(updatedIssues)
|
outputJSON(updatedIssues)
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user