openai/codex · critical

generated input schema for {name} should parse: {err}

Error message

generated input schema for {name} should parse: {err}

What it means

This panic fires inside skill_function_tool (codex-rs/ext/skills/src/tools/mod.rs:253-262): the JSON Schema that schemars derives for the tool input type I is passed to parse_tool_input_schema, which converts it into the strict Responses API tool-parameter format. The unwrap_or_else(panic) encodes an internal invariant - a schema derived from a Rust JsonSchema type should always parse. When it fires, a skill tool's input type produced a schema shape the parser rejects (for example a non-object root such as a bare Vec/tuple alias, or keywords the strict parser cannot normalize), and it happens at tool registration time, before any model call.

Source

Thrown at codex-rs/ext/skills/src/tools/mod.rs:260

            SkillSourceKind::Host | SkillSourceKind::Orchestrator | SkillSourceKind::Custom(_) => {
                None
            }
        }
    }
}

fn skill_tool_name(name: &str) -> ToolName {
    ToolName::namespaced(SKILLS_NAMESPACE, name)
}

fn skill_function_tool<I: JsonSchema, O: JsonSchema>(name: &str, description: &str) -> ToolSpec {
    let tool = ResponsesApiTool {
        name: name.to_string(),
        description: description.to_string(),
        strict: false,
        defer_loading: None,
        parameters: parse_tool_input_schema(&schema::input_schema_for::<I>())
            .unwrap_or_else(|err| panic!("generated input schema for {name} should parse: {err}")),
        output_schema: Some(schema::output_schema_for::<O>()),
    };

    ToolSpec::Namespace(ResponsesApiNamespace {
        name: SKILLS_NAMESPACE.to_string(),
        description: default_namespace_description(SKILLS_NAMESPACE),
        tools: vec![ResponsesApiNamespaceTool::Function(tool)],
    })
}

fn parse_args<T: for<'de> Deserialize<'de>>(call: &ToolCall) -> Result<T, FunctionCallError> {
    let arguments = call.function_arguments()?;
    let value = if arguments.trim().is_empty() {
        Value::Object(serde_json::Map::new())
    } else {
        serde_json::from_str(arguments)
            .map_err(|err| FunctionCallError::RespondToModel(err.to_string()))?
    };

View on GitHub (pinned to 339751715c)

Solutions

  1. Make the tool input type a struct with named fields so the root schema is an object (struct SearchArgs { terms: Vec<String> }) instead of a Vec/tuple/primitive alias
  2. Reproduce in a unit test: run schema::input_schema_for::<I>() through parse_tool_input_schema and read the embedded {err} - it names the offending construct
  3. If it appeared after a dependency change, inspect Cargo.lock for schemars/serde_json drift and pin a known-good version
  4. If the type is legitimately object-shaped and it still fails, file a codex-rs issue - generated schemas failing to parse are a library bug

Example fix

// before
type SearchArgs = Vec<String>; // array root schema -> panic
skill_function_tool::<SearchArgs, SearchOut>("search", "...")

// after
#[derive(Deserialize, JsonSchema)]
struct SearchArgs {
    /// One or more search terms.
    terms: Vec<String>,
}
skill_function_tool::<SearchArgs, SearchOut>("search", "...")
Defensive patterns

Strategy: try-catch

Validate before calling

// unit test per tool: construction panics if the generated schema does not parse
#[test]
fn my_tool_schema_parses() {
    let spec = skill_function_tool::<MyArgs, MyOut>("my_tool", "desc");
    assert!(matches!(spec, ToolSpec::Namespace(_)));
}

Type guard

fn tool_args_have_object_root<I: schemars::JsonSchema>() -> bool {
    matches!(
        serde_json::to_value(schemars::schema_for!(I)).ok(),
        Some(serde_json::Value::Object(_))
    )
}

Try / catch

// keep registration panics from taking down a long-lived host:
let spec = std::panic::catch_unwind(|| {
    skill_function_tool::<MyArgs, MyOut>("my_tool", "desc")
});
if let Err(panic) = spec {
    tracing::error!(?panic, "skill tool registration failed; skipping tool");
}

Prevention

When it happens

Trigger: Registering a skills-extension tool whose input type's derived schema fails parse_tool_input_schema - typically a non-object root schema (type alias of Vec<T>, a primitive, or a tuple struct), or an exotic construct emitted after a schemars upgrade. It panics during startup or first skill listing, deterministically per build.

Common situations: A new skill tool defined with 'type MyArgs = Vec<String>' instead of a struct with named fields; a schemars major-version bump changing emitted schema shapes; a patched or forked schemars in the lockfile.

Related errors


AI-assisted analysis of openai/codex@339751715c (2026-08-25). Data as JSON: /api/errors/f1278249da564fde. Report an issue: GitHub.