#3068·seafile

Windows: files with the read-only attribute can never be deleted from a synced library

Author: dragonofmercyCreated Aug 20, 2026Updated Aug 20, 2026

Summary

On Windows, the client calls DeleteFileW() directly at every worktree deletion site. DeleteFileW() returns ERROR_ACCESS_DENIED (5) for any file carrying the FILE_ATTRIBUTE_READONLY attribute, so such a file can never be removed from a synced library. The deletion fails permanently and the enclosing directory is moved to seafile-recycle-bin instead.

Neither SetFileAttributesW nor FILE_ATTRIBUTE_READONLY appears anywhere in this repository, so the attribute is never cleared on any code path.

Environment

  • Seafile client 9.0.20 on Windows 11
  • Observed continuously over 11 months on the same client

What happens

[08/10/26 06:30:19] Failed to delete file Windows/PrintAgent/build/.git/objects/e6/848e3b...: 5.
[08/10/26 06:30:19] Moved folder D:/Seafile/.../.git/objects/e6 to Seafile recycle bin D:/Seafile/recycle-bin/e6.
[08/10/26 06:30:23] Repo 'Developpements' sync is finished but with error: A folder that may
                    contain not-yet-uploaded files is moved to seafile-recycle-bin folder

Counts from a single client log:

Log message Call site Occurrences
Failed to delete file %s: %lu delete_worktree_dir_recursive_win32() 6676
Failed to remove %s: %s seaf_util_unlink() 1233
Moved folder ... to Seafile recycle bin fallback path 829

Every one of the 6676 is error 5, and every affected path is a read-only file.

Impact

The last log line is the real concern. The fallback moves the whole directory out of the library, and the client itself warns that it may contain files not yet uploaded. For content whose only copy is the library, that is data loss rather than a failed deletion.

Affected call sites

File Function
lib/utils.c seaf_util_unlink()
daemon/repo-mgr.c delete_worktree_dir_recursive_win32()
daemon/vc-utils.c remove_hidden_file()

Why git repositories trigger this reliably

Git creates every loose object and pack file read-only. The mode is hardcoded in odb_mkstemp() and is not configurable:

c
	/*
	 * we let the umask do its job, don't try to be more
	 * restrictive except to remove write permission.
	 */
	int mode = 0444;

core.sharedRepository cannot relax it either, since calc_shared_perm() masks out every write bit when the file has no owner write bit to begin with.

So any git repository stored in a library is affected. As soon as one client repacks it, the resulting deletions reach every other Windows client and none of them can apply a single one. Over 11 months this hit 7 different repositories on this one client.

Git handles the same case on its side, in compat/mingw.c:

c
	do {
		/* read-only files cannot be removed */
		_wchmod(wpathname, 0666);
		if (try_delete_file(wpathname, use_legacy_delete))
			return 0;

Steps to reproduce

Nothing git-specific is required:

  1. In a library synced by two Windows clients A and B, create a file and run attrib +R on it from A.
  2. Let both clients sync.
  3. Delete the file from B, or from the web interface.
  4. A logs Failed to delete file <path>: 5 and moves the parent directory into seafile-recycle-bin.

Proposed fix

PR #3065: https://github.com/haiwen/seafile/pull/3065

It adds a win32_delete_file() helper that clears the attribute and retries, restoring it if the deletion still fails, and preserving GetLastError() so the existing warnings keep reporting the true cause. The three call sites above are routed through it.