{"record":{"id":"39aea976d716b18a","repo":"alibaba/nacos","slug":"304","errorCode":"304","errorMessage":"not modified","messagePattern":"not modified","errorType":"exception","errorClass":"NacosException","httpStatus":null,"severity":"info","filePath":"client/src/main/java/com/alibaba/nacos/client/ai/remote/AiHttpClientProxy.java","lineNumber":704,"sourceCode":"    \n    private byte[] callServerBytes(String api, Map<String, String> params, String server,\n        RequestResource resource)\n        throws NacosException {\n        Map<String, String> securityHeaders = securityProxy.getIdentityContext(resource);\n        Header header = Header.newInstance();\n        header.addAll(securityHeaders);\n        \n        String url = buildUrl(server, api);\n        \n        try {\n            HttpRestResult<byte[]> restResult = nacosRestTemplate.get(url, header,\n                Query.newInstance().initParams(params), byte[].class);\n            \n            if (restResult.ok()) {\n                return restResult.getData();\n            }\n            if (HttpURLConnection.HTTP_NOT_MODIFIED == restResult.getCode()) {\n                throw new NacosException(NacosException.NOT_MODIFIED, \"not modified\");\n            }\n            if (HttpURLConnection.HTTP_FORBIDDEN == restResult.getCode()) {\n                securityProxy.reLogin();\n            }\n            throw new NacosException(restResult.getCode(), restResult.getMessage());\n        } catch (NacosException e) {\n            throw e;\n        } catch (Exception e) {\n            LOGGER.error(\"[AI-HTTP] Failed to request {}\", url, e);\n            throw new NacosException(NacosException.SERVER_ERROR, e);\n        }\n    }\n    \n    /**\n     * Variant of {@link #callServerBytes} that exposes the raw {@link HttpRestResult} so callers\n     * can inspect response headers (e.g. {@code X-Nacos-Skill-Md5}). Status code translation rules\n     * mirror {@link #callServerBytes}: 304 raises {@link NacosException#NOT_MODIFIED}, 403\n     * triggers a security re-login before bubbling the original status code up.","sourceCodeStart":686,"sourceCodeEnd":722,"githubUrl":"https://github.com/alibaba/nacos/blob/9b989acdf181d00898f2e8839257bb2b2a3cefe3/client/src/main/java/com/alibaba/nacos/client/ai/remote/AiHttpClientProxy.java#L686-L722","documentation":"Thrown by callServerBytes() when the server returns HTTP 304 Not Modified. Code is NOT_MODIFIED (304). Same semantics as error 625 but for byte[] responses (skill download path). This signals the client's cached skill artifact is still current — not an error. Used by reqApiBytes(), which does NOT short-circuit on 304 (it retries across servers before the 304 surfaces as a retry-exhaustion error).","triggerScenarios":"A conditional skill-download GET where the server matches the client's md5 and returns 304. Because reqApiBytes retries on 304, this exception is caught and retried on every server before the final retry-exhaustion exception (error 622) is thrown with the 304 code.","commonSituations":"Repeated skill downloads with an unchanged md5. Developers expecting byte[] data but getting 304. The retry-on-304 behavior in reqApiBytes means 304 effectively becomes a retry-exhaustion error (error 622), not a clean 304 signal.","solutions":["Use querySkill() (via reqApiBytesWithHeader) instead of downloadSkill() (via reqApiBytes) when you need 304 propagation — the header variant short-circuits on 304.","Catch NacosException.NOT_MODIFIED at the call site and treat it as 'cache still valid'.","Ensure you pass the correct cached md5 to avoid unnecessary full downloads."],"exampleFix":"// before: downloadSkill retries 304 across all servers\ntry {\n    byte[] zip = aiService.downloadSkill(\"skill\", \"1.0\", null);\n} catch (NacosException e) {\n    // if 304, this surfaces as error 622 (retry exhaustion) with code 304\n}\n\n// after: use querySkill for proper 304 handling\ntry {\n    SkillQueryResponse resp = aiService.querySkill(\"skill\", \"1.0\", null, cachedMd5);\n} catch (NacosException e) {\n    if (e.getErrCode() == NacosException.NOT_MODIFIED) {\n        // cached skill ZIP is still current\n        return;\n    }\n    throw e;\n}","handlingStrategy":"try-catch","validationCode":null,"typeGuard":null,"tryCatchPattern":"try {\n    byte[] zip = aiService.downloadSkill(name, ver, label);\n} catch (NacosException e) {\n    // reqApiBytes does NOT short-circuit on 304 — it retries, so 304 surfaces as error 622\n    // Prefer querySkill() for 304-aware conditional downloads\n    if (e.getErrCode() == NacosException.NOT_MODIFIED) {\n        log.debug(\"Skill unchanged\");\n        return cachedZip;\n    }\n    throw e;\n}","preventionTips":["Use querySkill() instead of downloadSkill() when you need 304 handling.","Pass cached md5 to avoid full re-downloads.","Be aware that reqApiBytes retries 304s across all servers."],"tags":["nacos","ai-client","http","not-modified","conditional-request","skill-download"],"backgroundTag":null,"analyzedSha":"9b989acdf181d00898f2e8839257bb2b2a3cefe3","analyzedAt":"2026-08-14T07:17:31.569Z","schemaVersion":2},"datasetVersion":"2026-08-14T10:17:34.591Z"}