tinyhumansai/openhuman · error · anyhow::Error

[claude-code][driver] no input messages to deliver

Error message

[claude-code][driver] no input messages to deliver

What it means

The claude-code driver validates its serialized stdin payload BEFORE spawning the CLI (a deliberate validate-before-spawn check at driver.rs:~355). `build_stdin(ctx.messages, is_new)` returned empty, meaning the outgoing conversation has zero deliverable messages for this turn. Rather than launching a process it cannot feed, the driver refuses.

Source

Thrown at src/openhuman/inference/provider/claude_code/driver.rs:342

    );
    if let Some(p) = mcp_config_path.as_ref() {
        args.push("--mcp-config".into());
        args.push(p.display().to_string());
        args.push("--strict-mcp-config".into());
    }
    // Tool surface follows the permission posture: full access → no
    // `--disallowedTools` (CC keeps its entire toolset incl. Bash/network);
    // default `acceptEdits` → withhold the dangerous builtins (edits only).
    if !full_access {
        args.push("--disallowedTools".into());
        args.push(DISALLOWED_CC_BUILTINS.join(","));
    }

    // Validate input *before* spawning so we don't launch a process we
    // can't feed (CodeRabbit: validate before spawn).
    let stdin_bytes = build_stdin(ctx.messages, is_new);
    if stdin_bytes.is_empty() {
        anyhow::bail!("[claude-code][driver] no input messages to deliver");
    }

    log::debug!(
        "[claude-code][driver] spawn bin={} model={} is_new={} cc_session_id={}",
        ctx.bin_path.display(),
        ctx.model,
        is_new,
        cc_session_id
    );

    // Best-effort: ensure the project dir exists so spawn (cwd) doesn't fail.
    std::fs::create_dir_all(&ctx.project_dir).ok();

    // Wrap the spawn in the macOS Seatbelt jail when available so CC's file
    // writes are OS-confined: `sandbox-exec -p <profile> <claude> <args…>`.
    #[cfg(target_os = "macos")]
    let (program, final_args): (PathBuf, Vec<String>) = if jailed {
        let profile = seatbelt_profile(&ctx.workspace_dir);

View on GitHub (pinned to 7491200858)

Solutions

  1. Inspect the caller: ensure the ChatRequest/messages vector contains at least one non-empty message before dispatching to the claude-code provider.
  2. If this is a continuation turn, verify the session history was loaded (cc_session_id resolves) so `build_stdin` has prior context to serialize.
  3. Guard at the API edge: short-circuit empty turns with a user-facing 'empty message' response instead of invoking the provider.
  4. Add a unit test pinning `build_stdin` output non-empty for your canonical message payload.

Example fix

// before
let messages: Vec<ChatMessage> = vec![];
let resp = provider.chat(messages).await?; // trips the guard

// after
if messages.is_empty() {
    return Ok(ChatResponse::empty("nothing to send"));
}
let resp = provider.chat(messages).await?;
Defensive patterns

Strategy: validation

Validate before calling

// Before dispatching to the claude-code provider:
let deliverable: Vec<_> = messages.iter().filter(|m| !m.content_is_empty()).collect();
if deliverable.is_empty() {
    return Ok(empty_turn_response()); // skip the provider entirely
}

Type guard

fn has_deliverable_messages(msgs: &[ChatMessage]) -> bool {
    msgs.iter().any(|m| m.text().map(|t| !t.trim().is_empty()).unwrap_or(false))
}

Prevention

When it happens

Trigger: Calling the claude-code chat path with an empty message list, a continuation turn (`is_new=false`) whose history resolved to nothing, or upstream filtering that stripped every message (e.g. all content blocks removed by sanitization), leaving `stdin_bytes` empty.

Common situations: An orchestration/agent loop passing an empty turn (empty user prompt after trimming), a thread-continuation bug that drops history, or a caller constructing a ChatRequest with no messages expecting the CLI to idle.

Related errors


AI-assisted analysis of tinyhumansai/openhuman@7491200858 (2026-08-17). Data as JSON: /api/errors/9dc093c01eb6e56b. Report an issue: GitHub.