nikivdev/code · error
plan body is empty
Error message
plan body is empty
What it means
This function reads a plan document from stdin (io::stdin().read_to_string) and, after successfully reading it, checks that the trimmed body is non-empty. An empty or whitespace-only stdin input produces this error, because a plan file with no content would be invalid. Note the preceding read is wrapped with context 'failed to read plan body from stdin', so this bail specifically means stdin was readable but blank.
Source
Thrown at src/codex_runtime.rs:1244
.ok()
.or_else(|| env::var("FLOW_CODEX_RUNTIME_STATE").ok())?;
let path = PathBuf::from(raw_path);
let raw = fs::read(path).ok()?;
serde_json::from_slice::<CodexRuntimeState>(&raw).ok()
}
pub fn write_plan_from_stdin(
title: Option<&str>,
stem: Option<&str>,
dir: Option<&str>,
source_session: Option<&str>,
) -> Result<PathBuf> {
let mut body = String::new();
io::stdin()
.read_to_string(&mut body)
.context("failed to read plan body from stdin")?;
if body.trim().is_empty() {
bail!("plan body is empty");
}
let root = resolve_plan_root(dir);
fs::create_dir_all(&root)?;
let resolved_title = title
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
.unwrap_or_else(|| derive_plan_title(&body));
let mut resolved_stem = stem
.map(str::trim)
.filter(|value| !value.is_empty())
.map(ToOwned::to_owned)
.unwrap_or_else(|| slugify(&resolved_title));
if !resolved_stem.ends_with("-plan") {
resolved_stem.push_str("-plan");
}View on GitHub (pinned to a747e741ae)
Solutions
- Pipe the actual plan content into the command, e.g. `cat plan.md | tool plan --title "..."`.
- Check the upstream command that feeds stdin — it may be failing or producing empty output.
- In CI/cron, ensure stdin is connected to a file with content rather than /dev/null.
- Add a pre-check in your pipeline: fail early if the generated plan body is empty before invoking the tool.
Example fix
// before tool plan --title "Refactor" < /dev/null // after tool plan --title "Refactor" < ./plans/refactor.md
Defensive patterns
Strategy: validation
Validate before calling
let mut body = String::new();
io::stdin().read_to_string(&mut body)?;
if body.trim().is_empty() {
return Err("refusing to call: plan body from stdin is empty".into());
} Type guard
fn has_content(s: &str) -> bool { !s.trim().is_empty() } Try / catch
match write_plan_from_stdin(dir, title) {
Err(e) if e.to_string().contains("plan body is empty") => {
eprintln!("stdin was empty — check the upstream command/file feeding the pipe.");
}
other => other?,
} Prevention
- Always verify the file or upstream command piped into stdin actually has content.
- In CI/cron, redirect stdin from a real file, not /dev/null.
- Check upstream generators exit 0 and produce output before piping them in.
When it happens
Trigger: Invoking the plan-creation routine with stdin closed, redirected from an empty file (`< /dev/null` or `< empty.txt`), or piped from a command that produced no output (`echo -n '' | tool plan ...`).
Common situations: Forgetting to pipe the plan content and running the command in a non-interactive context where stdin is /dev/null (CI jobs, cron); an upstream generator failing silently and emitting nothing; heredocs left empty; paste failures in scripted pipelines.
Related errors
- no queries provided
- prompt is empty
- missing prompt. Usage: f ai everruns "your prompt"
- Suggested command is empty.
- Suggested command is incomplete.
AI-assisted analysis of nikivdev/code@a747e741ae (2026-09-01).
Data as JSON: /api/errors/6cfaf3b61e59dca2.
Report an issue: GitHub.