{"record":{"id":"d3ffecb8016024e3","repo":"alibaba/arthas","slug":"cursor-offset","errorCode":null,"errorMessage":"cursor 缺少 offset","messagePattern":"cursor 缺少 offset","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/com/taobao/arthas/core/mcp/tool/function/basic1000/ViewFileTool.java","lineNumber":151,"sourceCode":"        private CursorValue(String path, long offset) {\n            this.path = path;\n            this.offset = offset;\n        }\n    }\n\n    private CursorValue decodeCursor(String cursor) {\n        try {\n            byte[] jsonBytes = Base64.getUrlDecoder().decode(cursor);\n            String json = new String(jsonBytes, StandardCharsets.UTF_8);\n\n            Map<String, Object> map = JsonParser.fromJson(json, new TypeReference<Map<String, Object>>() {});\n            Object pathObj = map.get(\"path\");\n            Object offsetObj = map.get(\"offset\");\n            if (!(pathObj instanceof String) || ((String) pathObj).trim().isEmpty()) {\n                throw new IllegalArgumentException(\"cursor 缺少 path\");\n            }\n            if (!(offsetObj instanceof Number)) {\n                throw new IllegalArgumentException(\"cursor 缺少 offset\");\n            }\n            long offset = ((Number) offsetObj).longValue();\n            if (offset < 0) {\n                throw new IllegalArgumentException(\"cursor offset 不允许为负数\");\n            }\n            return new CursorValue(((String) pathObj).trim(), offset);\n        } catch (IllegalArgumentException e) {\n            throw new IllegalArgumentException(\"cursor 解析失败: \" + e.getMessage(), e);\n        }\n    }\n\n    private String encodeCursor(String path, long offset) {\n        Map<String, Object> cursor = new LinkedHashMap<>();\n        cursor.put(\"v\", 1);\n        cursor.put(\"path\", path);\n        cursor.put(\"offset\", offset);\n        String json = JsonParser.toJson(cursor);\n        return Base64.getUrlEncoder().withoutPadding().encodeToString(json.getBytes(StandardCharsets.UTF_8));","sourceCodeStart":133,"sourceCodeEnd":169,"githubUrl":"https://github.com/alibaba/arthas/blob/21cf2e9ba52b305290be7223b980ff504bb9cb5b/core/src/main/java/com/taobao/arthas/core/mcp/tool/function/basic1000/ViewFileTool.java#L133-L169","documentation":"Thrown by decodeCursor() in the ViewFile MCP tool when the decoded cursor JSON has no 'offset' field or 'offset' is not a Number. The cursor must carry a numeric offset to know where to resume reading. Surfaced to the caller as 'cursor 解析失败: cursor 缺少 offset' (error 98).","triggerScenarios":"Calling viewfile with a cursor whose JSON omits 'offset' (e.g. {\"v\":1,\"path\":\"/x\"}) or has offset as a non-numeric type.","commonSituations":"Manually built cursor missing the offset key; schema drift between cursor versions; client serialization dropping numeric fields.","solutions":["Use cursor tokens returned by previous viewfile responses.","If building manually, include a numeric \"offset\", e.g. {\"v\":1,\"path\":\"/var/log/app.log\",\"offset\":0}."],"exampleFix":"// before\ncursor = base64url({\"v\":1,\"path\":\"/var/log/app.log\"})\n// after\ncursor = base64url({\"v\":1,\"path\":\"/var/log/app.log\",\"offset\":0})","handlingStrategy":"validation","validationCode":"// When building a cursor, always include a numeric offset\nif (!(offset instanceof Number)) throw new IllegalArgumentException(\"offset required\");\nc.put(\"offset\", ((Number) offset).longValue());","typeGuard":"static boolean cursorHasValidOffset(Map<String,Object> m) {\n    return m.get(\"offset\") instanceof Number;\n}","tryCatchPattern":"null","preventionTips":["Reuse cursor tokens returned by prior viewfile calls.","When crafting cursors, always include a numeric offset."],"tags":["arthas","mcp","viewfile","cursor","input-validation"],"backgroundTag":null,"analyzedSha":"21cf2e9ba52b305290be7223b980ff504bb9cb5b","analyzedAt":"2026-08-14T00:57:07.243Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}