a-b-street/abstreet · error · anyhow::Error
set_item for failed for encoded length
Error message
set_item for {path} failed for encoded length {} What it means
On wasm, abstio::write_raw persists bytes to browser localStorage, which only holds strings, so the payload is base64-encoded first. If the storage.set_item call returns a DomException without a readable message, the library formats this fallback message including the key path and encoded length. The most common underlying cause is the ~5MB localStorage quota being exceeded.
Solutions
- Reduce the size of the data written, or split it across multiple keys
- Use IndexedDB / OPFS instead of localStorage for large or binary data
- Detect quota errors (err.name == 'QuotaExceededError') and prompt the user to free space or fall back to a download
- Check encoded length before writing: base64 length ~4/3 of bytes; warn if near 5MB
Example fix
// before
abstio::write_binary(path, big_bytes); // may blow the 5MB quota
// after
if big_bytes.len() * 4 / 3 > 4_000_000 {
// store via IndexedDB or offer a file download instead
} else {
abstio::write_binary(path, big_bytes);
} Defensive patterns
Strategy: validation
Validate before calling
const LIMIT: usize = 4_000_000; // stay under ~5MB localStorage quota after base64
fn fits_local_storage(bytes: &[u8]) -> bool { bytes.len() * 4 / 3 <= LIMIT } Try / catch
match write_result {
Ok(()) => (),
Err(e) if e.to_string().contains("QuotaExceeded") || e.to_string().contains("set_item") => {
// free space, split data, or switch to IndexedDB
}
Err(e) => return Err(e),
} Prevention
- Keep localStorage payloads small (< ~3MB raw bytes)
- Use IndexedDB/OPFS for large or binary data
- Detect QuotaExceededError and surface a user-facing message
- Track cumulative usage across keys, since the quota is per-origin
When it happens
Trigger: Calling abstio::write_binary (or write_path/write_json) on a wasm build where the base64-encoded payload pushes localStorage over the browser quota (typically 5MB), or the key/path is malformed.
Common situations: Saving large maps, big simulation results, or base64-inflated binary blobs (+33% size) into the ~5MB browser localStorage limit; users with already-full storage from previous sessions.
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 a-b-street/abstreet@0964f29315 (2026-09-13).
Data as JSON: /api/errors/16acc92e1c6e84ee.
Report an issue: GitHub.
Appendix: source
Thrown at abstio/src/io_web.rs:146
}
pub fn write_binary<T: Serialize>(path: String, obj: &T) {
write_raw(path, &abstutil::to_binary(obj)).unwrap();
}
pub fn write_raw(path: String, bytes: &[u8]) -> Result<()> {
// Only save for data/player, for now
if !path.starts_with(&path_player("")) {
bail!("Not saving {}", path);
}
let window = web_sys::window().unwrap();
let storage = window.local_storage().unwrap().unwrap();
// Local storage only supports strings, so base64 encoding needed
use base64::Engine;
let encoded = base64::engine::general_purpose::STANDARD.encode(bytes);
storage.set_item(&path, &encoded).map_err(|err| {
anyhow!(err.as_string().unwrap_or_else(|| format!(
"set_item for {path} failed for encoded length {}",
encoded.len()
)))
})?;
Ok(())
}
pub fn delete_file<I: AsRef<str>>(path: I) {
let path = path.as_ref();
if !path.starts_with(&path_player("")) {
warn!("Not deleting {}", path);
return;
}
let window = web_sys::window().unwrap();
let storage = window.local_storage().unwrap().unwrap();
storage.remove_item(path).unwrap();
}
View on GitHub (pinned to 0964f29315)