repo_description: Beads - A memory upgrade for your coding agent
repo_topics: agents claude-code coding
code: package main
  
  import (
  	"fmt"
  	"os"
  	"time"
  
  	"github.com/spf13/cobra"
  	"github.com/steveyegge/beads/internal/timeparsing"
  	"github.com/steveyegge/beads/internal/types"
  	"github.com/steveyegge/beads/internal/ui"
  	"github.com/steveyegge/beads/internal/utils"
  )
  
  var deferCmd = &cobra.Command{
  	Use:   "defer [id...]",
  	Short: "Defer one or more issues for later",
  	Long: `Defer issues to put them on ice for later.
  
  Deferred issues are deliberately set aside - not blocked by anything specific,
  just postponed for future consideration. Unlike blocked issues, there's no
  dependency keeping them from being worked. Unlike closed issues, they will
  be revisited.
  
  Deferred issues don't show in 'bd ready' but remain visible in 'bd list'.
  
  Examples:
    bd defer bd-abc                  # Defer a single issue (status-based)
    bd defer bd-abc --until=tomorrow # Defer until specific time
    bd defer bd-abc bd-def           # Defer multiple issues`,
  	Args: cobra.MinimumNArgs(1),
  	Run: func(cmd *cobra.Command, args []string) {
  		CheckReadonly("defer")
  
  		// Parse --until flag (GH#820)
  		var deferUntil *time.Time
  		untilStr, _ := cmd.Flags().GetString("until")
  		if untilStr != "" {
  			t, err := timeparsing.ParseRelativeTime(untilStr, time.Now())
  			if err != nil {
  				FatalError("invalid --until format %q. Examples: +1h, tomorrow, next monday, 2025-01-15", untilStr)
  			}
  			// Warn if defer date is in the past (user probably meant future)
  			if t.Before(time.Now()) && !jsonOutput {
  				fmt.Fprintf(os.Stderr, "%s Defer date %q is in the past. Issue will appear in bd ready immediately.\n",
  					ui.RenderWarn("!"), t.Format("2006-01-02 15:04"))
  				fmt.Fprintf(os.Stderr, "  Did you mean a future date? Use --until=+1h or --until=tomorrow\n")
  			}
  			deferUntil = &t
  		}
  
  		ctx := rootCtx
  
  		// Resolve partial IDs
  		_, err := utils.ResolvePartialIDs(ctx, store, args)
  		if err != nil {
  			FatalError("%v", err)
  		}
  
  		deferredIssues := []*types.Issue{}
  
  		// Direct storage access
  		if store == nil {
  			FatalErrorWithHint("database not initialized",
  				diagHint())
  		}
  
  		for _, id := range args {
  			fullID, err := utils.ResolvePartialID(ctx, store, id)
  			if err != nil {
  				fmt.Fprintf(os.Stderr, "Error resolving %s: %v\n", id, err)
  				continue
  			}
  
  			updates := map[string]interface{}{
  				"status": string(types.StatusDeferred),
  			}
  			// Add defer_until if --until specified (GH#820)
  			if deferUntil != nil {
  				updates["defer_until"] = *deferUntil
  			}
  
  			if err := store.UpdateIssue(ctx, fullID, updates, actor); err != nil {
  				fmt.Fprintf(os.Stderr, "Error deferring %s: %v\n", fullID, err)
  				continue
  			}
  
  			if jsonOutput {
  				issue, _ := store.GetIssue(ctx, fullID)
  				if issue != nil {
  					deferredIssues = append(deferredIssues, issue)
  				}
  			} else {
  				fmt.Printf("%s Deferred %s\n", ui.RenderAccent("*"), fullID)
  			}
  		}
  
  		if jsonOutput && len(deferredIssues) > 0 {
  			outputJSON(deferredIssues)
  		}
  
  		// Embedded mode: flush Dolt commit.
  		if isEmbeddedMode() && len(args) > 0 && store != nil {
  			if _, err := store.CommitPending(ctx, actor); err != nil {
  				FatalError("failed to commit: %v", err)
  			}
  		}
  	},
  }
  
  func init() {
  	// Time-based scheduling flag (GH#820)
  	deferCmd.Flags().String("until", "", "Defer until specific time (e.g., +1h, tomorrow, next monday)")
  	deferCmd.ValidArgsFunction = issueIDCompletion
  	rootCmd.AddCommand(deferCmd)
  }
  