vxcontrol/pentagi · error
container path '%s' is not a directory
Error message
container path '%s' is not a directory
What it means
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.
Source
Thrown at backend/pkg/docker/client.go:976
// and only the first maxListEntries were listed.
Truncated bool
}
func (dc *dockerClient) ListContainerDir(
ctx context.Context,
containerID string,
dirPath string,
) (ContainerDirListing, error) {
if strings.TrimSpace(dirPath) == "" {
dirPath = WorkFolderPathInContainer
}
dirStat, err := dc.ContainerStatPath(ctx, containerID, dirPath)
if err != nil {
return ContainerDirListing{}, fmt.Errorf("failed to stat container path '%s': %w", dirPath, err)
}
if !dirStat.Mode.IsDir() {
return ContainerDirListing{}, fmt.Errorf("container path '%s' is not a directory", dirPath)
}
// List direct children NUL-delimited. Parsing `ls` output is unsafe: under a
// TTY GNU coreutils shell-quotes names and busybox wraps them in ANSI escapes,
// so a readable file with a space / quote / non-ASCII byte would be mis-stat'd
// and reported unreadable. `find -print0` emits literal bytes and is portable
// (GNU + busybox); the NUL delimiter also survives names containing newlines.
// No TTY: a TTY's onlcr would rewrite every \n in the stream to \r\n —
// including a \n that is part of a filename — corrupting the name. Without a
// TTY the exec stream is multiplexed and demuxed below.
createResp, err := dc.ContainerExecCreate(ctx, containerID, client.ExecCreateOptions{
Cmd: []string{"find", dirPath, "-maxdepth", "1", "-mindepth", "1", "!", "-name", ".*", "-print0"},
AttachStdout: true,
AttachStderr: true,
})
if err != nil {
return ContainerDirListing{}, fmt.Errorf("failed to create list exec for '%s': %w", dirPath, err)
}View on GitHub (pinned to ea665308ba)
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
Example fix
// before
listing, err := c.ListContainerDir(ctx, containerID, "/work/output.txt")
// after
dir := "/work/output.txt"
if strings.HasSuffix(dir, ".txt") {
dir = "/work"
}
listing, err := c.ListContainerDir(ctx, containerID, dir) Defensive patterns
Strategy: validation
Validate before calling
// stat first and branch on type before listing
st, err := c.ContainerStatPath(ctx, id, p)
if err == nil && !st.Mode.IsDir() {
// caller passed a file; handle as file or use its parent directory
p = path.Dir(p)
} Try / catch
listing, err := c.ListContainerDir(ctx, id, p)
if err != nil && strings.Contains(err.Error(), "is not a directory") {
return c.ListContainerDir(ctx, id, path.Dir(p)) // fall back to parent
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- failed to stat container path '%s': %w
- failed to create resources directory: %w
- invalid ContainerStatus: %s
- path is required and cannot be empty
- Internal
AI-assisted analysis of vxcontrol/pentagi@ea665308ba (2026-09-01).
Data as JSON: /api/errors/5d3cd057377e41fc.
Report an issue: GitHub.