From 6408ef65f4a3fc82fc17fd51c6cb0fefd53f3c87 Mon Sep 17 00:00:00 2001 From: vector-sigma Date: Fri, 7 Nov 2025 17:21:14 -0500 Subject: [PATCH] Fix #249: Add nil storage checks to prevent RPC daemon crashes (#250) The daemon RPC server was crashing with a nil pointer dereference when the global daemon received list, ready, stats, or other storage-dependent RPC requests. The global daemon is created with nil storage, causing these operations to panic when they attempted to access storage methods. This fix adds defensive nil checks at the beginning of all RPC handlers that require storage access. When storage is unavailable, they now return a proper JSON error response instead of crashing the daemon. The error message also informs users that the global daemon is deprecated and they should use local daemons instead. Handlers fixed: - handleCreate, handleUpdate, handleClose - handleList, handleShow, handleReady, handleStale - handleResolveID, handleStats, handleEpicStatus - handleCompact, handleCompactStats - handleDepAdd (and via handleSimpleStoreOp for all label/dep/comment ops) Co-authored-by: Test User --- internal/rpc/server_compact.go | 12 ++++ internal/rpc/server_issues_epics.go | 61 +++++++++++++++++++++ internal/rpc/server_labels_deps_comments.go | 12 ++++ 3 files changed, 85 insertions(+) diff --git a/internal/rpc/server_compact.go b/internal/rpc/server_compact.go index e1b8411f..864fb630 100644 --- a/internal/rpc/server_compact.go +++ b/internal/rpc/server_compact.go @@ -19,6 +19,12 @@ func (s *Server) handleCompact(req *Request) Response { } store := s.storage + if store == nil { + return Response{ + Success: false, + Error: "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)", + } + } sqliteStore, ok := store.(*sqlite.SQLiteStorage) if !ok { @@ -228,6 +234,12 @@ func (s *Server) handleCompactStats(req *Request) Response { } store := s.storage + if store == nil { + return Response{ + Success: false, + Error: "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)", + } + } sqliteStore, ok := store.(*sqlite.SQLiteStorage) if !ok { diff --git a/internal/rpc/server_issues_epics.go b/internal/rpc/server_issues_epics.go index 5248cb04..e7a17e95 100644 --- a/internal/rpc/server_issues_epics.go +++ b/internal/rpc/server_issues_epics.go @@ -87,6 +87,12 @@ func (s *Server) handleCreate(req *Request) Response { } store := s.storage + if store == nil { + return Response{ + Success: false, + Error: "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)", + } + } ctx := s.reqCtx(req) // If parent is specified, generate child ID @@ -242,6 +248,12 @@ func (s *Server) handleUpdate(req *Request) Response { } store := s.storage + if store == nil { + return Response{ + Success: false, + Error: "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)", + } + } ctx := s.reqCtx(req) updates := updatesFromArgs(updateArgs) @@ -284,6 +296,12 @@ func (s *Server) handleClose(req *Request) Response { } store := s.storage + if store == nil { + return Response{ + Success: false, + Error: "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)", + } + } ctx := s.reqCtx(req) if err := store.CloseIssue(ctx, closeArgs.ID, closeArgs.Reason, s.reqActor(req)); err != nil { @@ -314,6 +332,12 @@ func (s *Server) handleList(req *Request) Response { } store := s.storage + if store == nil { + return Response{ + Success: false, + Error: "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)", + } + } filter := types.IssueFilter{ Limit: listArgs.Limit, @@ -492,6 +516,13 @@ func (s *Server) handleResolveID(req *Request) Response { } } + if s.storage == nil { + return Response{ + Success: false, + Error: "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)", + } + } + ctx := s.reqCtx(req) resolvedID, err := utils.ResolvePartialID(ctx, s.storage, args.ID) if err != nil { @@ -518,6 +549,12 @@ func (s *Server) handleShow(req *Request) Response { } store := s.storage + if store == nil { + return Response{ + Success: false, + Error: "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)", + } + } ctx := s.reqCtx(req) issue, err := store.GetIssue(ctx, showArgs.ID) @@ -593,6 +630,12 @@ func (s *Server) handleReady(req *Request) Response { } store := s.storage + if store == nil { + return Response{ + Success: false, + Error: "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)", + } + } wf := types.WorkFilter{ Status: types.StatusOpen, @@ -632,6 +675,12 @@ func (s *Server) handleStale(req *Request) Response { } store := s.storage + if store == nil { + return Response{ + Success: false, + Error: "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)", + } + } filter := types.StaleFilter{ Days: staleArgs.Days, @@ -657,6 +706,12 @@ func (s *Server) handleStale(req *Request) Response { func (s *Server) handleStats(req *Request) Response { store := s.storage + if store == nil { + return Response{ + Success: false, + Error: "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)", + } + } ctx := s.reqCtx(req) stats, err := store.GetStatistics(ctx) @@ -684,6 +739,12 @@ func (s *Server) handleEpicStatus(req *Request) Response { } store := s.storage + if store == nil { + return Response{ + Success: false, + Error: "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)", + } + } ctx := s.reqCtx(req) epics, err := store.GetEpicsEligibleForClosure(ctx) diff --git a/internal/rpc/server_labels_deps_comments.go b/internal/rpc/server_labels_deps_comments.go index 345ed280..e48f90ef 100644 --- a/internal/rpc/server_labels_deps_comments.go +++ b/internal/rpc/server_labels_deps_comments.go @@ -19,6 +19,12 @@ func (s *Server) handleDepAdd(req *Request) Response { } store := s.storage + if store == nil { + return Response{ + Success: false, + Error: "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)", + } + } dep := &types.Dependency{ IssueID: depArgs.FromID, @@ -51,6 +57,12 @@ func (s *Server) handleSimpleStoreOp(req *Request, argsPtr interface{}, argDesc } store := s.storage + if store == nil { + return Response{ + Success: false, + Error: "storage not available (global daemon deprecated - use local daemon instead with 'bd daemon' in your project)", + } + } ctx := s.reqCtx(req) if err := opFunc(ctx, store, s.reqActor(req)); err != nil {