sigoden/aichat · error · anyhow::Error
Invalid document loader
Error message
Invalid document loader '{extension}': `{loader_command}` What it means
`run_loader_command` builds the loader command line with `shell_words::split(loader_command)`; when that parser rejects the string (unbalanced quotes, trailing backslash, unterminated escape), it wraps the parse error as "Invalid document loader '{extension}': `{loader_command}`". The extension identifies which configured loader was at fault, and the loader_command is echoed so the misconfiguration is visible.
Solutions
- Inspect the loader command for the given extension and fix unbalanced quotes, smart quotes, or dangling backslashes.
- Test the command string with `shell_words::split` semantics: plain space-separated tokens with single/double quoting.
- Validate loader commands at config-load time instead of at fetch time.
- Replace problematic quoting with simpler argument lists (no embedded quotes) if possible.
Example fix
// before
loaders.insert("pdf", "pdftotext - \"$1\""); // unbalanced quote -> Invalid document loader 'pdf'
// after
loaders.insert("pdf", "pdftotext - $1"); Defensive patterns
Strategy: validation
Validate before calling
fn validate_loader_command(cmd: &str) -> Result<(), String> {
match shell_words::split(cmd) {
Ok(args) if !args.is_empty() => Ok(()),
Ok(_) => Err("loader command is empty".into()),
Err(e) => Err(format!("cannot tokenize loader command {cmd:?}: {e}")),
}
} Try / catch
match run_loader_command(path, ext, &cmd) {
Err(e) if e.to_string().starts_with("Invalid document loader") => {
eprintln!("Check the loader definition for '{ext}': {e}");
}
other => other?,
} Prevention
- Validate all loader commands with shell_words::split at config load time.
- Avoid smart quotes and unbalanced quoting in loader definitions.
- Keep loader commands as simple space-separated token lists.
- Echo the failing extension/command in config-parse errors so typos are obvious.
When it happens
Trigger: Calling `load_with_command`/`load_recursive_url`/`load_protocol_path`/`fetch_with_loaders` with a loader command string that `shell_words::split` cannot tokenize, e.g. `pandoc -f "markdown` (unterminated quote) or a dangling `\`.
Common situations: Typo in a loader definition in config (unbalanced quotes, stray backslash); copy-pasting commands with smart quotes or Windows-style quoting; environment-specific quoting that shell_words cannot parse.
Understand the failure class
Background: "Invalid ... format", "must be in format X", "does not look like a ..." — invalid argument format errors across CLI tools and libraries — this error's family across 17 libraries.
Related errors
- No document loader for
- Invalid response data
- Invalid response data
- Tool call exit with
- No command generated
AI-assisted analysis of sigoden/aichat@82976d349a (2026-09-09).
Data as JSON: /api/errors/448b2b57c81545d9.
Report an issue: GitHub.
Appendix: source
Thrown at src/utils/command.rs:110
pub fn run_command_with_output<T: AsRef<OsStr>>(
cmd: &str,
args: &[T],
envs: Option<HashMap<String, String>>,
) -> Result<(bool, String, String)> {
let output = Command::new(cmd)
.args(args.iter())
.envs(envs.unwrap_or_default())
.output()?;
let status = output.status;
let stdout = std::str::from_utf8(&output.stdout).context("Invalid UTF-8 in stdout")?;
let stderr = std::str::from_utf8(&output.stderr).context("Invalid UTF-8 in stderr")?;
Ok((status.success(), stdout.to_string(), stderr.to_string()))
}
pub fn run_loader_command(path: &str, extension: &str, loader_command: &str) -> Result<String> {
let cmd_args = shell_words::split(loader_command)
.with_context(|| anyhow!("Invalid document loader '{extension}': `{loader_command}`"))?;
let mut use_stdout = true;
let outpath = temp_file("-output-", "").display().to_string();
let cmd_args: Vec<_> = cmd_args
.into_iter()
.map(|mut v| {
if v.contains("$1") {
v = v.replace("$1", path);
}
if v.contains("$2") {
use_stdout = false;
v = v.replace("$2", &outpath);
}
v
})
.collect();
let cmd_eval = shell_words::join(&cmd_args);
debug!("run `{cmd_eval}`");
let (cmd, args) = cmd_args.split_at(1);View on GitHub (pinned to 82976d349a)