larksuite/cli · error
%s: path %q is a directory, not a file
Error message
%s: path %q is a directory, not a file
What it means
lstatNonDir succeeded but the Lstat info shows the audited path is a directory, and the audit requires a regular file. The security audit deliberately refuses directories so bindings reference actual files.
Source
Thrown at internal/binding/audit.go:88
// orthogonal concern and the audit is intentionally Go-stdlib strict here.
// Callers that accept user-authored config (e.g. resolveFileRef) must
// pre-resolve any such shortcuts before passing the path in.
func requireAbsolutePath(target, label string) error {
if !filepath.IsAbs(target) {
return fmt.Errorf("%s: path must be absolute, got %q", label, target)
}
return nil
}
// lstatNonDir stats the path without following symlinks, rejecting
// directories. Returns the stat info for downstream steps to reuse.
func lstatNonDir(target, label string) (fs.FileInfo, error) {
info, err := vfs.Lstat(target)
if err != nil {
return nil, fmt.Errorf("%s: cannot stat %q: %w", label, target, err)
}
if info.IsDir() {
return nil, fmt.Errorf("%s: path %q is a directory, not a file", label, target)
}
return info, nil
}
// resolveSymlinkIfAllowed resolves a symlink to its target when
// params.AllowSymlinkPath is true, or rejects it otherwise. When the input
// is not a symlink, target is returned unchanged. A symlink that points to
// another symlink is rejected so callers only deal with a single hop.
func resolveSymlinkIfAllowed(target string, linfo fs.FileInfo, params AuditParams) (string, error) {
if linfo.Mode()&os.ModeSymlink == 0 {
return target, nil
}
if !params.AllowSymlinkPath {
return "", fmt.Errorf("%s: path %q is a symlink (not allowed)", params.Label, target)
}
resolved, err := vfs.EvalSymlinks(target)
if err != nil {
return "", fmt.Errorf("%s: cannot resolve symlink %q: %w", params.Label, target, err)View on GitHub (pinned to 7fd6ef3c07)
Solutions
- Point the binding at the concrete file inside the directory (append the filename).
- Verify with 'ls -la' what exists at the path and correct the configured path.
- If a directory is legitimately expected, this is the wrong audit/API — use a directory-aware path instead of AssertSecurePath for files.
- Recreate the layout so the path is a regular file (e.g. remove the mistakenly created directory and restore the file).
Example fix
// before path := "/home/u/.config/myapp" // directory // after path := "/home/u/.config/myapp/config.yaml"
Defensive patterns
Strategy: validation
Validate before calling
// preflight: reject directories before the audit
if info, err := os.Lstat(p); err == nil && info.IsDir() {
return fmt.Errorf("%q is a directory; point the binding at a concrete file inside it", p)
} Type guard
func isDirErr(err error) bool {
return err != nil && strings.Contains(err.Error(), "is a directory, not a file")
} Try / catch
if err := binding.AssertSecurePath(p, params); err != nil {
if isDirErr(err) {
return fmt.Errorf("append the filename to the configured path: %w", err)
}
return err
} Prevention
- Configure full file paths, never directory paths, for file bindings.
- Verify layout after workspace/version changes that may have turned files into directories.
- Add config validation that lstats and rejects directories at load time.
- Use ls -la to confirm the path is a regular file before deployment.
When it happens
Trigger: AssertSecurePath is called with a path that Lstat reports IsDir() — a directory was configured where a file is required (e.g. a directory named like the expected config file, or the user passed a folder path).
Common situations: Config value points at a directory (e.g. ~/.config/myapp/ instead of ~/.config/myapp/config.yaml); workspace layout change turned the expected file into a directory; accidental mkdir with the file's name.
Related errors
- %s: path must be absolute, got %q
- %s: cannot stat %q: %w
- %s: path %q is a symlink (not allowed)
- unsafe output path: %w
- %s %q has multiple hard links, so writing it would also rewr
AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04).
Data as JSON: /api/errors/6296e75f0d56e93f.
Report an issue: GitHub.