ducaale/xh · error
Could not create file after unreasonable number of attempts
Error message
Could not create file after unreasonable number of attempts
What it means
download_file panics via panic! after the retry loop in open_new_file exhausts all candidate filename attempts (N, N-1, N-2, ...) without being able to create an unused file. It is a deliberate internal-invariant violation rather than a recoverable Result error, meaning the filesystem appears to reject every candidate name.
Solutions
- Check write permissions on the target download directory before running
- Free disk space and verify the filesystem is not read-only
- Catch the panic at the download_file boundary (catch_unwind) or refactor open_new_file to return a typed error
- Choose a different, writable output directory explicitly
Example fix
// before
let (path, file) = download_file(...).unwrap(); // panics
// after
let result = std::panic::catch_unwind(|| download_file(...));
match result {
Ok(Ok((path, file))) => { /* download */ },
_ => eprintln!("could not create download file; check directory permissions"),
} Defensive patterns
Strategy: try-catch
Validate before calling
// before calling download_file
let dir = target_dir.as_path();
if !dir.is_dir() { anyhow::bail!("not a directory: {}", dir.display()); }
let probe = dir.join(".write_probe");
std::fs::File::create(&probe).and_then(|_| std::fs::remove_file(&probe))
.context("download directory is not writable"); Type guard
fn is_writable_dir(p: &Path) -> bool {
p.is_dir() && std::fs::metadata(p).map(|m| !m.permissions().readonly()).unwrap_or(false)
} Try / catch
let result = std::panic::catch_unwind(AssertUnwindSafe(|| download_file(&url, &out_dir)));
match result {
Ok(Ok((path, file))) => { /* use file */ }
Ok(Err(e)) => eprintln!("download failed: {e}"),
Err(_) => eprintln!("could not create file after unreasonable number of attempts"),
} Prevention
- Ensure the download directory exists and is writable before starting
- Check disk space and read-only mount status
- Prefer passing an explicit writable output path
- Wrap risky downloads in catch_unwind or refactor to Result-based errors
When it happens
Trigger: Downloading to a directory where the base name and every enumerated fallback candidate cannot be opened new (try_open_new returns None for all iterations), e.g. an unwritable directory or extreme filesystem contention.
Common situations: Download directory lacks write permission; candidate names collide with files that can't be opened due to permissions; a pathological FS where open with O_CREAT|O_EXCL fails for all candidates.
Understand the failure class
Background: "This is a bug, please report it": internal invariant violations, unreachable panics, and SNH errors explained — this error's family across 47 libraries.
Related errors
- Can't parse Content-Range header, can't resume download
- Invalid Content-Range
- Content-Range has wrong end
- Content-Range has wrong start
- Missing Content-Range header
AI-assisted analysis of ducaale/xh@2404aceecc (2026-09-13).
Data as JSON: /api/errors/323879a6e34551a0.
Report an issue: GitHub.
Appendix: source
Thrown at src/download.rs:105
Ok(file) => Ok(Some(file)),
Err(err) if err.kind() == ErrorKind::AlreadyExists => Ok(None),
Err(err) => Err(err),
}
}
if let Some(file) = try_open_new(&file_name)? {
return Ok((file_name, file));
}
for suffix in 1..u32::MAX {
let candidate = {
let mut candidate = file_name.clone().into_os_string();
candidate.push(format!("-{suffix}"));
PathBuf::from(candidate)
};
if let Some(file) = try_open_new(&candidate)? {
return Ok((candidate, file));
}
}
panic!("Could not create file after unreasonable number of attempts");
}
// https://github.com/httpie/httpie/blob/84c7327057/httpie/downloads.py#L44
// https://tools.ietf.org/html/rfc7233#section-4.2
fn total_for_content_range(header: &str, expected_start: u64) -> Result<u64> {
let re_range = Regex::new(concat!(
r"^bytes (?P<first_byte_pos>\d+)-(?P<last_byte_pos>\d+)",
r"/(?:\*|(?P<complete_length>\d+))$"
))
.unwrap();
let caps = re_range
.captures(header)
// Could happen if header uses unit other than bytes
.ok_or_else(|| anyhow!("Can't parse Content-Range header, can't resume download"))?;
let first_byte_pos: u64 = caps
.name("first_byte_pos")
.unwrap()
.as_str()View on GitHub (pinned to 2404aceecc)