sigoden/aichat · error · anyhow::Error
No document loader for
Error message
No document loader for '{}' What it means
`load_protocol_path` parses a path of the form `protocol:rest`, looks the protocol up in the configured `loaders` map, and returns `anyhow!("No document loader for '{}'", path)` when the protocol is missing or the path has no `protocol:` prefix at all (`split_once(':')` yields None). It means the given URI scheme has no loader registered, so the document cannot be fetched.
Solutions
- Register a loader for the protocol: add an entry to the `loaders` map keyed by the scheme in the path.
- Fix the path to use a protocol that has a configured loader.
- Check for typos between the path prefix and the loader key (e.g. `https:` vs `http:`).
- Validate all document paths against the loader keys before starting a sync run.
Example fix
// before
let mut loaders = HashMap::new();
loaders.insert("http", "curl -L $1");
load_protocol_path("s3:manual.md", &loaders)?; // No document loader for 's3:manual.md'
// after
loaders.insert("s3", "aws s3 cp $1 -");
load_protocol_path("s3:manual.md", &loaders)?; Defensive patterns
Strategy: validation
Validate before calling
fn loader_registered(path: &str, loaders: &HashMap<String, String>) -> Result<(), String> {
match path.split_once(':') {
Some((protocol, _)) if loaders.contains_key(protocol) => Ok(()),
Some((protocol, _)) => Err(format!("no loader registered for protocol '{protocol}'")),
None => Err(format!("path {path:?} has no 'protocol:' prefix")),
}
} Try / catch
match load_protocol_path(p, &loaders) {
Err(e) if e.to_string().starts_with("No document loader for") => {
eprintln!("Skipping {p}: no loader configured");
}
other => other?,
} Prevention
- Keep loader keys and path prefixes consistent (e.g. always 'https', never 'http').
- Validate document lists against configured loaders before syncing.
- Register a default/error loader for unknown protocols in shared config.
- Grep docs indexes for 'scheme:' prefixes that lack loader entries.
When it happens
Trigger: Calling `load_protocol_path` (directly or via `load_documents`/`sync_documents`) with a path whose prefix is absent from the `loaders` map, e.g. `s3:docs/report.md` when only `http` is configured, or a plain `README.md` with no colon at all.
Common situations: Typo'd protocol in a document list; loader removed or renamed in config while old references remain; forgetting to register a loader for a scheme used in a docs index.
Understand the failure class
Background: 'Could not be found', 'does not exist', 'not found in database': the resource-not-found family when an ID, slug, key, or URI lookup comes back empty — this error's family across 20 libraries.
Related errors
AI-assisted analysis of sigoden/aichat@82976d349a (2026-09-09).
Data as JSON: /api/errors/c1d1a94f434c7853.
Report an issue: GitHub.
Appendix: source
Thrown at src/utils/loader.rs:103
pub fn is_loader_protocol(loaders: &HashMap<String, String>, path: &str) -> bool {
match path.split_once(':') {
Some((protocol, _)) => loaders.contains_key(protocol),
None => false,
}
}
pub fn load_protocol_path(
loaders: &HashMap<String, String>,
path: &str,
) -> Result<Vec<LoadedDocument>> {
let (protocol, loader_command, new_path) = path
.split_once(':')
.and_then(|(protocol, path)| {
let loader_command = loaders.get(protocol)?;
Some((protocol, loader_command, path))
})
.ok_or_else(|| anyhow!("No document loader for '{}'", path))?;
let contents = run_loader_command(new_path, protocol, loader_command)?;
let output = if let Ok(list) = serde_json::from_str::<Vec<LoadedDocument>>(&contents) {
list.into_iter()
.map(|mut v| {
if v.path.starts_with(path) {
} else if v.path.starts_with(new_path) {
v.path = format!("{}:{}", protocol, v.path);
} else {
v.path = format!("{}/{}", path, v.path);
}
v
})
.collect()
} else {
vec![LoadedDocument::new(
path.into(),
contents,
Default::default(),View on GitHub (pinned to 82976d349a)