alibaba/arthas · error · IllegalArgumentException

cursor 缺少 offset

Error message

cursor 缺少 offset

What it means

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).

Source

Thrown at core/src/main/java/com/taobao/arthas/core/mcp/tool/function/basic1000/ViewFileTool.java:151

        private CursorValue(String path, long offset) {
            this.path = path;
            this.offset = offset;
        }
    }

    private CursorValue decodeCursor(String cursor) {
        try {
            byte[] jsonBytes = Base64.getUrlDecoder().decode(cursor);
            String json = new String(jsonBytes, StandardCharsets.UTF_8);

            Map<String, Object> map = JsonParser.fromJson(json, new TypeReference<Map<String, Object>>() {});
            Object pathObj = map.get("path");
            Object offsetObj = map.get("offset");
            if (!(pathObj instanceof String) || ((String) pathObj).trim().isEmpty()) {
                throw new IllegalArgumentException("cursor 缺少 path");
            }
            if (!(offsetObj instanceof Number)) {
                throw new IllegalArgumentException("cursor 缺少 offset");
            }
            long offset = ((Number) offsetObj).longValue();
            if (offset < 0) {
                throw new IllegalArgumentException("cursor offset 不允许为负数");
            }
            return new CursorValue(((String) pathObj).trim(), offset);
        } catch (IllegalArgumentException e) {
            throw new IllegalArgumentException("cursor 解析失败: " + e.getMessage(), e);
        }
    }

    private String encodeCursor(String path, long offset) {
        Map<String, Object> cursor = new LinkedHashMap<>();
        cursor.put("v", 1);
        cursor.put("path", path);
        cursor.put("offset", offset);
        String json = JsonParser.toJson(cursor);
        return Base64.getUrlEncoder().withoutPadding().encodeToString(json.getBytes(StandardCharsets.UTF_8));

View on GitHub (pinned to 21cf2e9ba5)

Solutions

  1. Use cursor tokens returned by previous viewfile responses.
  2. If building manually, include a numeric "offset", e.g. {"v":1,"path":"/var/log/app.log","offset":0}.

Example fix

// before
cursor = base64url({"v":1,"path":"/var/log/app.log"})
// after
cursor = base64url({"v":1,"path":"/var/log/app.log","offset":0})
Defensive patterns

Strategy: validation

Validate before calling

// When building a cursor, always include a numeric offset
if (!(offset instanceof Number)) throw new IllegalArgumentException("offset required");
c.put("offset", ((Number) offset).longValue());

Type guard

static boolean cursorHasValidOffset(Map<String,Object> m) {
    return m.get("offset") instanceof Number;
}

Try / catch

null

Prevention

When it happens

Trigger: Calling viewfile with a cursor whose JSON omits 'offset' (e.g. {"v":1,"path":"/x"}) or has offset as a non-numeric type.

Common situations: Manually built cursor missing the offset key; schema drift between cursor versions; client serialization dropping numeric fields.

Related errors


AI-assisted analysis of alibaba/arthas@21cf2e9ba5 (2026-08-14). Data as JSON: /api/errors/d3ffecb8016024e3. Report an issue: GitHub.