#9911·rclone

iclouddrive (photos): names containing "/" can be listed but not addressed (FromStandardName re-encodes the slash)

Author: cybercyberzCreated Sep 12, 2026Updated Sep 12, 2026

Before you start

  • I have searched the forum and the existing issues for this problem.
  • I have tested with the latest beta or stable release and can still reproduce the problem.
  • This is a reproducible bug, not a usage question (questions belong on the forum).

Associated forum post URL

No response

What is the problem you are having with rclone?

On the iclouddrive backend with service = photos, rclone renames colliding filenames by appending the photo's record id. CloudKit record names are base64 and can contain /, so the generated filename contains a path separator. The file is listed normally, with the slash shown as the fullwidth solidus (U+FF0F), but it cannot then be addressed by that name:

operations/copyfile  srcFs=icphotos:PrimarySync/<album>
                     srcRemote=lp_image_ATOMfgPib/j3YAcQ+gVIIrNhkzpB.heic
  -> {"error": "object not found", "status": 404}

The same file copies fine through a sync, which never resolves a name. A sync copies the objects the listing yields:

sync/copy  srcFs=icphotos:PrimarySync/<album>  dstFs=/tmp/slashtest
           _filter={"IncludeRule": ["/lp_image_ATOMfgPib/j3YAcQ+gVIIrNhkzpB.heic"]}
  -> success, 1 transfer, 22467 bytes, 0 errors

In one album, 2,195 of 9,933 files are affected, and every one of them is unreachable by name.

This is the same root cause as #9464 (albums with a slash in their name), which is still open. That issue reports it for directories; this one reports it for files, where the slash is introduced by rclone itself rather than by the user's album naming.

The deployment drives rclone through rcd, so most of the evidence here is from the rc API.

Listing shows the file:

{"Path": "lp_image_ATOMfgPib/j3YAcQ+gVIIrNhkzpB.heic",
 "Name": "lp_image_ATOMfgPib/j3YAcQ+gVIIrNhkzpB.heic",
 "Size": 22467, "MimeType": "image/heif",
 "ModTime": "2024-05-17T15:12:12+02:00", "IsDir": false}

operations/copyfile on that exact name, with the destination pointed at local disk so no second remote is involved:

{"error": "object not found",
 "input": {"srcFs": "icphotos:PrimarySync/<album>",
           "srcRemote": "lp_image_ATOMfgPib/j3YAcQ+gVIIrNhkzpB.heic",
           "dstFs": "/tmp/slashtest",
           "dstRemote": "lp_image_ATOMfgPib/j3YAcQ+gVIIrNhkzpB.heic"},
 "path": "operations/copyfile", "status": 404}

Same result whether the album is part of the Fs root (icphotos:PrimarySync/<album> plus the leaf name) or part of the remote path (icphotos: plus PrimarySync/<album>/<name>).

Expected behaviour: a name that appears in a listing can be used to address that object.

rclone version

First observed on Linux:

rclone v1.75.1
- os/version: ubuntu 24.04 (64 bit)
- os/kernel: 6.8.0-138-generic (x86_64)
- os/type: linux
- os/arch: amd64
- go/version: go1.26.8

The -vv log below is from a second reproduction on macOS, on a different album and driven from the CLI rather than through rcd:

rclone v1.74.3
- os/version: darwin 15.7.7 (64 bit)
- os/kernel: 24.6.0 (arm64)
- os/type: darwin
- os/arch: arm64 (ARMv8 compatible)
- go/version: go1.26.4
- go/linking: dynamic
- go/tags: cmount

The relevant code is unchanged on master (812e693).

Operating system

Linux (also reproduced on macOS, log below)

Which cloud storage system are you using?

iCloud Photos, using the iclouddrive backend with service = photos.

Output of rclone config redacted

[icphotos]
type = iclouddrive
service = photos
apple_id = XXX
_auth_session = 
password = XXX
cookies = XXX
trust_token = XXX

The command you were trying to run

rclone lsf "icphotos:PrimarySync/Live" | grep '/' | head -1
rclone -vv copy "icphotos:PrimarySync/Live/FullSizeRender_AS9ueJXBbemIxo0zLZ5s/HtDNEmd.heic" /tmp/slashtest
rclone -vv copy "icphotos:PrimarySync/Live" /tmp/slashtest --include "/FullSizeRender_AS9ueJXBbemIxo0zLZ5s/HtDNEmd.heic"

In that Live album, rclone lsf ... | grep -c '/' reports 780 affected files out of 9,372 listed.

A log from the command with the -vv flag

Addressing the file by name fails. The NewObject debug line shows the lookup being made with the fullwidth solidus still in place, which is the bug:

$ rclone -vv copy "icphotos:PrimarySync/Live/FullSizeRender_AS9ueJXBbemIxo0zLZ5s/HtDNEmd.heic" /tmp/slashtest
DEBUG : iclouddrive photos: zone PrimarySync unchanged, using cached listings
DEBUG : iclouddrive photos: 9372 items from cache for "Live"
DEBUG : iCloud Photos root 'PrimarySync/Live': NewObject(FullSizeRender_AS9ueJXBbemIxo0zLZ5s/HtDNEmd.heic): photo "FullSizeRender_AS9ueJXBbemIxo0zLZ5s/HtDNEmd.heic" not found in album "Live"
DEBUG : Creating backend with remote "/tmp/slashtest"
ERROR : iCloud Photos root 'PrimarySync/Live/FullSizeRender_AS9ueJXBbemIxo0zLZ5s/HtDNEmd.heic': error reading source root directory: directory not found
ERROR : Attempt 1/3 failed with 1 errors and: directory not found
ERROR : Attempt 2/3 failed with 1 errors and: directory not found
ERROR : Attempt 3/3 failed with 1 errors and: directory not found
NOTICE: Failed to copy: directory not found

The same file through a listing, with a filter instead of a name lookup, transfers fine:

$ rclone -vv copy "icphotos:PrimarySync/Live" /tmp/slashtest --include "/FullSizeRender_AS9ueJXBbemIxo0zLZ5s/HtDNEmd.heic"
DEBUG : FullSizeRender_AS9ueJXBbemIxo0zLZ5s/HtDNEmd.heic: Need to transfer - File not found at Destination
DEBUG : FullSizeRender_AS9ueJXBbemIxo0zLZ5s/HtDNEmd.heic.f15bb91a.partial: size = 1031139 OK
INFO  : FullSizeRender_AS9ueJXBbemIxo0zLZ5s/HtDNEmd.heic: Copied (new)
Transferred:   	 1006.972 KiB / 1006.972 KiB, 100%, 0 B/s, ETA -
Checks:                 0 / 0, -, Listed 9372
Transferred:            1 / 1, 100%

Anything else?

The slash is the trigger, not the renaming

Renamed files are fine as long as their record id has no slash in it. From the same album, same listing, same run:

file result
lp_image_ATOMfgPib/j3YAcQ+gVIIrNhkzpB.heic (id contains /) object not found
lp_image_ASSF+TOD9orbqwbHjoX+8epk+jGc.heic (id, no /) copied OK
lp_image_AaLMxhsv8uIaBhtjMHzyBo3FA+1j.heic (id, no /) copied OK
Image_Ab9RCvKZf8Ze+u2NmyOdpCIFoQxK.png (id, no /) copied OK
Image 17-6-2026 at 9.40 pm.jpg (ordinary name) copied OK

So this is not about the renaming, and not about + or other base64 characters. Only the slash matters.

Where the round trip breaks

ToStandardName and FromStandardName are not inverses when the backend's raw name contains a character that the backend's own encoder encodes. From lib/encoder/encoder.go:

func FromStandardName(e Encoder, s string) string { return e.Encode(Standard.Decode(s)) }
func ToStandardName(e Encoder, s string) string   { return Standard.Encode(e.Decode(s)) }

The iclouddrive default encoding is Display | EncodeBackSlash | EncodeSlash | EncodeInvalidUtf8 (backend/iclouddrive/icloud.go), and Display == Standard, which already contains EncodeSlash. So for a raw name holding a literal /:

  • ToStandardName: e.Decode leaves the literal / alone, Standard.Encode turns it into . The listing shows . Correct.
  • FromStandardName: Standard.Decode turns back into /, and then e.Encode, which has EncodeSlash set, immediately turns it into again.

So PhotosFs.NewObject looks up lp_image_…/….heic while album.photoCache is keyed on the raw lp_image_…/….heic, and the lookup misses. List never does the reverse conversion, which is why sync works.

A standalone test in lib/encoder reproduces it with no iCloud account:

func TestSlashRoundTrip(t *testing.T) {
	raw := "lp_image_ATOMfgPib/j3YAcQ+gVIIrNhkzpB.heic"
	for _, tc := range []struct {
		name string
		e    MultiEncoder
	}{
		{"current default", MultiEncoder(Display | EncodeBackSlash | EncodeSlash | EncodeInvalidUtf8)},
		{"minus EncodeSlash", MultiEncoder((Display | EncodeBackSlash | EncodeInvalidUtf8) &^ EncodeSlash)},
	} {
		listed := tc.e.ToStandardName(raw)
		back := tc.e.FromStandardName(listed)
		t.Logf("%-18s listed=%q back=%q roundtrip_ok=%v", tc.name, listed, back, back == raw)
	}
}
current default    listed="lp_image_ATOMfgPib/j3YAcQ+gVIIrNhkzpB.heic" back="lp_image_ATOMfgPib/j3YAcQ+gVIIrNhkzpB.heic" roundtrip_ok=false
minus EncodeSlash  listed="lp_image_ATOMfgPib/j3YAcQ+gVIIrNhkzpB.heic" back="lp_image_ATOMfgPib/j3YAcQ+gVIIrNhkzpB.heic" roundtrip_ok=true

Note that the listed name is identical in both cases, so dropping EncodeSlash from the backend default would not change any name rclone shows or writes. It only restores the reverse direction. Since Display already implies EncodeSlash, clearing it needs &^, not just removing the explicit term.

EncodeBackSlash has the same defect in the other direction: a raw name containing \ is listed as \ (because Standard does not encode backslash) but looked up as .

Two places this could be fixed

  1. The encoding default in backend/iclouddrive/icloud.go. This is what #9464 needs, and it also fixes the file case here.
  2. deduplicateFilenames in backend/iclouddrive/api/photos.go synthesises the name as base + "_" + p.ID + ext, where p.ID is master.RecordName. Even with the encoder fixed, it seems worth not minting a name containing a path separator in the first place. I have not proposed a specific sanitisation because it has to stay stable and collision-free.

I am happy to send a PR for whichever of these you prefer.

Supporting data on the renaming

All 2,195 affected names parse as <base>_<id><ext>; none of them still has a bare <base><ext> present in the listing (consistent with every colliding copy being renamed); and 1,082 of the 1,109 distinct ids are shared by exactly two files differing only in extension: lp_image_<id>.heic and lp_image_<id>.MOV, the two components of one Live Photo. The album holds roughly eleven hundred Live Photos all delivered as lp_image.heic / lp_image.MOV, which is what triggers the mass rename.

Note: operations/stat succeeds and is misleading

operations/stat on the failing path returns the item happily. That is not evidence the lookup works: StatJSON in fs/operations/lsjson.go tries NewObject first and then falls back to listing the parent and matching the leaf name, so it answers from the listing.

How I ruled out stale state

fscache/clear before the run, and fscache/entries reported 0 both before and after; the album was then listed fresh (4.6 s) and the copy failed on that fresh listing. I did not clear the on-disk cache under ~/.cache/rclone/iclouddrive-photos/, but staleness cannot explain the result either way: within a single listing, files split cleanly by whether their id contains a slash.