{"record":{"id":"eee15bb3e3ea782d","repo":"siyuan-note/siyuan","slug":"invalid-image-operation-key","errorCode":null,"errorMessage":"invalid image operation key","messagePattern":"invalid image operation key","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"kernel/mcp/tools/image.go","lineNumber":281,"sourceCode":"\t\treturn CallToolResult{}, false\n\t}\n\tif record.State == imageOperationStateRunning {\n\t\treturn imageUnknown(\"a previous image operation was interrupted; its external result is unknown and it must not be retried automatically\"), true\n\t}\n\tif record.State != imageOperationStateCompleted || record.Result.IsError {\n\t\tremoveImageOperationRecord(key)\n\t\treturn CallToolResult{}, false\n\t}\n\tif record.AssetPath != \"\" && !imageOperationAssetExists(record.DocumentID, record.AssetPath) {\n\t\tremoveImageOperationRecord(key)\n\t\treturn CallToolResult{}, false\n\t}\n\treturn record.Result, true\n}\n\nfunc saveImageOperationRecord(key string, meta imageOperationMeta, state string, result CallToolResult) error {\n\tif !validImageOperationKey(key) {\n\t\treturn errors.New(\"invalid image operation key\")\n\t}\n\tassetPath := meta.AssetPath\n\tif state == imageOperationStateCompleted {\n\t\tif resultPath := imageResultAssetPath(result); resultPath != \"\" {\n\t\t\tassetPath = resultPath\n\t\t}\n\t}\n\tif state != imageOperationStateRunning && state != imageOperationStateCompleted {\n\t\treturn errors.New(\"invalid image operation state\")\n\t}\n\trecord := imageOperationRecord{\n\t\tVersion: 1, CreatedAt: time.Now().UnixMilli(), State: state, Action: meta.Action, DocumentID: meta.DocumentID,\n\t\tAssetPath: assetPath, Result: result,\n\t}\n\tdata, err := json.Marshal(record)\n\tif err != nil {\n\t\treturn err\n\t}","sourceCodeStart":263,"sourceCodeEnd":299,"githubUrl":"https://github.com/siyuan-note/siyuan/blob/8641553a1f07374001902d3ce773285db1292b2d/kernel/mcp/tools/image.go#L263-L299","documentation":"saveImageOperationRecord persists the state record of an asynchronous image operation and first validates the operation key with validImageOperationKey. This error is returned when the supplied key is not a valid pending/known image operation key. It is an internal invariant guard: callers are expected to look up the operation meta from the pending store before saving a record.","triggerScenarios":"runImageOperation calls saveImageOperationRecord with a key that was never registered as a pending image operation, or with a key that was already consumed/expired/corrupted before the state save; the test TestRunImageOperationBlocksUnknownPendingOperation exercises this deliberately.","commonSituations":"A stale or hand-forged operation key is passed to an image tool continuation; the kernel restarted and lost pending operation state while the client still holds the old key; concurrent invocations race and the pending entry is removed between lookup and save.","solutions":["Start the image operation again from the original image tool call to obtain a fresh valid operation key","Do not cache or reuse operation keys across kernel restarts","Ensure the pending operation lookup and record save use the same key string (no trimming/encoding differences)","Check for concurrent removal of the pending entry if multiple clients drive the same operation"],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"// keep and pass only keys obtained from the original tool response\nif (!opKey || typeof opKey !== 'string') throw new Error('operation key missing; start a new image operation');","typeGuard":"const hasValidKeyShape = (k) => typeof k === 'string' && k.length > 0 && /^[A-Za-z0-9_-]+$/.test(k);","tryCatchPattern":"try {\n  await runImageOperation(opKey);\n} catch (e) {\n  if (String(e.message).includes('invalid image operation key')) {\n    opKey = await startNewImageOperation(args); // restart to mint a fresh key\n  } else throw e;\n}","preventionTips":["Treat operation keys as single-run tokens; never reuse across kernel restarts","Restart the operation from scratch when the key is rejected","Avoid mutating or trimming the key string before sending it"],"tags":["mcp","image","state-management","invalid-key"],"backgroundTag":"invalid-identifier","analyzedSha":"8641553a1f07374001902d3ce773285db1292b2d","analyzedAt":"2026-09-11T16:08:28.414Z","contentChangedAt":"2026-09-11T16:08:28.414Z","schemaVersion":2},"datasetVersion":"2026-09-14T00:17:10.932Z"}