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 has more than one hard link (NumberOfLinks > 1), meaning it can be reached by multiple directory names. Since the library cannot verify that content written through this handle is not also visible/mutated via the other names, it refuses the file and suggests using a copy. This preserves the write-isolation guarantee of validated opens.

Source

Thrown at internal/vfs/localfileio/openvalidated_windows.go:57

// inspectOpenedFile validates the opened handle. pre is nil when there is no
// prior Stat to compare against.
func inspectOpenedFile(f *os.File, pre os.FileInfo) 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)")
	}
	var handleInfo syscall.ByHandleFileInformation
	if err := syscall.GetFileInformationByHandle(syscall.Handle(f.Fd()), &handleInfo); err != nil {
		return fmt.Errorf("cannot inspect opened file links: %w", err)
	}
	if handleInfo.NumberOfLinks > 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)")
	}
	return nil
}

View on GitHub (pinned to 7fd6ef3c07)

Solutions

  1. Copy the file to a new path (a fresh copy has one link) and use the copy, as the error hint suggests
  2. Break the extra links: delete the other directory entries pointing at the same file, leaving one
  3. Replace the hard link with a real copy: copy to a temp file and rename it over the original name
  4. Avoid hardlink-preserving copy flags (e.g. robocopy /J /HK style options) when staging files for this library

Example fix

// before: hard-linked file
// mklink /H config-copy.json config.json  -> NumberOfLinks = 2
f, err := openValidated("config-copy.json", pre)
// after: use an independent copy
// copy config-copy.json config-copy.json.tmp && move /y config-copy.json.tmp config-copy.json
f, err := openValidated("config-copy.json", pre)
Defensive patterns

Strategy: validation

Validate before calling

// count links via the name before opening (Windows)
func hardLinkCount(path string) (uint32, error) {
    info, err := os.Stat(path)
    if err != nil { return 0, err }
    if !info.Mode().IsRegular() { return 0, fmt.Errorf("not a regular file") }
    return info.Sys().(*syscall.Win32FileAttributeData).NFileIndexHigh, nil // pair with link-count check via ByHandleFileInformation
}

Try / catch

f, err := openValidated(path, pre)
if err != nil && strings.Contains(err.Error(), "multiple hard links") {
    tmp := path + ".copy.tmp"
    if cerr := copyFile(path, tmp); cerr != nil { return cerr }
    if rerr := os.Rename(tmp, path); rerr != nil { return rerr } // rename replaces links with a single-link file
    f, err = openValidated(path, nil)
}
return err

Prevention

When it happens

Trigger: Calling openValidated on Windows on a file that has been hard-linked (e.g. via mklink /H, backup tools' hardlink dedup, package managers like pnpm-style stores) so ByHandleFileInformation.NumberOfLinks > 1.

Common situations: Files deduplicated by backup software (Carbonite, Macrium), files hard-linked by cargo/pnpm-style caches, users hard-linking config files across workspaces, files copied with hardlink-preserving tools (robocopy /HK).

Related errors


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