{"record":{"id":"1fae1a43b0c24836","repo":"siyuan-note/siyuan","slug":"plugin-pluginlabel-returned-invalid-input-va","errorCode":null,"errorMessage":"Plugin ${pluginLabel} returned invalid input: ${validationError}","messagePattern":"Plugin (.+?) returned invalid input: (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"app/src/protyle/upload/pluginEvent.ts","lineNumber":443,"sourceCode":"            discardResponse();\n            return fail(responseError);\n        }\n        if (emitResult.hasAsyncListener && !responseClaimed) {\n            return fail(new Error(`Plugin ${pluginLabel} must call respondWith synchronously before awaiting`));\n        }\n        if (!response) {\n            return processPlugin(index + 1);\n        }\n        return waitForDecision(response, task, pluginLabel, timeout).then(decision => {\n            if (decision?.action === \"cancel\") {\n                return cancel();\n            }\n            if (decision?.action !== \"replace\") {\n                throw new Error(`Plugin ${pluginLabel} returned an invalid action`);\n            }\n            const validationError = validateAssetUploadInput(decision.input);\n            if (validationError) {\n                throw new Error(`Plugin ${pluginLabel} returned invalid input: ${validationError}`);\n            }\n            const targetValidationError = validateTargetInput(decision.input, context);\n            if (targetValidationError) {\n                throw new Error(`Plugin ${pluginLabel} returned invalid input: ${targetValidationError}`);\n            }\n            task.input = cloneInput(decision.input);\n            return processPlugin(index + 1);\n        }).catch(error => error instanceof AssetUploadCanceledError ? cancel(error.message) : fail(error));\n    };\n    return processPlugin(0);\n};\n","sourceCodeStart":425,"sourceCodeEnd":455,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/app/src/protyle/upload/pluginEvent.ts#L425-L455","documentation":"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.","triggerScenarios":"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.","commonSituations":"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'.","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"],"exampleFix":"// before (plugin side): invalid replace input\nrespondWith({ action: \"replace\", input: { files: [path] } });\n// after (plugin side): well-formed input\nrespondWith({\n    action: \"replace\",\n    input: { kind: \"local-files\", files: [{ path, size: file.size }] }\n});","handlingStrategy":"validation","validationCode":"// mirror kernel-side validateAssetUploadInput before responding\nconst isValidInput = (input: any): boolean => {\n    if (!input || typeof input !== \"object\" || !Array.isArray(input.files)) return false;\n    if (input.kind === \"files\") {\n        return input.files.every((f: unknown) => f instanceof File);\n    }\n    if (input.kind === \"local-files\") {\n        return input.files.every((f: any) => f && typeof f.path === \"string\" && f.path.length > 0 &&\n            (f.size === null || (typeof f.size === \"number\" && Number.isFinite(f.size) && f.size >= 0)) &&\n            (f.isDir === undefined || typeof f.isDir === \"boolean\"));\n    }\n    return false;\n};","typeGuard":"const isAssetUploadInput = (v: unknown): v is IAssetUploadInput =>\n    typeof v === \"object\" && v !== null && Array.isArray((v as any).files) &&\n    ((v as any).kind === \"files\" || (v as any).kind === \"local-files\");","tryCatchPattern":"try {\n    await prepareAssetUpload(task, context);\n} catch (err) {\n    const m = err.message.match(/returned invalid input: (.+)$/);\n    if (m) {\n        console.error(`Plugin input rejected: ${m[1]}`); // fix decision.input accordingly\n    } else { throw err; }\n}","preventionTips":["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"],"tags":["plugin","upload","schema-validation","input-validation"],"backgroundTag":"schema-validation-failed","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T05:17:10.506Z"}