Space export silently drops synced images and media from the backup zip in local only mode

Severity: Critical — This bug silently produces incomplete backups. The export dialog shows every option enabled, the export finishes with a success message, the resulting zip looks normal at first glance, but the files folder inside is missing media that is clearly visible inside the app. There is no warning, no error, no failed file count, nothing. A user who trusts this export as a backup will only discover the gap when they try to restore. Because the operation is presented as successful and complete, this is effectively silent data loss in a backup context.


WHAT IS THE BUG

When a space contains images or other media that were synced from another device, exporting that space produces a zip archive that silently omits those synced files. The files folder of the export only contains files that were originally uploaded on the same device doing the export. Media that was uploaded on a peer device and then synced over is skipped entirely.

The bug is format independent. It reproduces with Markdown, Any-Block (Protobuf), and JSON exports. It reproduces with every option enabled: Zip archive, Include linked objects, Include files, Include archived objects.

The export finishes without any error. The completion dialog reports success. The only way to notice the gap is to manually unzip the archive and compare the files folder against what the app actually shows.

This makes the export feature unreliable for backup in any multi-device setup. A user who performs this export believing they have a full backup will lose access to their synced media if they later wipe the source device.

HOW TO REPRODUCE IT

  1. Set up two devices in the same Local-only vault with P2P sync working between them. In this case the setup was an Android phone acting as a hotspot in airplane mode with a VPN app like Rethink in firewall mode to keep images and other media being displayed immediately (seen here), and an Arch Linux laptop connected to that hotspot running the Flathub/Flatpak Anytype desktop client. Both clients show Connected.
  2. On the Android phone, open a page (for example the Welcome page) and insert several images from the gallery.
  3. Wait until the desktop client displays those images inline in the page and they also appear in the Images system collection. Confirm manually the sync has finished since the status indicator in local mode is still not supported.
  4. Disconnect from the Wifi hotspot on the desktop
  5. On the desktop, open the space menu and choose Export.
  6. Pick Markdown or any other as the format. Enable Zip archive, Include linked objects, Include files, and Include archived objects. Click Export.
  7. When the export finishes, open the resulting zip and look inside the files folder.
  8. The images synced from the Android phone are missing. Only files originally uploaded on the desktop are present.

THE EXPECTED BEHAVIOR

  • A space export with Include files enabled must contain every file object that is visible in the app for that space. It must not matter which device originally uploaded the file, and it must not matter whether the file’s content was fetched on demand from a peer or was always local.
  • If a file cannot be read for any reason during export, the export must fail loudly. It should either abort with a clear error, or at minimum pop up a non-dismissable warning at the end listing the file objects that could not be exported. Silently omitting files from a backup is unacceptable.
  • If a file is displayed correctly inside the app, the exporter must be able to read the same file. Any inconsistency between what the app can display and what the exporter can read is a bug.
  • There is no sync indicators for local only mode, this is a guessing game, are the files / objects synced?! are they not…

ADDITIONAL CONTEXT

Why synced media specifically is affected if both devices arent connected

When a file is uploaded on device A and synced to device B, two things travel separately. The file’s metadata (its object, encryption keys, name, size, mime type) syncs immediately through the tree and lands in the local object store. The actual file content, meaning the encrypted bytes, is not pushed. It is fetched on demand the first time device B needs to display the file.

This on-demand fetch goes through a local-peer lookup over mDNS, with a short timeout and a 5 minute ban on peers that fail to respond. The fetched bytes are cached locally so that subsequent displays are instant. But that cache is not permanent. It can be evicted, the peer can be temporarily banned, the peer list can be stale, or the original variant of an image may never have been requested because the app only fetched the resized variant for display.

Inside the app this is invisible because the image loader retries indefinitely until the peer delivers the block, and the user just sees the image appear after a moment. The exporter does not retry the same way. It tries to read the file once, and if the local cache misses and the peer fetch fails on that single attempt, the exporter logs a warning and moves on to the next file. The file is not written to the archive. The overall export is still reported as successful.

Why the bug is format independent

Markdown, Protobuf, and JSON exports all share the same file saving path. The only difference between formats is whether the file object’s own metadata document is written to the archive. The binary file content is written by the same code path for all three formats, so the bug reproduces identically regardless of which format is selected.

Why this is critical

The export feature is the primary user-facing backup mechanism. The completion dialog explicitly says success. There is no tooltip, no log surfaced to the UI, no partial count, no warning icon. The only way a user discovers the gap is by manually inspecting the zip, which most users will never do for every export. In a backup workflow this is silent data loss. The user believes they have a complete copy of their space, deletes the source, and only later discovers the synced media is gone.

Workaround

The export works successfully ONLY when the two devices are connected to each other at the moment of export. No warm-up is needed and no pages need to be open. When both devices are online and the P2P sync link between them is alive, the exporter fetches the missing file blocks from the peer on demand and the resulting zip is complete. The moment the peer is offline, unreachable, or the sync link is down, the export silently drops every file that was not already fully cached locally.

Additional notes:

  • After every export, manually diff the files folder against the Images system collection to confirm nothing was dropped.
  • For a true offline backup today, copy the entire Anytype data directory instead of using the in-app export. This is undocumented and not user friendly but is the only option that guarantees file content is included without needing the peer online.

Suggested fix direction

  1. The exporter must not silently skip files. When a file cannot be read, the export should either abort with a clear error listing the affected file objects, or complete but surface a non-dismissable warning at the end with the list of files that could not be exported.
  2. The exporter should use the same wait-for-available behavior that the in-app image loader uses, so the on-demand peer fetch has a chance to complete before the exporter gives up.
  3. The sync status indicator should distinguish tree sync from file content sync. Synced should mean both the tree heads match and all referenced file content is present locally.
  4. The export completion dialog should report the number of objects exported versus the total, so a user immediately sees something like Exported 47 of 52 objects, 5 files could not be read instead of a flat Export complete.
  5. Local store garbage collection and the file offloader should not evict content for file objects that still exist in the space. Otherwise the app can display an image once, the content gets evicted, and the next display or export silently fails.

TECHNICAL INFORMATION

Desktop (where export is performed):

Device: Arch Linux (Flathub/Flatpak build)
App version: 0.55.5

Source device (where the synced media was originally uploaded):

Android version: 33
App version: 0.47.3
Build number: 4703
Library version: 0.50.8

Network setup:

  • Android hotspot in airplane mode, no cellular, no upstream Wi-Fi
  • A VPN app like Rethink in firewall mode on Android to keep images and other media immediately displayed (seen here)
  • Arch Linux laptop connected to the Android hotspot as a Wi-Fi client
  • Both Anytype clients in the same Local-only vault, P2P sync over the hotspot

Export settings used:

  • Format: Markdown (also reproduced with Any-Block / Protobuf and JSON)
  • Zip archive: enabled
  • Include linked objects: enabled
  • Include files: enabled
  • Include archived objects: enabled

CODE-LEVEL ANALYSIS (may not be perfectly accurate)

The exporter calls file.Reader(context.Background()) with no retry and no wait-for-available, unlike the gateway which uses infinite retry. When a file’s blocks are not in localStore (because they were never fetched, only the resized variant was cached, or the cache was evicted), the exporter must fetch from a peer via getFromLocalPeers. If the peer is offline, this fails immediately. The exporter logs a warning and silently skips the file. The RPC returns success because the error is captured in a local variable and logged, not returned. The frontend only checks the error code, not the succeed count, so the user sees “Export complete” with no indication that files were dropped. Both devices being connected ensures the peer is reachable for on-demand block fetch during export.

Thanks for reporting this. We’ll take a look at it.