kopia/kopia · error

cannot resolve

Error message

cannot resolve '%q'

What it means

resolveSymlink follows symlinks a bounded number of times; if the chain still has not resolved to a file after that many hops, it wraps errTooManySymlinks with "cannot resolve '%q'". This guards against symlink cycles and pathologically deep chains during ignore-rule processing.

Solutions

  1. Find and break the symlink cycle: `find -L . -type l` or `namei -l <path>` to locate the loop
  2. Remove or repoint the cyclic symlink
  3. Exclude the problematic directory from the scan via ignore rules

Example fix

// before
ln -s . parent-link   // creates infinite loop
// after
rm parent-link        // remove the cyclic symlink or link to a bounded target
Defensive patterns

Strategy: validation

Validate before calling

if tgt, err := filepath.EvalSymlinks(path); err != nil { return fmt.Errorf("symlink chain unresolved for %s: %w", path, err) }

Type guard

func hasSymlinkLoop(root string) error { return filepath.Walk(root, func(p string, fi os.FileInfo, err error) error { if err != nil { return err }; if fi.Mode()&os.ModeSymlink != 0 { _, e := filepath.EvalSymlinks(p); return e } ; return nil }) }

Try / catch

if err != nil {
    log.Warnf("symlink cycle at %s, excluding from scan", path)
    return filepath.SkipDir
}

Prevention

When it happens

Trigger: A symlink loop (a -> b -> a) or a chain longer than the resolver's iteration limit is encountered while buildContext walks the tree.

Common situations: Accidental self-referencing links (ln -s . loop), circular links created by misconfigured deployment tooling, or extremely deep symlink chains in package-manager directories.

Related errors


AI-assisted analysis of kopia/kopia@82495e54b5 (2026-09-07). Data as JSON: /api/errors/1d3a0dc44d1cf73a. Report an issue: GitHub.

Appendix: source

Thrown at fs/ignorefs/ignorefs.go:299

	for range maxSymlinkFollow {
		target, err := entry.Resolve(ctx)
		if err != nil {
			link, _ := entry.Readlink(ctx)
			return nil, errors.Wrapf(err, "when resolving symlink %s of type %T, which points to %s", entry.Name(), entry, link)
		}

		switch t := target.(type) {
		case fs.File:
			return t, nil
		case fs.Symlink:
			entry = t
			continue
		default:
			return nil, errors.Wrapf(errSymlinkNotAFile, "%s does not eventually link to a file", entry.Name())
		}
	}

	return nil, errors.Wrapf(errTooManySymlinks, "cannot resolve '%q'", entry.Name())
}

func (d *ignoreDirectory) buildContext(ctx context.Context) (*ignoreContext, error) {
	effectiveDotIgnoreFiles := d.parentContext.dotIgnoreFiles

	pol := d.policyTree.DefinedPolicy()
	if pol != nil {
		effectiveDotIgnoreFiles = pol.FilesPolicy.DotIgnoreFiles
	}

	var dotIgnoreFiles []fs.File

	for _, dotfile := range effectiveDotIgnoreFiles {
		if e, err := d.Directory.Child(ctx, dotfile); err == nil {
			switch entry := e.(type) {
			case fs.File:
				dotIgnoreFiles = append(dotIgnoreFiles, entry)

View on GitHub (pinned to 82495e54b5)