stride3d/stride · error · InvalidOperationException

Read-only backend.

Error message

Read-only backend.

What it means

FileOdbBackend.CreateStream writes new objects to the database by creating a temp file and moving it into the store on dispose. When the backend was opened read-only (IsReadOnly), any write attempt throws InvalidOperationException 'Read-only backend.'.

Solutions

  1. Open the object database backend writable (IsReadOnly = false) wherever writes are needed, e.g. in editor/user-data storage.
  2. Route writes to a separate writable backend (e.g. a user-profile store) instead of the read-only game-data backend.
  3. Check IsReadOnly before calling CreateStream and disable or redirect the write path in shipped builds.

Example fix

// before
using var writer = readOnlyBackend.CreateStream(); // throws
// after
if (backend.IsReadOnly)
    backend = new FileOdbBackend(writableProvider, writablePath, isReadOnly: false);
using var writer = backend.CreateStream();
Defensive patterns

Strategy: validation

Validate before calling

if (backend.IsReadOnly)
    throw new InvalidOperationException("Cannot create streams on a read-only object database backend.");
using var writer = backend.CreateStream();

Try / catch

try
{
    using var writer = backend.CreateStream();
    // write object...
}
catch (InvalidOperationException ex) when (ex.Message == "Read-only backend.")
{
    backend = OpenWritableBackend(); // switch to a writable store
}

Prevention

When it happens

Trigger: Calling CreateStream on a FileOdbBackend constructed/mounted with IsReadOnly set — e.g. shipped game data opened read-only, then code (or an asset import path) tries to store new objects.

Common situations: Running a game built with read-only data mounts while an in-game importer/editor feature attempts to save; writing to installed Program Files or read-only media; forgetting to open a writable backend in tooling.

Related errors


AI-assisted analysis of stride3d/stride@96fad776d2 (2026-09-14). Data as JSON: /api/errors/dacff729d03c91a2. Report an issue: GitHub.

Appendix: source

Thrown at sources/core/Stride.Core.Serialization/Storage/FileOdbBackend.cs:132

        if (!forceWrite && virtualFileProvider.FileExists(url))
            return objectId;

        using (var file = virtualFileProvider.OpenStream(tmpFileName, VirtualFileMode.Create, VirtualFileAccess.Write))
        {
            dataStream.CopyTo(file);
        }

        MoveToDatabase(tmpFileName, objectId, forceWrite);

        return objectId;
    }

    /// <inheritdoc/>
    public OdbStreamWriter CreateStream()
    {
        if (IsReadOnly)
            throw new InvalidOperationException("Read-only backend.");

        string tmpFileName = vfsTempUrl + Guid.NewGuid() + ".tmp";
        Stream stream = virtualFileProvider.OpenStream(tmpFileName, VirtualFileMode.Create, VirtualFileAccess.Write);
        return new DigestStream(stream, tmpFileName) { Disposed = x => MoveToDatabase(x.TemporaryName, x.CurrentHash) };
    }

    private void MoveToDatabase(string temporaryFilePath, ObjectId objId, bool forceWrite = false)
    {
        string fileUrl = BuildUrl(vfsRootUrl, objId);

        lock (LockOnMove)
        {
            var fileExists = virtualFileProvider.FileExists(fileUrl);

            // File may already exists, in this case we decide to not override it.
            if (!fileExists || forceWrite)
            {
                try

View on GitHub (pinned to 96fad776d2)