larksuite/cli · error

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

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.

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.

Appendix: 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)