{"record":{"id":"ee17ab6e7b5d5918","repo":"hashicorp/nomad","slug":"file-q-is-a-directory","errorCode":null,"errorMessage":"file %q is a directory","messagePattern":"file %q is a directory","errorType":"http","errorClass":null,"httpStatus":400,"severity":"error","filePath":"client/fs_endpoint.go","lineNumber":237,"sourceCode":"\tif err != nil {\n\t\tcode := new(int64(http.StatusInternalServerError))\n\t\tif structs.IsErrUnknownAllocation(err) {\n\t\t\tcode = new(int64(http.StatusNotFound))\n\t\t}\n\n\t\thandleStreamResultError(err, code, encoder)\n\t\treturn\n\t}\n\n\t// Calculate the offset\n\tfileInfo, err := fs.Stat(req.Path)\n\tif err != nil {\n\t\thandleStreamResultError(err, new(int64(http.StatusBadRequest)), encoder)\n\t\treturn\n\t}\n\tif fileInfo.IsDir {\n\t\thandleStreamResultError(\n\t\t\tfmt.Errorf(\"file %q is a directory\", req.Path),\n\t\t\tnew(int64(http.StatusBadRequest)), encoder)\n\t\treturn\n\t}\n\n\t// If offsetting from the end subtract from the size\n\tif req.Origin == \"end\" {\n\t\treq.Offset = max(fileInfo.Size-req.Offset, 0)\n\t}\n\n\tframes := make(chan *sframer.StreamFrame, streamFramesBuffer)\n\terrCh := make(chan error)\n\tvar buf bytes.Buffer\n\tframeCodec := codec.NewEncoder(&buf, structs.JsonHandle)\n\n\t// Create the framer\n\tframer := sframer.NewStreamFramer(frames, streamHeartbeatRate, streamBatchWindow, streamFrameSize)\n\tframer.Run()\n\tdefer framer.Destroy()","sourceCodeStart":219,"sourceCodeEnd":255,"githubUrl":"https://github.com/hashicorp/nomad/blob/482b49bf1aec006f089bcfc7e632d8f6ac303e5e/client/fs_endpoint.go#L219-L255","documentation":"The file stream endpoint stat's the requested path and, if the result is a directory, refuses to stream it because only regular files can be streamed at an offset. The error is returned as HTTP 400 Bad Request with the requested path embedded in the message.","triggerScenarios":"Calling GET /v1/client/fs/stream (or the Allocs().Logs/files API backing it) with a path parameter pointing to a directory instead of a regular file, e.g. streaming /alloc/logs/ instead of a specific log file.","commonSituations":"Hardcoded paths that point at directories; using the fs endpoint to browse directories when the list endpoint (/v1/client/fs/ls) is the right one; path built from user input that resolves to a directory; a file replaced by a directory after a task restart.","solutions":["Pass the full path to a regular file (e.g. /alloc/logs/web.stdout.0) instead of a directory.","Use the file listing endpoint (GET /v1/client/fs/ls) first to enumerate files, then stream a concrete file entry.","Validate the requested path is a file with the stat endpoint (GET /v1/client/fs/stat) before streaming."],"exampleFix":"// before\nr, err := a.Stream(ctx, &api.StreamFrameReq{Path: \"/alloc/logs/\"})\n// after\ninfo, _, err := client.AllocFS().Stat(alloc, \"/alloc/logs/web.stdout.0\", nil)\nif err != nil || info.IsDir {\n    return fmt.Errorf(\"path is a directory or missing\")\n}\nr, err = client.AllocFS().Stream(alloc, \"/alloc/logs/web.stdout.0\", \"start\", 0, false, nil, ctx.Done())","handlingStrategy":"validation","validationCode":"info, _, err := client.AllocFS().Stat(alloc, path, nil)\nif err != nil {\n    return err\n}\nif info.IsDir {\n    return fmt.Errorf(\"%s is a directory; use List\", path)\n}","typeGuard":"func isDirStat(err error) bool {\n    return strings.Contains(err.Error(), \"is a directory\")\n}","tryCatchPattern":"if err := streamFile(path); err != nil {\n    if isDirStat(err) {\n        entries, lerr := client.AllocFS().List(alloc, path, nil)\n        if lerr == nil {\n            useEntries(entries)\n        }\n        return nil\n    }\n    return err\n}","preventionTips":["Stat the path before streaming; switch to the ls endpoint for directories.","Never pass directory paths from user input straight to the stream API.","Build paths from ls results rather than hardcoded assumptions."],"tags":["nomad","client","filesystem","bad-request","path"],"backgroundTag":"path-is-a-directory","analyzedSha":"482b49bf1aec006f089bcfc7e632d8f6ac303e5e","analyzedAt":"2026-09-04T07:54:14.808Z","contentChangedAt":"2026-09-04T07:54:14.808Z","schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}