GoogleContainerTools/skaffold · error

%s is a directory

Error message

%s is a directory

What it means

AbsFile resolves a filename against a workspace and returns its absolute path, but first stats the path and refuses directories with this error. It is thrown when the provided filename resolves to a directory rather than a regular file. CreateCommand (and tests) use it to validate executable/file inputs before launching commands.

Source

Thrown at pkg/skaffold/util/util.go:207

			m[v[0]] = v[1]
		}
	}
	return m
}

func isAlphaNum(c uint8) bool {
	return c == '_' || '0' <= c && c <= '9' || 'a' <= c && c <= 'z' || 'A' <= c && c <= 'Z'
}

// AbsFile resolves the absolute path of the file named filename in directory workspace, erroring if it is not a file
func AbsFile(workspace string, filename string) (string, error) {
	file := filepath.Join(workspace, filename)
	info, err := os.Stat(file)
	if err != nil {
		return "", err
	}
	if info.IsDir() {
		return "", fmt.Errorf("%s is a directory", file)
	}
	return filepath.Abs(file)
}

// NonEmptyLines scans the provided input and returns the non-empty strings found as an array
func NonEmptyLines(input []byte) []string {
	var result []string
	scanner := bufio.NewScanner(bytes.NewReader(input))
	for scanner.Scan() {
		if line := scanner.Text(); len(line) > 0 {
			result = append(result, line)
		}
	}
	return result
}

// CloneThroughJSON clones an `old` object into a `new` one
// using json marshalling and unmarshalling.

View on GitHub (pinned to a1189de023)

Solutions

  1. Point the configuration at the actual file, not its containing directory (e.g. ./script.sh instead of ./scripts).
  2. Verify the resolved path with `ls -la <workspace>/<filename>` — if it is a directory, append the filename.
  3. Check templating/variable substitution isn't dropping the filename portion of the path.
  4. If you intended a directory, use the appropriate config field for directories instead of one requiring a file.

Example fix

// before
cmd := util.CreateCommand(ctx, "/work/tools", "", []string{})  // /work/tools is a directory
// after
cmd := util.CreateCommand(ctx, "/work/tools/runner.sh", "", []string{})
Defensive patterns

Strategy: validation

Validate before calling

func mustBeFile(workspace, name string) error {
    p := filepath.Join(workspace, name)
    info, err := os.Stat(p)
    if err != nil {
        return err
    }
    if info.IsDir() {
        return fmt.Errorf("%q is a directory; expected a file", p)
    }
    return nil
}

Type guard

func isRegularFile(path string) bool {
    info, err := os.Stat(path)
    return err == nil && info.Mode().IsRegular()
}

Try / catch

abs, err := util.AbsFile(workspace, name)
if err != nil {
    if strings.HasSuffix(err.Error(), "is a directory") {
        return fmt.Errorf("config points at directory %s; append the filename", err)
    }
    return err
}

Prevention

When it happens

Trigger: Calling AbsFile (directly or via CreateCommand) with a path that os.Stat reports IsDir — e.g. pointing a command/file config at a directory name, or the workspace + filename join landing on a directory because a filename component was omitted.

Common situations: skaffold.yaml referencing a directory where a binary or file is expected (e.g. a custom test command pointing at a folder); a build artifact path that lost its filename during templating; typo making the path resolve to a directory.

Related errors


AI-assisted analysis of GoogleContainerTools/skaffold@a1189de023 (2026-09-05). Data as JSON: /api/errors/2cec98aebc4c0a1a. Report an issue: GitHub.