diff --git a/backend/plugins/azuredevops_go/tasks/shared.go b/backend/plugins/azuredevops_go/tasks/shared.go index e6fe0eca1e2..318850077e8 100644 --- a/backend/plugins/azuredevops_go/tasks/shared.go +++ b/backend/plugins/azuredevops_go/tasks/shared.go @@ -153,8 +153,11 @@ func change203To401(res *http.Response) errors.Error { // that failed due to a YAML syntax error never produce a usable timeline), instead // of aborting the entire subtask. func ignoreInvalidTimelineResponse(res *http.Response) errors.Error { - // Keep existing behaviour: treat 404 as a graceful skip (build was deleted). - if res.StatusCode == http.StatusNotFound { + // Treat 404 (build deleted) and 204 (build has no timeline data) as + // graceful skips so the subtask continues instead of failing. + // The 204 guard must come before the body is read because a 204 response + // has an empty body by definition and would cause the JSON parser to fail. + if res.StatusCode == http.StatusNotFound || res.StatusCode == http.StatusNoContent { return api.ErrIgnoreAndContinue } diff --git a/backend/plugins/azuredevops_go/tasks/shared_test.go b/backend/plugins/azuredevops_go/tasks/shared_test.go index 353ae3f4c91..d46e01d6c0d 100644 --- a/backend/plugins/azuredevops_go/tasks/shared_test.go +++ b/backend/plugins/azuredevops_go/tasks/shared_test.go @@ -41,6 +41,12 @@ func TestIgnoreInvalidTimelineResponse_404(t *testing.T) { assert.Equal(t, api.ErrIgnoreAndContinue, err, "404 should return ErrIgnoreAndContinue") } +func TestIgnoreInvalidTimelineResponse_204(t *testing.T) { + res := makeResponse(http.StatusNoContent, "") + err := ignoreInvalidTimelineResponse(res) + assert.Equal(t, api.ErrIgnoreAndContinue, err, "204 No Content should return ErrIgnoreAndContinue") +} + func TestIgnoreInvalidTimelineResponse_EmptyBody(t *testing.T) { res := makeResponse(http.StatusOK, "") err := ignoreInvalidTimelineResponse(res)