Skip to content

Nil pointer panic in GetWatchersWithContext when watchers lack AccountID #743

@hrez

Description

@hrez

Description

IssueService.GetWatchersWithContext() panics with a nil pointer dereference when processing watchers that don't have an AccountID field. This commonly occurs with legacy Jira Server installations or watchers migrated from older Jira versions.

Version

  • go-jira version: v1.17.0 (bug also exists in v1.12.0)
  • Go version: go1.25.4
  • Jira: Cloud/Server (any version with legacy watchers)

Steps to Reproduce

  1. Call GetWatchersWithContext() on an issue that has watchers without AccountID
  2. Observe panic

Minimal Reproducible Example

package main

import (
    "context"
    "encoding/json"
    "net/http"
    "net/http/httptest"

    jira "github.com/andygrunwald/go-jira"
)

func main() {
    // Mock server returns watcher without AccountID (legacy format)
    server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
        if r.URL.Path == "/rest/api/2/issue/TEST-123/watchers" {
            response := map[string]interface{}{
                "self":       "https://example.atlassian.net/rest/api/2/issue/TEST-123/watchers",
                "watchCount": 1,
                "watchers": []map[string]interface{}{
                    {
                        "name":        "olduser",
                        "displayName": "Old User",
                        "active":      true,
                        // No accountId field
                    },
                },
            }
            json.NewEncoder(w).Encode(response)
            return
        }
        http.NotFound(w, r)
    }))
    defer server.Close()

    client, _ := jira.NewClient(server.Client(), server.URL)

    // This panics:
    _, _, _ = client.Issue.GetWatchersWithContext(context.Background(), "TEST-123")
}

Expected Behavior

The function should either:

  • Skip watchers without AccountID gracefully, or
  • Return an error, or
  • Return the watcher data available (name, displayName)

Actual Behavior

panic: runtime error: invalid memory address or nil pointer dereference
[signal SIGSEGV: segmentation violation code=0x1 addr=0x0 pc=0x756eb3]

goroutine 1 [running]:
github.com/andygrunwald/go-jira.(*IssueService).GetWatchersWithContext(...)
    .../go-jira@v1.17.0/issue.go:1575

Root Cause

In issue.go at line ~1575:

func (s *IssueService) GetWatchersWithContext(ctx context.Context, issueID string) (*[]User, *Response, error) {
    // ...
    result := []User{}
    for _, watcher := range watches.Watchers {
        var user *User
        if watcher.AccountID != "" {
            user, resp, err = s.client.User.GetByAccountID(watcher.AccountID)
            if err != nil {
                return nil, resp, NewJiraError(resp, err)
            }
        }
        result = append(result, *user)  // ← PANIC: user is nil if AccountID is empty
    }
    return &result, resp, nil
}

When watcher.AccountID is empty, user remains nil, and dereferencing *user causes a panic.

Suggested Fix

Option 1: Skip watchers without AccountID

for _, watcher := range watches.Watchers {
    if watcher.AccountID == "" {
        continue  // Skip legacy watchers
    }
    user, resp, err := s.client.User.GetByAccountID(watcher.AccountID)
    if err != nil {
        return nil, resp, NewJiraError(resp, err)
    }
    if user != nil {
        result = append(result, *user)
    }
}

Option 2: Handle both legacy and modern formats

for _, watcher := range watches.Watchers {
    var user *User
    var err error

    if watcher.AccountID != "" {
        user, resp, err = s.client.User.GetByAccountID(watcher.AccountID)
        if err != nil {
            return nil, resp, NewJiraError(resp, err)
        }
    } else if watcher.Name != "" {
        // Fallback for legacy watchers - use watcher data directly
        user = &User{
            Name:        watcher.Name,
            DisplayName: watcher.DisplayName,
            Active:      watcher.Active,
        }
    }

    if user != nil {
        result = append(result, *user)
    }
}

Additional Context

This issue affects environments with:

  • Legacy Jira Server installations
  • Migrated Jira Cloud instances with old watchers
  • Any scenario where watchers were added before AccountID became mandatory

Probably related: #272

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions