{"id":"b51eafd63aed9d3e","repo":"square/okhttp","slug":"unexpected-code-b51eaf","errorCode":null,"errorMessage":"Unexpected code ","messagePattern":"Unexpected code ","errorType":"exception","errorClass":"IOException","httpStatus":null,"severity":"error","filePath":"samples/guide/src/main/java/okhttp3/recipes/RewriteResponseCacheControl.java","lineNumber":68,"sourceCode":"      Request request = new Request.Builder()\n          .url(\"https://api.github.com/search/repositories?q=http\")\n          .build();\n\n      OkHttpClient clientForCall;\n      if (i == 2) {\n        // Force this request's response to be written to the cache. This way, subsequent responses\n        // can be read from the cache.\n        System.out.println(\"Force cache: true\");\n        clientForCall = client.newBuilder()\n            .addNetworkInterceptor(REWRITE_CACHE_CONTROL_INTERCEPTOR)\n            .build();\n      } else {\n        System.out.println(\"Force cache: false\");\n        clientForCall = client;\n      }\n\n      try (Response response = clientForCall.newCall(request).execute()) {\n        if (!response.isSuccessful()) throw new IOException(\"Unexpected code \" + response);\n\n        System.out.println(\"    Network: \" + (response.networkResponse() != null));\n        System.out.println();\n      }\n    }\n  }\n\n  public static void main(String... args) throws Exception {\n    new RewriteResponseCacheControl(new File(\"RewriteResponseCacheControl.tmp\")).run();\n  }\n}\n","sourceCodeStart":50,"sourceCodeEnd":80,"githubUrl":"https://github.com/square/okhttp/blob/4fc083138014aba3d0078f5c26d1ce84815fa984/samples/guide/src/main/java/okhttp3/recipes/RewriteResponseCacheControl.java#L50-L80","documentation":"RewriteResponseCacheControl.java:68 uses the same isSuccessful() guard against a call to https://api.github.com/search/repositories?q=http. The IOException 'Unexpected code ' surfaces the Response toString, hiding the real GitHub status code. Notably, the recipe's REWRITE_CACHE_CONTROL_INTERCEPTOR (lines 28-33) is documented as 'dangerous' and overwrites the server's Cache-Control with 'max-age=60', so a stale or error response can be served from cache as a 200 on later iterations, while the first un-cached hit can surface the original non-2xx.","triggerScenarios":"Hitting GitHub's /search/repositories without an Authorization header — GitHub returns 403 with rate-limit headers for unauthenticated search (10 requests/minute). Iteration i==2 (line 55) adds the network interceptor that rewrites Cache-Control; subsequent iterations read from cache, masking transient upstream 5xx errors as cached 200s or surfacing them only on the seeding call. A corporate proxy returning an HTML block page (status 407/502) also trips this.","commonSituations":"Running the recipe in a loop exceeding the unauthenticated search rate limit; sharing a network/IP with CI runners that exhaust the GitHub quota; the 'dangerous' cache-control rewrite producing stale data that downstream code misreads; misconfigured OkHttpClient.cache directory (lines 37-39) causing evictions/permission errors.","solutions":["Add an Authorization header (e.g. a GitHub token via .header(\"Authorization\", \"token <gpa_token>\")) to lift search to 30 req/min and avoid 403.","Log response.code(), response.headers().get(\"X-RateLimit-Remaining\"), and response.body().string() before throwing to confirm rate-limit vs. real upstream error.","Remove or scope REWRITE_CACHE_CONTROL_INTERCEPTOR — it is explicitly labelled dangerous; do not ship cache-control rewriting in production code.","Verify the cache directory (new File(\"RewriteResponseCacheControl.tmp\")) is writable and unique per test run to avoid cross-run cache poisoning."],"exampleFix":"// before\ntry (Response response = clientForCall.newCall(request).execute()) {\n  if (!response.isSuccessful()) throw new IOException(\"Unexpected code \" + response);\n  System.out.println(\"    Network: \" + (response.networkResponse() != null));\n}\n\n// after\ntry (Response response = clientForCall.newCall(request).execute()) {\n  if (!response.isSuccessful()) {\n    throw new IOException(\"GitHub \" + response.code()\n        + \" rateLimit=\" + response.header(\"X-RateLimit-Remaining\"));\n  }\n  System.out.println(\"    Network: \" + (response.networkResponse() != null));\n}","handlingStrategy":"validation","validationCode":"// Check GitHub rate-limit headers cheaply before doing the real search\nRequest probe = new Request.Builder()\n    .url(\"https://api.github.com/rate_limit\")\n    .header(\"Authorization\", \"token \" + token)\n    .build();\ntry (Response r = client.newCall(probe).execute()) {\n  String remaining = r.header(\"X-RateLimit-Remaining\");\n  if (remaining == null || Integer.parseInt(remaining) <= 0) {\n    throw new IllegalStateException(\"GitHub search quota exhausted\");\n  }\n}","typeGuard":null,"tryCatchPattern":"try (Response response = clientForCall.newCall(request).execute()) {\n  if (response.code() == 403 && response.header(\"X-RateLimit-Remaining\") != null) {\n    long reset = Long.parseLong(response.header(\"X-RateLimit-Reset\"));\n    throw new IOException(\"GitHub rate-limited until \" + new java.util.Date(reset * 1000L));\n  }\n  if (!response.isSuccessful()) {\n    throw new IOException(\"GitHub \" + response.code());\n  }\n  process(response);\n}","preventionTips":["Always send an Authorization header for GitHub search to raise the 10/min unauthenticated ceiling.","Do not ship REWRITE_CACHE_CONTROL_INTERCEPTOR — cache-control rewriting masks upstream errors and stale data.","Use a unique cache directory per test run to avoid cross-run cache pollution.","Check X-RateLimit-Remaining on every GitHub call and back off when near zero."],"tags":["okhttp","java","github-api","rate-limiting","caching","http-status"],"analyzedSha":"4fc083138014aba3d0078f5c26d1ce84815fa984","analyzedAt":"2026-08-04T19:09:04.639Z","schemaVersion":2}