Hmbown/CodeWhale · error · io::Error (InvalidInput)
export output must be outside the session store
Error message
export output must be outside the session store
What it means
The export refuses to write the archive inside the session store directory. After canonicalizing both the output's parent directory and the sessions dir, if the parent is the store itself or a subdirectory of it, the export would write into the data it is exporting (risking recursion, self-inclusion, or clobbering sessions), so it fails with InvalidInput.
Solutions
- Choose an output path outside the sessions directory (e.g. a dedicated `exports/` folder).
- If the sessions dir is configured wrongly, fix the sessions-dir setting so it does not equal the export destination's parent.
- In scripts, build the output path from a distinct variable, e.g. `"$HOME/exports/session-$ID.tar.xz"`.
- Verify with canonical paths — compare `std::fs::canonicalize`d parents — since symlinks can silently place the output inside the store.
Example fix
// before
let output = sessions_dir.join("session-123.tar.xz"); // inside the store
// after
let output = Path::new("/tmp").join("session-123.tar.xz"); Defensive patterns
Strategy: validation
Validate before calling
fn output_inside_store(output: &Path, sessions_dir: &Path) -> std::io::Result<bool> {
let parent = output.parent().unwrap_or(Path::new(".")).canonicalize()?;
let root = sessions_dir.canonicalize()?;
Ok(parent.starts_with(root))
}
// if output_inside_store(out, store)? { pick another output path } Try / catch
match write_session_archive(&session, dir, out, opts) {
Err(e) if e.to_string().contains("outside the session store") => {
eprintln!("choose an --output path outside {}", dir.display());
}
other => other?,
} Prevention
- Keep exports in a dedicated directory separate from the session store
- Compare canonicalized paths in scripts before running the export
- Never point --output at the sessions directory or its children
When it happens
Trigger: Calling `write_session_archive` (or `run_sessions_export`) with `output` whose canonicalized parent path starts with the canonicalized sessions_dir — e.g. exporting to `~/.codewhale/sessions/export.tar.xz` while the store is `~/.codewhale/sessions`.
Common situations: Pointing the CLI `--output` at the sessions directory by mistake; scripting an export using the same base path variable for both store and output; a config where the export dir was set equal to the sessions dir.
Understand the failure class
Background: Path traversal blocked: "path escapes the workspace" and "outside site root" errors when a path will not stay inside its allowed directory — this error's family across 26 libraries.
Related errors
- already exists; pass --force to overwrite it
- artifact name is not portable UTF-8
- artifact tree exceeds export depth limit
- artifact tree exceeds export entry limit
- Automation lock must be a regular file
AI-assisted analysis of Hmbown/CodeWhale@433685b202 (2026-09-15).
Data as JSON: /api/errors/b8b1231a14f697fc.
Report an issue: GitHub.
Appendix: source
Thrown at crates/tui/src/session_export.rs:172
io::ErrorKind::InvalidInput,
format!(
"xz compression level {} is out of range 0-9",
options.compression_level
),
));
}
let session_json = session_json(session)?;
let container_json = container_json(session)?;
let parent = output
.parent()
.filter(|parent| !parent.as_os_str().is_empty())
.map_or_else(|| PathBuf::from("."), Path::to_path_buf);
fs::create_dir_all(&parent)?;
let sessions_dir = sessions_dir.map(Path::canonicalize).transpose()?;
if let Some(root) = &sessions_dir
&& parent.canonicalize()?.starts_with(root)
{
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
"export output must be outside the session store",
));
}
let artifact_files = match (options.include_artifacts, sessions_dir.as_deref()) {
(true, Some(root)) => match session_artifacts_dir(root, &session.metadata.id)? {
Some(dir) => collect_artifact_files(root, &dir)?,
None => Vec::new(),
},
_ => Vec::new(),
};
let mut temp = tempfile::Builder::new()
.prefix(".codewhale-session-export-")
.tempfile_in(&parent)?;
let mut summary = SessionArchiveSummary {
output: output.to_path_buf(),
session_id: session.metadata.id.clone(),View on GitHub (pinned to 433685b202)