Skip to content

Commit 2e9cca0

Browse files
authored
fix(gh-copilot): skip empty report responses for dates without Copilot data (#8804)
GitHub's Copilot Usage Metrics Reports API returns HTTP 200 with body "" for dates before usage data was available, instead of returning a 404. The existing ignore404 callback does not catch this, so the ResponseParser attempts json.Unmarshal on "" which fails and crashes the collector pipeline. Add an isEmptyReport guard that detects empty/null bodies and returns nil so the collector silently skips those days. Fixes #8789
1 parent 17e3ea4 commit 2e9cca0

5 files changed

Lines changed: 38 additions & 1 deletion

File tree

backend/plugins/gh-copilot/tasks/enterprise_metrics_collector.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,12 +95,14 @@ func CollectEnterpriseMetrics(taskCtx plugin.SubTaskContext) errors.Error {
9595
Concurrency: 1,
9696
AfterResponse: ignore404,
9797
ResponseParser: func(res *http.Response) ([]json.RawMessage, errors.Error) {
98-
// Parse metadata response to get download links
9998
body, readErr := io.ReadAll(res.Body)
10099
res.Body.Close()
101100
if readErr != nil {
102101
return nil, errors.Default.Wrap(readErr, "failed to read report metadata")
103102
}
103+
if isEmptyReport(body) {
104+
return nil, nil
105+
}
104106

105107
var meta reportMetadataResponse
106108
if jsonErr := json.Unmarshal(body, &meta); jsonErr != nil {

backend/plugins/gh-copilot/tasks/metrics_collector_test.go

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -67,3 +67,24 @@ func TestComputeReportDateRangeClampsFutureSince(t *testing.T) {
6767
require.Equal(t, time.Date(2025, 1, 9, 0, 0, 0, 0, time.UTC), until)
6868
require.Equal(t, time.Date(2025, 1, 9, 0, 0, 0, 0, time.UTC), start)
6969
}
70+
71+
func TestIsEmptyReport(t *testing.T) {
72+
tests := []struct {
73+
name string
74+
body []byte
75+
want bool
76+
}{
77+
{"empty JSON string", []byte(`""`), true},
78+
{"null", []byte("null"), true},
79+
{"empty body", []byte{}, true},
80+
{"whitespace only", []byte(" "), true},
81+
{"padded empty string", []byte(` "" `), true},
82+
{"valid metadata", []byte(`{"download_links":["https://example.com/report.json"],"report_day":"2026-03-19"}`), false},
83+
{"valid metadata empty links", []byte(`{"download_links":[],"report_day":"2026-03-19"}`), false},
84+
}
85+
for _, tt := range tests {
86+
t.Run(tt.name, func(t *testing.T) {
87+
require.Equal(t, tt.want, isEmptyReport(tt.body))
88+
})
89+
}
90+
}

backend/plugins/gh-copilot/tasks/org_metrics_collector.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -94,6 +94,9 @@ func CollectOrgMetrics(taskCtx plugin.SubTaskContext) errors.Error {
9494
if readErr != nil {
9595
return nil, errors.Default.Wrap(readErr, "failed to read report metadata")
9696
}
97+
if isEmptyReport(body) {
98+
return nil, nil
99+
}
97100

98101
var meta reportMetadataResponse
99102
if jsonErr := json.Unmarshal(body, &meta); jsonErr != nil {

backend/plugins/gh-copilot/tasks/report_download_helper.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -60,6 +60,14 @@ func ignore404(res *http.Response) errors.Error {
6060
return nil
6161
}
6262

63+
// isEmptyReport returns true when the GitHub API returned an HTTP 200 but the
64+
// body carries no usable report data. For dates before Copilot usage data was
65+
// available the API responds with "" (empty JSON string) instead of a 404.
66+
func isEmptyReport(body []byte) bool {
67+
b := bytes.TrimSpace(body)
68+
return len(b) == 0 || string(b) == `""` || string(b) == "null"
69+
}
70+
6371
// reportMetadataResponse represents the JSON returned by the report metadata endpoints.
6472
type reportMetadataResponse struct {
6573
DownloadLinks []string `json:"download_links"`

backend/plugins/gh-copilot/tasks/user_metrics_collector.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -99,6 +99,9 @@ func CollectUserMetrics(taskCtx plugin.SubTaskContext) errors.Error {
9999
if readErr != nil {
100100
return nil, errors.Default.Wrap(readErr, "failed to read report metadata")
101101
}
102+
if isEmptyReport(body) {
103+
return nil, nil
104+
}
102105

103106
var meta reportMetadataResponse
104107
if jsonErr := json.Unmarshal(body, &meta); jsonErr != nil {

0 commit comments

Comments
 (0)