mgth/LittleBigMouse · error · InvalidOperationException
The live configuration was applied, but crash-recovery…
Error message
The live configuration was applied, but crash-recovery settings could not be saved.
What it means
LittleBigMouseClientService.SendMessagesAsync sends live commands over the IPC connection and then asks a recovery store (_recovery.PersistAsync) to persist crash-recovery state. PersistAsync does not throw on failure — it returns the exception as persistenceFailure — so the live configuration is already applied when this InvalidOperationException is thrown, wrapping the underlying persistence exception as InnerException. The message stresses that the client state and the daemon state are now out of sync only with respect to what happens after a crash.
Solutions
- Inspect InnerException (persistenceFailure) to see the real IO error and fix the root cause — free disk space, repair permissions, or point the recovery store at a writable directory.
- Verify the crash-recovery file's directory is writable by the running user (ls -l on the config dir; chown/chmod if needed).
- Delete a corrupted recovery/persistence file so the store can recreate it, then resend the configuration.
- If the loss of crash-recovery persistence is acceptable, catch InvalidOperationException around Send* calls and continue — the live configuration was applied regardless.
Example fix
// before
await client.SendLiveAsync(state); // throws after apply when persistence fails
// after
try { await client.SendLiveAsync(state); }
catch (InvalidOperationException ex)
{
_logger.Warn(ex.InnerException,
"Config applied but crash-recovery not saved; check disk/permissions for the recovery file");
} Defensive patterns
Strategy: try-catch
Validate before calling
// before sending, check the recovery store is writable
var recoveryPath = recoveryStore.FilePath;
if (File.Exists(recoveryPath) && File.GetAttributes(recoveryPath).HasFlag(FileAttributes.ReadOnly))
throw new InvalidOperationException("Recovery file is read-only; fix before sending live config"); Type guard
bool RecoveryStoreWritable(ILittleBigMouseRecovery recovery) =>
{
try
{
var probe = Path.Combine(Path.GetDirectoryName(recovery.FilePath)!, ".write-probe");
File.WriteAllText(probe, "ok"); File.Delete(probe);
return true;
}
catch (IOException) { return false; }
catch (UnauthorizedAccessException) { return false; }
}; Try / catch
try { await client.SendMessagesAsync(commands); }
catch (InvalidOperationException ex) when (ex.InnerException is not null)
{ _logger.Warn(ex.InnerException, "Config applied; crash-recovery save failed — check disk space/permissions"); } Prevention
- Keep the app config directory writable by the running user; avoid running as a different user than the installer.
- Monitor free disk space in the config volume before pushing large live configurations.
- Verify sandbox/Flatpak permissions include the recovery file path.
- Always inspect InnerException — it carries the real IO failure from PersistAsync.
When it happens
Trigger: Calling SendMessagesAsync (directly or via StartAsync, SendLiveAsync, SendShortcutAsync, SendMessageAsync) when the recovery store cannot write its persistence file — disk full, read-only config directory, permission denied on the recovery file, or serialization/IO failure inside PersistAsync.
Common situations: Running LittleBigMouse with a read-only or full disk; config directory owned by another user; Flatpak/sandbox restricting writes to the app config path; the recovery file locked by another process; a corrupted recovery store causing the write to fail.
Understand the failure class
Background: "failed to write file", "Could not save figure", "Error saving remote file" — file write failed: causes and fixes across languages and libraries — this error's family across 38 libraries.
Related errors
AI-assisted analysis of mgth/LittleBigMouse@7a42f01d47 (2026-09-16).
Data as JSON: /api/errors/a2da62d435384f08.
Report an issue: GitHub.
Appendix: source
Thrown at LittleBigMouse.Ui/LittleBigMouse.Ui.Avalonia/Remote/LittleBigMouseClientService.cs:208
}
/// <param name="persist">
/// False for a send the daemon should run but not remember (live preview): the
/// recovery file keeps describing the last applied layout. Serializing a layout is
/// the expensive half of a send, so the recovery copy is only built when it is
/// actually going to be written.
/// </param>
async Task SendMessagesAsync(IEnumerable<CommandMessage> messages,
CancellationToken token = default, int timeout = 5000, bool persist = true)
{
var commands = messages.ToList();
var wireXml = $"<Messages>{string.Concat(commands.Select(command => command.Serialize()))}</Messages>";
var persistenceFailure = await _recovery.PersistAsync(commands, persist, token);
await _client.SendMessageAsync(wireXml, TimeSpan.FromMilliseconds(timeout), token);
if (persistenceFailure is not null)
throw new InvalidOperationException(
"The live configuration was applied, but crash-recovery settings could not be saved.",
persistenceFailure);
}
public void Dispose()
{
_client.Dispose();
_processManager.Dispose();
GC.SuppressFinalize(this);
}
}
View on GitHub (pinned to 7a42f01d47)