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
- Route the copy to the service matching the source path's scheme (detect via the prefix before "://")
- Fix the storage provider configuration so records and service agree (STORAGE_PROVIDER / backend selection)
- Download from the source backend and re-upload via SaveFile if a manual cross-backend copy is truly needed
- 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
- Detect the source scheme before choosing the copy service
- Keep STORAGE_PROVIDER config consistent with where records were written
- Handle the ErrCrossBackendCopy sentinel explicitly in copy workflows
- For migrations, implement an explicit download-then-SaveFile path
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
- invalid source path: %w
- model ID cannot be empty
- model is currently downloading
- model download failed
- abnormal model status
AI-assisted analysis of Tencent/WeKnora@988cbb0330 (2026-09-02).
Data as JSON: /api/errors/6789ab131d551bbe.
Report an issue: GitHub.