Hmbown/CodeWhale · error · io::Error
Invalid session id '{session_id}'
Error message
Invalid session id '{session_id}' What it means
ApprovalLog::validated_session_id guards log_path() before it joins <sessions_dir>/<session_id>/approval_receipts.jsonl. It trims the id, then rejects it if it is empty or contains any character outside ASCII letters, digits, '-', and '_'. This is a path-safety guard: spaces, dots, slashes, or '..' in a session id could escape the per-session directory, so malformed ids fail closed with InvalidInput before any filesystem access.
Source
Thrown at crates/tui/src/approval_log.rs:174
impl ApprovalReceiptStore {
pub(crate) fn new(sessions_dir: PathBuf) -> Self {
Self { sessions_dir }
}
#[cfg_attr(test, allow(dead_code))]
pub(crate) fn default_location() -> io::Result<Self> {
crate::session_manager::default_sessions_dir().map(Self::new)
}
fn validated_session_id(session_id: &str) -> io::Result<&str> {
let trimmed = session_id.trim();
if trimmed.is_empty()
|| !trimmed
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
{
return Err(io::Error::new(
io::ErrorKind::InvalidInput,
format!("Invalid session id '{session_id}'"),
));
}
Ok(trimmed)
}
fn log_path(&self, session_id: &str) -> io::Result<PathBuf> {
let session_id = Self::validated_session_id(session_id)?;
Ok(self.sessions_dir.join(session_id).join(APPROVAL_LOG_FILE))
}
fn lock_path(&self, session_id: &str) -> io::Result<PathBuf> {
let session_id = Self::validated_session_id(session_id)?;
Ok(self.sessions_dir.join(session_id).join(APPROVAL_LOCK_FILE))
}
fn open_lock_file(&self, session_id: &str) -> io::Result<File> {View on GitHub (pinned to 0c42157ee5)
Solutions
- Use the session id produced by the session manager (generated [A-Za-z0-9_-] form), not a display name
- Sanitize before calling: trim, then replace or reject characters outside [A-Za-z0-9_-]
- Validate the id at the input boundary (CLI arg, config, recorded session index) and fail with a user-facing message before touching approval logs
Example fix
// before
let receipts = approval_log.load("session 42");? // InvalidInput: Invalid session id 'session 42'
// after
let id = "session 42".chars().map(|c| if c.is_ascii_alphanumeric() || c == '-' || c == '_' { c } else { '_' }).collect::<String>();
let receipts = approval_log.load(&id); Defensive patterns
Strategy: type-guard
Validate before calling
let id = session_id.trim(); assert!(!id.is_empty(), "session id required");
Type guard
fn is_valid_session_id(session_id: &str) -> bool {
!session_id.is_empty()
&& session_id
.chars()
.all(|c| c.is_ascii_alphanumeric() || c == '-' || c == '_')
} Try / catch
match approval_log.load(sid) {
Err(e) if e.kind() == io::ErrorKind::InvalidInput && e.to_string().contains("session id") => {
// reject/sanitize the id at the caller; do not retry the same value
}
other => other?,
} Prevention
- Only pass session-manager-generated ids to approval-log APIs
- Validate ids at the input boundary with the same [A-Za-z0-9_-] rule
- Never build log paths by joining raw user strings
When it happens
Trigger: Calling approval_log.load(session_id) / log_path(session_id) (directly or via replay/resume flows) with values like "session 2", "../other-session", "id:1234", "séance", or an empty/whitespace-only string. Leading/trailing whitespace is tolerated because the id is trimmed; any interior invalid character is not.
Common situations: Passing a user-typed session name containing spaces instead of the generated session id; passing a filesystem path or URL slug as an id; ids containing ':', '.', '/', or unicode copied from another tool.
Related errors
- persistent path allow rules must stay within the workspace
- {kind} must be a single path component
- workspace path cannot contain '..' components
- The Codewhale service returned an account without an ID
- The Codewhale service returned an invalid user code
AI-assisted analysis of Hmbown/CodeWhale@0c42157ee5 (2026-08-20).
Data as JSON: /api/errors/b27c4ada7c296bc0.
Report an issue: GitHub.