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 <test@example.com>
This commit is contained in:
vector-sigma
2025-11-07 17:21:14 -05:00
committed by GitHub
parent 64742cd574
commit 6408ef65f4
3 changed files with 85 additions and 0 deletions

View File

@@ -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 {

View File

@@ -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)

View File

@@ -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 {