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
- 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
- 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
- If it appeared after a dependency change, inspect Cargo.lock for schemars/serde_json drift and pin a known-good version
- 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
- Always define tool args as structs with named fields - never bare Vec/Option/tuple aliases
- Add a unit test per tool that constructs its ToolSpec so the panic becomes a red test, not a crashed process
- Review Cargo.lock diffs for schemars when this appears out of nowhere
- Read the panic text: {name} names the offending tool, {err} names the offending schema construct
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
- root tool schema must be an object
- generated skill tool schema should serialize: {err}
- plugin cache root should be absolute: {err}
- mutex poisoned
- failed to open bundled bubblewrap {}: {err}
AI-assisted analysis of openai/codex@339751715c (2026-08-25).
Data as JSON: /api/errors/f1278249da564fde.
Report an issue: GitHub.