siyuan-note/siyuan · error
Plugin ${pluginLabel} returned invalid input: ${validationEr
Error message
Plugin ${pluginLabel} returned invalid input: ${validationError} What it means
After a plugin's decision passes the action check, processPlugin validates decision.input twice: validateAssetUploadInput checks the generic shape (object with a files array, kind 'files' with File entries, or kind 'local-files' with objects containing a non-empty string path, optional numeric size, optional boolean isDir), and validateTargetInput checks target-specific fields. If either returns a message, it throws Error("Plugin ${pluginLabel} returned invalid input: ${validationError}"), so the exact validator message is embedded in the error.
Source
Thrown at app/src/protyle/upload/pluginEvent.ts:443
discardResponse();
return fail(responseError);
}
if (emitResult.hasAsyncListener && !responseClaimed) {
return fail(new Error(`Plugin ${pluginLabel} must call respondWith synchronously before awaiting`));
}
if (!response) {
return processPlugin(index + 1);
}
return waitForDecision(response, task, pluginLabel, timeout).then(decision => {
if (decision?.action === "cancel") {
return cancel();
}
if (decision?.action !== "replace") {
throw new Error(`Plugin ${pluginLabel} returned an invalid action`);
}
const validationError = validateAssetUploadInput(decision.input);
if (validationError) {
throw new Error(`Plugin ${pluginLabel} returned invalid input: ${validationError}`);
}
const targetValidationError = validateTargetInput(decision.input, context);
if (targetValidationError) {
throw new Error(`Plugin ${pluginLabel} returned invalid input: ${targetValidationError}`);
}
task.input = cloneInput(decision.input);
return processPlugin(index + 1);
}).catch(error => error instanceof AssetUploadCanceledError ? cancel(error.message) : fail(error));
};
return processPlugin(0);
};
View on GitHub (pinned to 8641553a1f)
Solutions
- Read the embedded validationError in the message and shape the plugin's decision.input to match: {kind: 'files', files: [File,...]} or {kind: 'local-files', files: [{path: string, size?: number|null, isDir?: boolean},...]}
- For 'files' kind, pass real File objects (check file instanceof File), not Blobs, strings, or plain objects
- For 'local-files' kind, ensure every entry has a non-empty string path and size is either null or a non-negative finite number, isDir a boolean if present
- If the target validation fails, fix the target fields the plugin replaced (validateTargetInput checks destination-specific constraints) or keep the original target instead of overriding it
- Catch the error around prepareAssetUpload and log pluginLabel plus the message to identify and fix the offending plugin
Example fix
// before (plugin side): invalid replace input
respondWith({ action: "replace", input: { files: [path] } });
// after (plugin side): well-formed input
respondWith({
action: "replace",
input: { kind: "local-files", files: [{ path, size: file.size }] }
}); Defensive patterns
Strategy: validation
Validate before calling
// mirror kernel-side validateAssetUploadInput before responding
const isValidInput = (input: any): boolean => {
if (!input || typeof input !== "object" || !Array.isArray(input.files)) return false;
if (input.kind === "files") {
return input.files.every((f: unknown) => f instanceof File);
}
if (input.kind === "local-files") {
return input.files.every((f: any) => f && typeof f.path === "string" && f.path.length > 0 &&
(f.size === null || (typeof f.size === "number" && Number.isFinite(f.size) && f.size >= 0)) &&
(f.isDir === undefined || typeof f.isDir === "boolean"));
}
return false;
}; Type guard
const isAssetUploadInput = (v: unknown): v is IAssetUploadInput =>
typeof v === "object" && v !== null && Array.isArray((v as any).files) &&
((v as any).kind === "files" || (v as any).kind === "local-files"); Try / catch
try {
await prepareAssetUpload(task, context);
} catch (err) {
const m = err.message.match(/returned invalid input: (.+)$/);
if (m) {
console.error(`Plugin input rejected: ${m[1]}`); // fix decision.input accordingly
} else { throw err; }
} Prevention
- Reuse the kernel's validation rules in your plugin: object input, files array, kind 'files' or 'local-files'
- For kind 'files' pass real File instances, never Blob/string substitutes
- For kind 'local-files' always provide a non-empty string path; use null (not undefined/negative) for unknown size
- Parse the embedded validator message from the error text — it names the exact offending field and index
- Add plugin tests that assert your replaced input passes validation before calling respondWith
When it happens
Trigger: A plugin responds with {action: 'replace', input} where input is missing/not an object, input.files is not an array, kind is neither 'files' nor 'local-files', a 'files' entry is not a File, or a 'local-files' entry lacks a non-empty string path / has a bad size or isDir; or the replacement target input fails validateTargetInput.
Common situations: A plugin substituting plain path strings where File objects are required for kind 'files'; empty path values; null or negative size fields; a plugin updated to a new input schema while the kernel still validates the old one; passing undefined input with action 'replace'.
Understand the failure class
Background: Schema validation failed / invalid input schema: payload rejected because its shape doesn't match the expected schema — this error's family across 28 libraries.
Related errors
- Plugin ${pluginLabel} returned an invalid action
- Plugin ${pluginLabel} returned invalid input: ${targetValida
- Account authentication failed, please login again
- Upload failed: %s
- Failed to insert asset file, please reopen the document
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/1fae1a43b0c24836.
Report an issue: GitHub.