nexu-io/open-design · error · Error
inputs must be an object
Error message
inputs must be an object
What it means
Thrown by the MCP run-start helper while building POST /api/runs. The `inputs` argument becomes `body.pluginInputs`, a key/value map passed to the resolved plugin. The guard rejects null, arrays, and primitives because plugin input resolution expects a plain object; a non-object would either crash later dispatch or be silently mis-applied.
Source
Thrown at apps/daemon/src/mcp.ts:2536
if (args.resume !== undefined) {
if (typeof args.resume !== 'boolean') throw new Error('resume must be a boolean');
body.resume = args.resume;
}
if (typeof args.prompt === 'string' && args.prompt.length > 0) {
body.message = args.prompt;
body.currentPrompt = args.prompt;
}
if (typeof args.skill === 'string' && args.skill.length > 0) body.skillId = args.skill;
if (Array.isArray(args.skills) && args.skills.length > 0) body.skillIds = args.skills;
if (typeof args.plugin === 'string' && args.plugin.length > 0) body.pluginId = args.plugin;
if (typeof args.agent === 'string' && args.agent.length > 0) body.agentId = args.agent;
if (typeof args.model === 'string' && args.model.length > 0) body.model = args.model;
if (typeof args.serviceTier === 'string' && args.serviceTier.length > 0) {
body.serviceTier = args.serviceTier;
}
if (args.inputs !== undefined) {
if (args.inputs === null || typeof args.inputs !== 'object' || Array.isArray(args.inputs)) {
throw new Error('inputs must be an object');
}
body.pluginInputs = args.inputs;
}
const created = await postJson<JsonObject>(
`${baseUrl}/api/runs`,
body,
{ ...options.analyticsHeaders, ...headers },
);
// Build studioUrl (conversation-level — no entry file yet) so the
// outer agent has a URL to give the user right away. The daemon
// returns conversationId in the response now that POST /api/runs
// falls back to the project's default conversation for MCP callers.
const webBase = await getWebBaseUrl(baseUrl);
const studioUrl = buildStudioUrl(webBase, id, created?.conversationId, null);
return ok(
withActiveEcho(
{
...created,View on GitHub (pinned to 5be4028344)
Solutions
- Pass a plain object: `inputs: { field: value }`.
- If you hold a JSON string, parse first: `inputs: JSON.parse(raw)`.
- If you have an array of one object, unwrap it: `inputs: arr[0]`.
- Omit `inputs` when the plugin takes no inputs rather than passing `null`.
Example fix
// before
{ plugin: id, inputs: [{ tone: "bold" }] }
// after
{ plugin: id, inputs: { tone: "bold" } } Defensive patterns
Strategy: type-guard
Validate before calling
if (inputs !== undefined && (inputs === null || typeof inputs !== 'object' || Array.isArray(inputs))) {
throw new TypeError('inputs must be a plain object');
} Type guard
function isPlainObject(v: unknown): v is Record<string, unknown> {
return typeof v === 'object' && v !== null && !Array.isArray(v);
} Prevention
- Type plugin inputs as Record<string, unknown> in your client.
- JSON.parse serialized inputs before passing them.
- Never wrap inputs in an array.
When it happens
Trigger: Calling start_run with `inputs: [{...}]` (array of objects), `inputs: null`, `inputs: "{"k":1}"` (a JSON string), or `inputs: ""`. Common when a caller wraps inputs in an extra array layer or forgets to JSON.parse a serialized payload.
Common situations: LLM tool-call args serialized as arrays by mistake; config files that store inputs as a JSON string; passing the whole plugin snapshot instead of its `.inputs` field.
Related errors
- resume must be a boolean
- artifactManifest must be an object
- existing config at ${where} is not valid JSON: ${err instanc
- existing config at ${where} is not a JSON object
- pluginWorkflowId requires a validated externalPluginContext
AI-assisted analysis of nexu-io/open-design@5be4028344 (2026-08-12).
Data as JSON: /api/errors/079ee69d4c34d377.
Report an issue: GitHub.