{"record":{"id":"0584474d73a7ba3f","repo":"alibaba/arthas","slug":"file","errorCode":null,"errorMessage":"不是普通文件: ${file}","messagePattern":"不是普通文件: (.+?)","errorType":"validation","errorClass":"IllegalArgumentException","httpStatus":null,"severity":"error","filePath":"core/src/main/java/com/taobao/arthas/core/mcp/tool/function/basic1000/ViewFileTool.java","lineNumber":258,"sourceCode":"            if (!candidate.startsWith(root)) {\n                continue;\n            }\n            if (!Files.exists(candidate)) {\n                continue;\n            }\n            Path real = candidate.toRealPath();\n            if (!real.startsWith(root)) {\n                continue;\n            }\n            assertRegularFile(real);\n            return real;\n        }\n        throw new IllegalArgumentException(\"文件不存在或不在允许目录白名单内: \" + requestedPath);\n    }\n\n    private static void assertRegularFile(Path file) {\n        if (!Files.isRegularFile(file)) {\n            throw new IllegalArgumentException(\"不是普通文件: \" + file);\n        }\n    }\n\n    private static boolean isUnderAllowedRoot(Path file, List<Path> allowedRoots) {\n        for (Path root : allowedRoots) {\n            if (file.startsWith(root)) {\n                return true;\n            }\n        }\n        return false;\n    }\n\n    private static int clampMaxBytes(Integer maxBytes) {\n        int value = (maxBytes != null && maxBytes > 0) ? maxBytes : DEFAULT_MAX_BYTES;\n        return Math.min(value, MAX_MAX_BYTES);\n    }\n\n    private static long adjustOffset(boolean cursorUsed, long requestedOffset, long fileSize) {","sourceCodeStart":240,"sourceCodeEnd":276,"githubUrl":"https://github.com/alibaba/arthas/blob/21cf2e9ba52b305290be7223b980ff504bb9cb5b/core/src/main/java/com/taobao/arthas/core/mcp/tool/function/basic1000/ViewFileTool.java#L240-L276","documentation":"Thrown by assertRegularFile when Files.isRegularFile(file) returns false. This catches directories, symlinks pointing to directories, device files, named pipes, and sockets. The check runs after the allowed-root verification, so only paths that already passed the whitelist are tested.","triggerScenarios":"Calling viewfile with a path that resolves to a directory (e.g. the arthas-output directory itself), a symlink whose target is a directory, or a special file under an allowed root. Can be hit from both the absolute-path branch (line 231) and the relative-path branch (line 250).","commonSituations":"User accidentally passes a directory path instead of a file; path has a trailing slash; the cursor from a previous call encoded a directory path.","solutions":["Point to a specific file, not a directory or folder path.","Remove any trailing slash or path separator from the path argument.","If using a cursor, ensure the original path was a regular file."],"exampleFix":"// before\nviewfile(path=\"/home/user/logs/\")\n// after\nviewfile(path=\"/home/user/logs/app.log\")","handlingStrategy":"validation","validationCode":"// Before calling viewfile, check the path is a regular file\nPath p = Paths.get(requestedPath);\nif (!Files.isRegularFile(p)) {\n    throw new IllegalStateException(\"Not a regular file: \" + requestedPath);\n}","typeGuard":null,"tryCatchPattern":"try {\n    Path file = resolveAllowedFile(path, allowedRoots);\n} catch (IllegalArgumentException e) {\n    if (e.getMessage().contains(\"不是普通文件\")) {\n        // path resolved to a directory or special file\n    }\n}","preventionTips":["Strip trailing slashes from path arguments before calling viewfile.","Verify with Files.isRegularFile before invoking if the path source is untrusted.","Never pass a directory path to viewfile."],"tags":["viewfile","path-validation","filesystem","arthas-mcp"],"backgroundTag":null,"analyzedSha":"21cf2e9ba52b305290be7223b980ff504bb9cb5b","analyzedAt":"2026-08-14T00:57:07.243Z","schemaVersion":2},"datasetVersion":"2026-08-14T05:17:29.042Z"}