kopia/kopia · error

unable to process directory

Error message

unable to process directory %q

What it means

When processing a child directory fails with an error that is NOT a dirReadError, processSingle treats it as fatal for the upload and wraps it with 'unable to process directory %q'. dirReadError failures (e.g. permission or IO problems reading a directory) are instead reported per policy and possibly ignored; any other failure type aborts the whole snapshot upload.

Solutions

  1. Read the %q name and the wrapped cause to locate the failing directory and underlying error type
  2. If the cause is an entry-type problem, fix or exclude that filesystem entry via ignore rules
  3. Verify repository storage health if the cause is an upload/write failure
  4. If it's a policy-action error surfacing as a non-dirReadError, correct the action script

Example fix

null
Defensive patterns

Strategy: try-catch

Validate before calling

info, err := os.Stat(dirPath)
if err != nil || !info.IsDir() {
    return fmt.Errorf("%s is not a readable directory: %w", dirPath, err)
}

Try / catch

err := uploadDir(ctx, ...)
var dre dirReadError
if errors.As(err, &dre) {
    // policy-controlled: report and maybe ignore
    reportError(dre.error)
} else if strings.Contains(err.Error(), "unable to process directory") {
    log.Fatalf("fatal failure processing %v: %v", dirPath, err)
    return err
}

Prevention

When it happens

Trigger: processEntryUploadResult or another step for a child directory returns a non-dirReadError error during processDirectoryEntries — e.g. upload/object-write failures, checkpoint errors, or panics converted to errors while processing entry.Name().

Common situations: Storage backend failure mid-snapshot causing non-read errors while walking the tree; bug or unexpected state in entry upload results; unusual filesystem entries causing type errors (cascading from 'invalid entry type'); running out of memory during very wide directory scans.

Related errors


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

Appendix: source

Thrown at snapshot/upload/upload.go:898

		childTree := policyTree.Child(entry.Name())
		childPrevDirs := uniqueChildDirectories(ctx, prevDirs, entry.Name())

		de, err := uploadDirInternal(ctx, u, entry, childTree, childPrevDirs, childLocalDirPathOrEmpty, entryRelativePath, childDirBuilder, parentCheckpointRegistry)
		if errors.Is(err, errCanceled) {
			return err
		}

		if err != nil {
			// Note: This only catches errors in subdirectories of the snapshot root, not on the snapshot
			// root itself. The intention is to always fail if the top level directory can't be read,
			// otherwise a meaningless, empty snapshot is created that can't be restored.
			var dre dirReadError
			if errors.As(err, &dre) {
				isIgnoredError := childTree.EffectivePolicy().ErrorHandlingPolicy.IgnoreDirectoryErrors.OrDefault(false)
				u.reportErrorAndMaybeCancel(dre.error, isIgnoredError, parentDirBuilder, entryRelativePath)
			} else {
				return errors.Wrapf(err, "unable to process directory %q", entry.Name())
			}
		} else {
			parentDirBuilder.AddEntry(de)
		}

		return nil

	case fs.Symlink:
		compressor := policyTree.Child(entry.Name()).EffectivePolicy().MetadataCompressionPolicy.MetadataCompressor()
		de, err := u.uploadSymlinkInternal(ctx, entryRelativePath, entry, compressor)

		return u.processEntryUploadResult(ctx, de, err, entryRelativePath, parentDirBuilder,
			policyTree.EffectivePolicy().ErrorHandlingPolicy.IgnoreFileErrors.OrDefault(false),
			u.OverrideEntryLogDetail.OrDefault(policyTree.EffectivePolicy().LoggingPolicy.Entries.Snapshotted.OrDefault(policy.LogDetailNone)),
			"snapshotted symlink", t0)

	case fs.File:
		atomic.AddInt32(&u.stats.NonCachedFiles, 1)

View on GitHub (pinned to 82495e54b5)