BigPizzaV3/CodexPlusPlus · error

failed to allocate a stale Dream Skin backup path

Error message

failed to allocate a stale Dream Skin backup path

What it means

isolate_stale_backup moves an orphaned Dream Skin base-theme backup (dream-skin-base-theme-backup.json) out of the way by renaming it to a timestamped *.stale-*.json path. It tries the plain timestamped name and up to 100 numbered variants; if all 100 candidate names already exist on disk, it gives up with this bail. It is a defensive exhaustion error — effectively 'cannot find a free filename for the stale backup after 100 attempts'.

Solutions

  1. List and delete (or archive) old dream-skin-base-theme-backup.stale-*.json files in the codex config directory to free up candidate names.
  2. Re-run the apply/restore operation — a new millisecond timestamp changes the candidate names, so this is almost always transient.
  3. Check the directory is writable and not a weird mount; verify with a manual rename of the stale backup file.
  4. If it recurs, manually move the original dream-skin-base-theme-backup.json aside yourself, then retry.

Example fix

// before (all 100 candidate names taken)
bail!("failed to allocate a stale Dream Skin backup path")
// after (user-side recovery)
// rm ~/.codex/dream-skin-base-theme-backup.stale-*.json && retry apply_base_theme
Defensive patterns

Strategy: try-catch

Validate before calling

let stale_count = std::fs::read_dir(config_dir)?
    .filter_map(|e| e.ok())
    .filter(|e| e.file_name().to_string_lossy().starts_with("dream-skin-base-theme-backup.stale-"))
    .count();
if stale_count > 90 { /* clean up stale backups before applying */ }

Try / catch

match apply_base_theme(...) {
    Err(e) if e.to_string().contains("failed to allocate a stale Dream Skin backup path") => {
        // purge dream-skin-base-theme-backup.stale-*.json, then retry once
    }
    other => other?,
}

Prevention

When it happens

Trigger: Called from apply_base_theme or restore_base_theme when the existing backup file's identity is BackupIdentity::Stale (a leftover backup pointing at a different, now-nonexistent config path). The bail fires only when all 100 candidate names dream-skin-base-theme-backup.stale-{timestamp}{,-1..-99}.json already exist in the same directory.

Common situations: An extremely fast loop re-applying themes many times within the same millisecond timestamp while leftover stale backups accumulate; a directory flooded with thousands of stale backup files from repeated crashed/failed applies; a filesystem where exists() misreports (e.g. permission-restricted directory or unusual mounts) so every candidate appears to exist.

Understand the failure class

Background: "already exists" / EEXIST / FileAlreadyExistsException: what the 'file already exists' error means and how to fix it — this error's family across 37 libraries.

Related errors


AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19). Data as JSON: /api/errors/dfd6c1148c68504e. Report an issue: GitHub.

Appendix: source

Thrown at crates/codex-plus-core/src/dream_skin.rs:627

            String::new()
        } else {
            format!("-{attempt}")
        };
        let isolated = backup_path.with_file_name(format!(
            "dream-skin-base-theme-backup.stale-{timestamp}{suffix}.json"
        ));
        if isolated.exists() {
            continue;
        }
        std::fs::rename(backup_path, &isolated).with_context(|| {
            format!(
                "failed to isolate stale Dream Skin backup {}",
                backup_path.display()
            )
        })?;
        return Ok(isolated);
    }
    bail!("failed to allocate a stale Dream Skin backup path")
}

fn read_config_or_empty(path: &Path) -> anyhow::Result<String> {
    match std::fs::read_to_string(path) {
        Ok(contents) => Ok(contents),
        Err(error) if error.kind() == std::io::ErrorKind::NotFound => Ok(String::new()),
        Err(error) => Err(error).with_context(|| format!("failed to read {}", path.display())),
    }
}

fn parse_config(contents: &str, path: &Path) -> anyhow::Result<DocumentMut> {
    contents
        .parse::<DocumentMut>()
        .with_context(|| format!("failed to parse {}", path.display()))
}

fn write_config(path: &Path, bytes: &[u8]) -> anyhow::Result<()> {
    crate::settings::atomic_write(path, bytes).with_context(|| {

View on GitHub (pinned to b1ed92e5e4)