commaai/openpilot · error

failed to save Cabana settings to %s: %s

Error message

failed to save Cabana settings to %s: %s

What it means

Catch-all failure report at the end of saveSettings() in tools/cabana/settings.cc. It fires when any stage of the atomic save after temp-file creation failed: writeAll() (partial/failed write), fsync(fd), close(fd), rename(temp -> cabana.json), or the directory open/fsync that durably records the rename. The temp file is unlinked and false returned. Note the errno printed is whatever errno happened to hold, which after close/fsync failures may not identify the first failing step.

Source

Thrown at openpilot/tools/cabana/settings.cc:132

  if (fd < 0) {
    fprintf(stderr, "failed to create temporary Cabana settings %s: %s\n", temporary_path.c_str(), strerror(errno));
    return false;
  }

  bool success = writeAll(fd, contents) && fsync(fd) == 0;
  if (close(fd) < 0) success = false;
  if (success && rename(temporary_path.c_str(), path.c_str()) < 0) success = false;

  if (success) {
    int dir_fd = open(path.parent_path().c_str(), O_RDONLY | O_CLOEXEC);
    success = dir_fd >= 0 && fsync(dir_fd) == 0;
    if (dir_fd >= 0 && close(dir_fd) < 0) success = false;
  }

  if (!success) {
    const int saved_errno = errno;
    unlink(temporary_path.c_str());
    fprintf(stderr, "failed to save Cabana settings to %s: %s\n", path.c_str(), strerror(saved_errno));
  }
  return success;
}

bool preserveCorruptSettings() {
  const auto path = settingsFile();
  auto backup = path;
  backup += ".corrupt";
  for (int i = 1; std::filesystem::exists(backup); ++i) {
    backup = path;
    backup += ".corrupt." + std::to_string(i);
  }
  if (rename(path.c_str(), backup.c_str()) < 0) {
    fprintf(stderr, "failed to preserve corrupt Cabana settings %s: %s\n", path.c_str(), strerror(errno));
    return false;
  }
  fprintf(stderr, "preserved corrupt Cabana settings at %s\n", backup.c_str());
  return true;

View on GitHub (pinned to 516ec1e682)

Solutions

  1. Check disk space first: `df -h <configPath>` — ENOSPC during writeAll is the most frequent cause.
  2. If strerror is 'Input/output error', suspect the underlying storage/mount (fsck, remount, replace media).
  3. Verify the config directory still exists and is writable at save time; re-run ensureSettingsDirectory() before retrying.
  4. Retry the save — settings are written on change, so transient failures self-heal on the next modification once space is freed.

Example fix

# before
# stderr: failed to save Cabana settings to /home/user/.comma/cabana.json: No space left on device
df -h /home/user        # 100% full

# after
clean up space (journal, cores, old drives), then change any setting in cabana to force a re-save
Defensive patterns

Strategy: retry

Validate before calling

// Cheap pre-save check: space and writability of the target dir
#include <sys/statvfs.h>
bool dirReadyForAtomicSave(const std::filesystem::path &dir, size_t needed = 65536) {
  struct statvfs st;
  if (statvfs(dir.c_str(), &st) != 0) return false;
  return st.f_bavail * st.f_frsize > needed && (st.f_flag & ST_RDONLY) == 0;
}

Prevention

When it happens

Trigger: saveSettings() where mkstemp succeeded but one of these fails: write() loop returns <= 0 (ENOSPC, EIO), fsync() != 0, close() < 0, rename() < 0 (cross-device is impossible here since temp is in the same dir, but permission/dir-removed races apply), or open(parent_path, O_RDONLY)/fsync(dir_fd) fails. Any one flips `success` to false and reaches the fprintf with saved_errno captured just before the unlink.

Common situations: Disk fills up mid-write (ENOSPC in writeAll — most common); flaky USB/NFS mount returning EIO on fsync; config directory deleted between ensureSettingsDirectory and rename; errno ambiguity making the message misleading after the close/fsync branch.

Related errors


AI-assisted analysis of commaai/openpilot@516ec1e682 (2026-08-15). Data as JSON: /api/errors/ae7c52ce9196aa9b. Report an issue: GitHub.