andygrunwald / andygrunwald/go-jira
Nil pointer panic in GetWatchersWithContext when watchers lack AccountID
- Dominant language
- Go
- Stars
- 1.6k
- Forks
- 500
- PR merge metrics
- No merged PRs in 30d
Description
## 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
```go
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:
```go
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
```go
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
```go
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: https://github.com/andygrunwald/go-jira/issues/272
Contributor guide
No contributing guide indexed for this repository
Assessment
This issue has not been assessed yet.