alibaba/arthas · error · IllegalArgumentException

cursor 解析失败

Error message

cursor 解析失败: ${e.getMessage()}

What it means

Thrown by decodeCursor() in the ViewFile MCP tool as the outer catch wrapping any inner IllegalArgumentException (the missing path/missing offset/negative offset cases, errors 95-97). It can also surface for a cursor that fails base64url decoding or JSON parsing, since the inner operations can throw. The message appends the specific inner reason.

Solutions

  1. Discard the bad cursor and call viewfile with an explicit `path` (+ offset) to start fresh, then use the newly returned cursor.
  2. Ensure the cursor is the exact base64url string returned by a prior viewfile response, unmodified.
  3. If you must build a cursor, encode JSON {"v":1,"path":<non-empty string>,"offset":<non-negative number>} with URL-safe base64, no padding.

Example fix

// before
viewfile(cursor="not-a-valid-cursor")
// after
viewfile(path="/var/log/app.log", offset=0)  // get a fresh cursor, then reuse it
Defensive patterns

Strategy: try-catch

Validate before calling

// Validate a cursor before sending it: decode + check shape
static boolean isValidCursor(String cursor) {
    try {
        String json = new String(Base64.getUrlDecoder().decode(cursor), StandardCharsets.UTF_8);
        Map<String,Object> m = JsonParser.fromJson(json, new TypeReference<Map<String,Object>>(){});
        return m.get("path") instanceof String && !((String)m.get("path")).trim().isEmpty()
            && m.get("offset") instanceof Number && ((Number)m.get("offset")).longValue() >= 0;
    } catch (Exception e) { return false; }
}

Type guard

null

Try / catch

try {
    viewfile(cursor);
} catch (IllegalArgumentException e) {
    if (e.getMessage().startsWith("cursor 解析失败")) {
        // discard the bad cursor, fall back to a fresh path-based call
        viewfile(path, 0L);
    } else throw e;
}

Prevention

When it happens

Trigger: Calling viewfile with any malformed cursor: not valid base64url, not valid JSON, or valid JSON missing required fields / with a negative offset. Any IllegalArgumentException inside the try block is rethrown with prefix 'cursor 解析失败:'.

Common situations: Using a stale/corrupted/truncated cursor token; passing a non-cursor string into the cursor field; client-side encoding bug; manually edited cursor.

Related errors


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

Appendix: source

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

            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));
    }

    private List<Path> loadAllowedRoots() {
        String config = System.getenv(ALLOWED_DIRS_ENV);

        List<Path> roots = new ArrayList<>();
        if (config != null && !config.trim().isEmpty()) {
            String[] parts = config.split(",");

View on GitHub (pinned to 21cf2e9ba5)