From 90a4ca8885469dd34832efbcc8f850509ba3514e Mon Sep 17 00:00:00 2001 From: Nevyana Angelova Date: Mon, 13 Apr 2026 16:58:17 +0300 Subject: [PATCH 1/2] Add admin API token fallback for watcher notifications --- server/issue.go | 63 ++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/server/issue.go b/server/issue.go index 9254234e..e3066fde 100644 --- a/server/issue.go +++ b/server/issue.go @@ -1679,15 +1679,24 @@ func (p *Plugin) checkIssueWatchers(wh *webhook, instanceID types.ID) { commentAuthor, jwhook.mdKeySummaryLink(), jwhook.mdIssueType()) } client, connection, err := wh.fetchConnectedUser(p, instanceID) - if err != nil || client == nil { - p.errorf("error while fetching connected users for the instanceID %v , Error : %v", instanceID, err) - return - } - watchers, err := client.GetWatchers(instanceID.String(), wh.Issue.ID, connection) - if err != nil { - p.errorf("error while getting watchers for the issue id %v , err : %v", wh.Issue.ID, err) - return + var watchers *jira.Watches + if err != nil || client == nil { + if p.getConfig().AdminAPIToken == "" { + p.errorf("error while fetching connected users for the instanceID %v and no admin API token configured, Error : %v", instanceID, err) + return + } + watchers, err = p.GetWatchersWithAPIToken(wh.Issue.ID, instanceID.String()) + if err != nil { + p.errorf("error while getting watchers with admin API token for issue id %v , err : %v", wh.Issue.ID, err) + return + } + } else { + watchers, err = client.GetWatchers(instanceID.String(), wh.Issue.ID, connection) + if err != nil { + p.errorf("error while getting watchers for the issue id %v , err : %v", wh.Issue.ID, err) + return + } } authorVal := jwhook.Comment.UpdateAuthor @@ -1894,6 +1903,44 @@ func (p *Plugin) GetProjectListWithAPIToken(instanceID string) (*jira.ProjectLis return &projectResponse.Values, nil } +func (p *Plugin) GetWatchersWithAPIToken(issueID, instanceID string) (*jira.Watches, error) { + httpClient := &http.Client{} + req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/rest/api/2/issue/%s/watchers", instanceID, issueID), nil) + if err != nil { + return nil, errors.Wrapf(err, "failed to create http request for fetching watchers. IssueID: %s", issueID) + } + + err = p.SetAdminAPITokenRequestHeader(req) + if err != nil { + return nil, err + } + + resp, err := httpClient.Do(req) + if err != nil { + return nil, errors.Wrapf(err, "failed to get watchers. IssueID: %s", issueID) + } + if resp == nil || resp.Body == nil { + return nil, errors.Errorf("missing response for watchers. IssueID: %s", issueID) + } + defer resp.Body.Close() + + if resp.StatusCode != http.StatusOK { + return nil, errors.Errorf("unexpected status code %d while fetching watchers. IssueID: %s", resp.StatusCode, issueID) + } + + body, err := io.ReadAll(resp.Body) + if err != nil { + return nil, errors.Wrapf(err, "failed to read watchers response. IssueID: %s", issueID) + } + + var watchers jira.Watches + if err = json.Unmarshal(body, &watchers); err != nil { + return nil, errors.Wrapf(err, "failed to unmarshal watchers data. IssueID: %s", issueID) + } + + return &watchers, nil +} + func shouldNotifyWatcherUser(watcher jira.Watcher, author *jira.User) bool { if author == nil { return true From a0ade1ce08b8266b9cb2737c92de9bfa7f323ee0 Mon Sep 17 00:00:00 2001 From: Nevyana Angelova Date: Mon, 13 Apr 2026 17:08:36 +0300 Subject: [PATCH 2/2] Separate hard errors from no-connected-user fallback and add HTTP client timeout - Only fall back to admin API token when no connected user is found (client == nil), not on hard errors like instance store failures - Add 30s timeout to HTTP client in GetWatchersWithAPIToken to prevent indefinite blocking on unresponsive Jira servers Made-with: Cursor --- server/issue.go | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/server/issue.go b/server/issue.go index e3066fde..f4b1b6b2 100644 --- a/server/issue.go +++ b/server/issue.go @@ -16,6 +16,7 @@ import ( "strconv" "strings" "sync" + "time" jira "github.com/andygrunwald/go-jira" "github.com/pkg/errors" @@ -1679,11 +1680,15 @@ func (p *Plugin) checkIssueWatchers(wh *webhook, instanceID types.ID) { commentAuthor, jwhook.mdKeySummaryLink(), jwhook.mdIssueType()) } client, connection, err := wh.fetchConnectedUser(p, instanceID) + if err != nil { + p.errorf("error while fetching connected users for the instanceID %v, Error: %v", instanceID, err) + return + } var watchers *jira.Watches - if err != nil || client == nil { + if client == nil { if p.getConfig().AdminAPIToken == "" { - p.errorf("error while fetching connected users for the instanceID %v and no admin API token configured, Error : %v", instanceID, err) + p.errorf("no connected user found for instanceID %v and no admin API token configured", instanceID) return } watchers, err = p.GetWatchersWithAPIToken(wh.Issue.ID, instanceID.String()) @@ -1904,7 +1909,7 @@ func (p *Plugin) GetProjectListWithAPIToken(instanceID string) (*jira.ProjectLis } func (p *Plugin) GetWatchersWithAPIToken(issueID, instanceID string) (*jira.Watches, error) { - httpClient := &http.Client{} + httpClient := &http.Client{Timeout: 30 * time.Second} req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/rest/api/2/issue/%s/watchers", instanceID, issueID), nil) if err != nil { return nil, errors.Wrapf(err, "failed to create http request for fetching watchers. IssueID: %s", issueID)