{"record":{"id":"5d3cd057377e41fc","repo":"vxcontrol/pentagi","slug":"container-path-s-is-not-a-directory","errorCode":null,"errorMessage":"container path '%s' is not a directory","messagePattern":"container path '(.+?)' is not a directory","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"backend/pkg/docker/client.go","lineNumber":976,"sourceCode":"\t// and only the first maxListEntries were listed.\n\tTruncated bool\n}\n\nfunc (dc *dockerClient) ListContainerDir(\n\tctx context.Context,\n\tcontainerID string,\n\tdirPath string,\n) (ContainerDirListing, error) {\n\tif strings.TrimSpace(dirPath) == \"\" {\n\t\tdirPath = WorkFolderPathInContainer\n\t}\n\n\tdirStat, err := dc.ContainerStatPath(ctx, containerID, dirPath)\n\tif err != nil {\n\t\treturn ContainerDirListing{}, fmt.Errorf(\"failed to stat container path '%s': %w\", dirPath, err)\n\t}\n\tif !dirStat.Mode.IsDir() {\n\t\treturn ContainerDirListing{}, fmt.Errorf(\"container path '%s' is not a directory\", dirPath)\n\t}\n\n\t// List direct children NUL-delimited. Parsing `ls` output is unsafe: under a\n\t// TTY GNU coreutils shell-quotes names and busybox wraps them in ANSI escapes,\n\t// so a readable file with a space / quote / non-ASCII byte would be mis-stat'd\n\t// and reported unreadable. `find -print0` emits literal bytes and is portable\n\t// (GNU + busybox); the NUL delimiter also survives names containing newlines.\n\t// No TTY: a TTY's onlcr would rewrite every \\n in the stream to \\r\\n —\n\t// including a \\n that is part of a filename — corrupting the name. Without a\n\t// TTY the exec stream is multiplexed and demuxed below.\n\tcreateResp, err := dc.ContainerExecCreate(ctx, containerID, client.ExecCreateOptions{\n\t\tCmd:          []string{\"find\", dirPath, \"-maxdepth\", \"1\", \"-mindepth\", \"1\", \"!\", \"-name\", \".*\", \"-print0\"},\n\t\tAttachStdout: true,\n\t\tAttachStderr: true,\n\t})\n\tif err != nil {\n\t\treturn ContainerDirListing{}, fmt.Errorf(\"failed to create list exec for '%s': %w\", dirPath, err)\n\t}","sourceCodeStart":958,"sourceCodeEnd":994,"githubUrl":"https://github.com/vxcontrol/pentagi/blob/ea665308baaff015b226f308438a68d929d0f29b/backend/pkg/docker/client.go#L958-L994","documentation":"ListContainerDir stats the requested path and rejects it when the stat result's mode is not a directory. The path exists in the container but is a regular file, symlink to a file, socket, or other non-directory entry.","triggerScenarios":"Caller passes a file path (e.g. /work/results.txt) or a symlink pointing at a file to ListContainerDir; empty dirPath defaulting to WorkFolderPathInContainer when that default itself is a mounted file; path case-mismatch resolving to a file.","commonSituations":"LLM agent passing a filename instead of a directory to a 'list directory' tool call; user mounting a single file at the work-folder path in docker-compose; symlinked 'folders' that actually point at files; Windows-style path strings that resolve oddly in Linux containers.","solutions":["Pass a directory path, not a file path — strip the filename or use filepath.Dir-style handling on the container side","Verify the target with `docker exec <id> stat <path>` and adjust the argument","If a work folder is volume-mounted, mount a directory, not a single file","Handle the error gracefully in tool code by returning the file's metadata or a clear message instead of a listing"],"exampleFix":"// before\nlisting, err := c.ListContainerDir(ctx, containerID, \"/work/output.txt\")\n// after\ndir := \"/work/output.txt\"\nif strings.HasSuffix(dir, \".txt\") {\n    dir = \"/work\"\n}\nlisting, err := c.ListContainerDir(ctx, containerID, dir)","handlingStrategy":"validation","validationCode":"// stat first and branch on type before listing\nst, err := c.ContainerStatPath(ctx, id, p)\nif err == nil && !st.Mode.IsDir() {\n    // caller passed a file; handle as file or use its parent directory\n    p = path.Dir(p)\n}","typeGuard":null,"tryCatchPattern":"listing, err := c.ListContainerDir(ctx, id, p)\nif err != nil && strings.Contains(err.Error(), \"is not a directory\") {\n    return c.ListContainerDir(ctx, id, path.Dir(p)) // fall back to parent\n}","preventionTips":["Validate the argument is a directory path before calling the list tool","Mount directories (not single files) at the container work folder","Resolve symlinks on the container side before listing","Give agents a stat tool so they can check entry type before listing"],"tags":["docker","filesystem","validation"],"backgroundTag":"not-a-directory","analyzedSha":"ea665308baaff015b226f308438a68d929d0f29b","analyzedAt":"2026-09-01T14:16:31.421Z","contentChangedAt":null,"schemaVersion":2},"datasetVersion":"2026-09-08T10:18:20.063Z"}