larksuite/cli · error

file has multiple hard links, so the other names it can be r

Error message

file has multiple hard links, so the other names it can be reached by cannot be checked (hint: copy the file and use the copy instead)

What it means

The opened file's st.Nlink > 1 and hard-link rejection is enabled (openValidated always passes true). An extra name lets a denylisted file be smuggled into an allowed directory, and the filesystem cannot enumerate the other names, so the library bluntly refuses any multi-linked file — including benign layouts like pnpm/nix content-addressed stores.

Source

Thrown at internal/vfs/localfileio/openvalidated_unix.go:71

	return f, nil
}

// inspectOpenedFile validates the opened fd and restores blocking mode. pre is
// nil when there is no prior Stat to compare against.
func inspectOpenedFile(f *os.File, pre os.FileInfo, rejectHardLinks bool) error {
	post, err := f.Stat()
	if err != nil {
		return fmt.Errorf("cannot stat opened file: %w", err)
	}
	if pre != nil && !os.SameFile(pre, post) {
		return fmt.Errorf("file changed between validation and open")
	}
	if !post.Mode().IsRegular() {
		return fmt.Errorf("not a regular file (directories, devices, FIFOs, and sockets are refused)")
	}
	if rejectHardLinks {
		if st, ok := post.Sys().(*syscall.Stat_t); ok && st.Nlink > 1 {
			return fmt.Errorf("file has multiple hard links, so the other names it can be reached by " +
				"cannot be checked (hint: copy the file and use the copy instead)")
		}
	}
	if err := syscall.SetNonblock(int(f.Fd()), false); err != nil {
		return fmt.Errorf("cannot restore blocking mode: %w", err)
	}
	return nil
}

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Copy the file (cp, not ln) and use the copy — this is the documented workaround
  2. If the file lives in a pnpm/nix-style store, resolve to the real file and copy it out, or reinstall with a non-hardlink layout (e.g. pnpm with hoisted node-linker)
  3. Check with stat: if Links is 1 the error was stale/re-raced; retry
  4. Avoid hardlink-based dedup tools on directories the CLI must read

Example fix

// before
ln config.yaml /workdir/config.yaml   # Nlink=2, refused
// after
cp config.yaml /workdir/config.yaml   # independent inode, accepted
Defensive patterns

Strategy: validation

Validate before calling

info, err := os.Stat(path)
if err != nil { return err }
if st, ok := info.Sys().(*syscall.Stat_t); ok && st.Nlink > 1 {
    // copy to a fresh file before use
    tmp := path + ".copy"
    if err := copyFile(path, tmp); err != nil { return err }
    path = tmp
}

Try / catch

var pve *fileio.PathValidationError
if errors.As(err, &pve) && strings.Contains(pve.Error(), "multiple hard links") { /* copy the file and retry with the copy */ }

Prevention

When it happens

Trigger: Any openValidated read of a file whose inode has 2+ directory entries: hard links created with ln, or content-addressed package stores (pnpm, nix, cargo hardlink caches) linking into a shared store outside the allowlist.

Common situations: Reading a file inside a pnpm node_modules or nix store path; a hardlink-based dedup/backup tool (rsync --link-dest) touched the file; user created a hard link for convenience.

Related errors


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