larksuite/cli · error

%s: cannot stat %q: %w

Error message

%s: cannot stat %q: %w

What it means

lstatNonDir failed to Lstat the audited path via vfs.Lstat and wraps the underlying error with %w (unwrappable). The stat itself failed — most commonly the file does not exist — so the secure-path audit cannot proceed.

Source

Thrown at internal/binding/audit.go:85

// requireAbsolutePath rejects relative paths; relative paths would depend on
// the process cwd and defeat the point of a static audit. Shell-style
// shortcuts like `~` are home-relative, not cwd-relative — they are an
// 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)
	}

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Check the wrapped cause after 'cannot stat': ENOENT means fix or create the path; EACCES means fix directory permissions.
  2. Verify the configured path exists on the machine running the CLI (ls the exact path).
  3. Fix typos or stale references in the binding/config that point to moved files.
  4. If running in a container/sandbox, mount or copy the file into the expected location.

Example fix

// caller
if _, err := os.Lstat(p); err != nil {
    return fmt.Errorf("configured file missing, create or fix path %q", p)
}
err := binding.AssertSecurePath(p, params)
Defensive patterns

Strategy: validation

Validate before calling

// preflight before AssertSecurePath
if _, err := os.Lstat(p); err != nil {
    if os.IsNotExist(err) { return fmt.Errorf("file %q does not exist; create it or fix the config", p) }
    return fmt.Errorf("cannot access %q: %w", p, err)
}

Type guard

func isStatErr(err error) bool {
    return err != nil && strings.Contains(err.Error(), "cannot stat")
}

Try / catch

if err := binding.AssertSecurePath(p, params); err != nil {
    if isStatErr(err) && errors.Is(err, fs.ErrNotExist) {
        return fmt.Errorf("configure an existing file path; %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: AssertSecurePath -> lstatNonDir where vfs.Lstat(target) errors: nonexistent file, permission denied on a parent directory, broken path component, or an invalid path on the host.

Common situations: Binding points at a file that was deleted or renamed; typo in a configured path; running on a host where the mounted workspace lacks the file; parent directory unreadable due to permissions or sandboxing.

Understand the failure class

Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.

Related errors


AI-assisted analysis of larksuite/cli@7fd6ef3c07 (2026-09-04). Data as JSON: /api/errors/db97d26479b4b993. Report an issue: GitHub.