xai-org/grok-build · error
Failed to read '{path}': {e}
Error message
Failed to read '{path}': {e} What it means
PromptSource::from_file wraps std::fs::read_to_string failures as "Failed to read '{path}': {e}". The prompt file could not be opened or decoded as UTF-8 (missing file, permission denied, directory passed, or invalid encoding).
Source
Thrown at crates/codegen/xai-grok-pager/src/headless/cli.rs:61
if let Some(text) = single {
Self::from_text(text)
.map(Some)
.map_err(|e| anyhow::anyhow!("--single: {e}"))
} else if let Some(json_str) = prompt_json {
Self::from_json(json_str)
.map(Some)
.map_err(|e| anyhow::anyhow!("--prompt-json: {e}"))
} else if let Some(path) = prompt_file {
Self::from_file(path).map(Some)
} else {
Ok(None)
}
}
/// `.json` files are parsed as content blocks, everything else as text.
pub fn from_file(path: &Path) -> anyhow::Result<Self> {
let content = std::fs::read_to_string(path)
.map_err(|e| anyhow::anyhow!("Failed to read '{}': {e}", path.display()))?;
let context = |e| anyhow::anyhow!("'{}': {e}", path.display());
if path.extension().and_then(|e| e.to_str()) == Some("json") {
Self::from_json(&content).map_err(context)
} else {
Self::from_text(&content).map_err(context)
}
}
fn from_text(text: &str) -> anyhow::Result<Self> {
let trimmed = text.trim();
if trimmed.is_empty() {
anyhow::bail!("prompt is empty");
}
Ok(Self::Text(trimmed.to_string()))
}
fn from_json(json_str: &str) -> anyhow::Result<Self> {View on GitHub (pinned to bc7f02eddd)
Solutions
- Verify the path exists and is readable (ls -l / test -r)
- Use an absolute path or resolve relative to the script's directory
- Re-save the file as UTF-8 without BOM
- Check the inner {e} (NotFound/PermissionDenied/InvalidData) for the exact cause
Example fix
# before pager --prompt-file prompts/task.txt # after pager --prompt-file "$(pwd)/prompts/task.txt"
Defensive patterns
Strategy: validation
Validate before calling
fn ensure_readable_prompt_file(path: &std::path::Path) -> Result<(), String> {
if !path.is_file() { return Err(format!("not a file: {}", path.display())); }
let meta = std::fs::metadata(path).map_err(|e| e.to_string())?;
if meta.len() == 0 { return Err("file is empty".into()); }
std::fs::read_to_string(path).map(|_| ()).map_err(|e| e.to_string())
} Type guard
fn is_readable_utf8_file(p: &std::path::Path) -> bool {
p.is_file() && std::fs::read_to_string(p).is_ok()
} Try / catch
match PromptSource::from_file(&path) {
Ok(src) => src,
Err(e) => {
if e.to_string().contains("Failed to read") {
eprintln!("prompt file unreadable: {e:#}"); std::process::exit(2);
}
return Err(e);
}
} Prevention
- Use absolute paths resolved from the script location
- Check file existence and readability before invoking
- Ensure files are UTF-8 without BOM
- Ship prompt files with the deployment artifact, not ad hoc
When it happens
Trigger: Calling --prompt-file with a path that does not exist, is unreadable (permissions), is a directory, or contains non-UTF-8 bytes.
Common situations: Typo in the path; running from a different working directory with a relative path; CI checkout missing the file; file saved with UTF-16/BOM from a Windows editor.
Understand the failure class
Background: "File not found" and ENOENT errors: why libraries can't find a file that should exist — this error's family across 50 libraries.
Related errors
AI-assisted analysis of xai-org/grok-build@bc7f02eddd (2026-08-31).
Data as JSON: /api/errors/c3a590da97a4d1c6.
Report an issue: GitHub.