The stored profile list is read an entry at a time. A client is not always the newest thing that has written profiles.json — an older release run again, a machine two versions behind, a file carried over from another install — and what it meets there is a server whose backend it has no case for. Decoding the file as one array threw on that single entry and the throw was the whole list: every server disappeared from a read that swallowed it, and the next save failed with a raw decoding error instead of saving.
ProfileFile reads each entry on its own and keeps the ones that fail as the bytes they were, so the list a build understands is complete and the entry it does not is written back in place — a version that cannot talk to a server must not also be the reason it is gone. Malformed bytes still throw, because a file that is not a list at all is a read failure and answering one with an empty list would let the next save rebuild it from nothing.