@@ -311,6 +311,32 @@ func TestEncoder429ReturnsNonPermanentHTTPStatusError(t *testing.T) {
311311 assert .False (t , statusErr .Permanent (), "429 is transient rate-limiting, not a content rejection" )
312312}
313313
314+ // TestHTTPStatusErrorPermanentClassification pins the skip-vs-abort
315+ // classification: auth statuses describe the caller's credentials, not the
316+ // input, so treating them as permanent would skip-stamp an entire corpus
317+ // on an expired token instead of aborting the build.
318+ func TestHTTPStatusErrorPermanentClassification (t * testing.T ) {
319+ cases := []struct {
320+ status int
321+ permanent bool
322+ }{
323+ {http .StatusBadRequest , true },
324+ {http .StatusNotFound , true },
325+ {http .StatusUnprocessableEntity , true },
326+ {http .StatusUnauthorized , false },
327+ {http .StatusForbidden , false },
328+ {http .StatusProxyAuthRequired , false },
329+ {http .StatusTooManyRequests , false },
330+ {http .StatusInternalServerError , false },
331+ {http .StatusBadGateway , false },
332+ }
333+ for _ , tc := range cases {
334+ err := & HTTPStatusError {Status : tc .status }
335+ assert .Equalf (t , tc .permanent , err .Permanent (),
336+ "status %d: Permanent() classification" , tc .status )
337+ }
338+ }
339+
314340// TestEncoderDecodeErrorIsRetried covers fix 2: a decoding failure almost
315341// always means the connection died mid-stream, not that the endpoint sent
316342// a deliberately malformed response, so it must be retried rather than
0 commit comments