Tencent/WeKnora · error

local file service cannot copy %q: %w

Error message

local file service cannot copy %q: %w

What it means

CopyFile refuses to copy a source path whose provider scheme is not local:// (e.g. s3://, minio://). The local file service only handles local storage; a cross-backend copy would require downloading and re-uploading through another provider, which it does not implement. The sentinel ErrCrossBackendCopy is wrapped so callers can detect this case with errors.Is.

Source

Thrown at internal/application/service/file/local.go:168

		return fmt.Errorf("failed to delete file: %w", err)
	}

	logger.Info(ctx, "File deleted successfully")
	return nil
}

// CopyFile copies an existing local object to a new knowledge-owned object.
// The destination uses the same layout as SaveFile (baseDir/{tenantID}/{knowledgeID}/{unique}{ext}),
// and the copy is a real byte-for-byte copy (no hardlink) so deleting the source
// never affects it. Returns ErrCrossBackendCopy when srcPath is not a local path.
func (s *localFileService) CopyFile(ctx context.Context,
	srcPath string, tenantID uint64, knowledgeID string,
) (string, error) {
	// Only local paths are accepted. A provider scheme other than local://
	// (e.g. s3://, minio://) means a cross-backend copy, which this service
	// does not support. Legacy bare/absolute paths have no scheme and pass.
	if i := strings.Index(srcPath, "://"); i >= 0 && srcPath[:i+3] != localScheme {
		return "", fmt.Errorf("local file service cannot copy %q: %w", srcPath, ErrCrossBackendCopy)
	}

	// Validate and resolve the source path under baseDir (same guard as GetFile).
	srcCandidate := s.normalizePathForBase(srcPath)
	srcResolved, err := secutils.SafePathUnderBase(s.baseDir, srcCandidate)
	if err != nil {
		logger.Errorf(ctx, "Path traversal denied for CopyFile src: %v", err)
		return "", fmt.Errorf("invalid source path: %w", err)
	}

	// Build destination path with the knowledge-owned layout.
	dir := filepath.Join(s.baseDir, fmt.Sprintf("%d", tenantID), knowledgeID)
	if _, err := secutils.SafePathUnderBase(s.baseDir, dir); err != nil {
		logger.Errorf(ctx, "Path traversal denied for CopyFile dir: %v", err)
		return "", fmt.Errorf("invalid path: %w", err)
	}
	if err := os.MkdirAll(dir, 0o755); err != nil {
		return "", fmt.Errorf("failed to create directory: %w", err)

View on GitHub (pinned to 988cbb0330)

Solutions

  1. Route the copy to the service matching the source path's scheme (detect via the prefix before "://")
  2. Fix the storage provider configuration so records and service agree (STORAGE_PROVIDER / backend selection)
  3. Download from the source backend and re-upload via SaveFile if a manual cross-backend copy is truly needed
  4. Check errors.Is(err, file.ErrCrossBackendCopy) in the caller to handle this case explicitly

Example fix

// before: wrong service for the scheme
copied, err := localSvc.CopyFile(ctx, record.StoragePath, tenantID, knowledgeID) // s3://...
// after: dispatch by scheme
var svc file.Service = localSvc
if strings.HasPrefix(record.StoragePath, "s3://") {
	svc = s3Svc
}
copied, err := svc.CopyFile(ctx, record.StoragePath, tenantID, knowledgeID)
Defensive patterns

Strategy: type-guard

Validate before calling

func isLocalScheme(p string) bool {
	i := strings.Index(p, "://")
	return i < 0 || p[:i+3] == "local://"
}

Type guard

func isLocalScheme(p string) bool {
	i := strings.Index(p, "://")
	return i < 0 || p[:i+3] == "local://"
}

Try / catch

copied, err := svc.CopyFile(ctx, src, tenantID, knowledgeID)
if errors.Is(err, file.ErrCrossBackendCopy) {
	// dispatch to the backend matching the scheme, or download+re-upload
}

Prevention

When it happens

Trigger: Calling CopyFile with srcPath containing a non-local scheme (strings.Index finds "://" and the prefix is not the local scheme) — typically when a knowledge record's storage path points at an S3/MinIO object while the local service was selected.

Common situations: Migration from S3 to local storage or vice versa; tenant records created under a different storage backend config; misconfigured STORAGE_PROVIDER defaulting to local while data lives in S3.

Related errors


AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02). Data as JSON: /api/errors/6789ab131d551bbe. Report an issue: GitHub.