siyuan-note/siyuan · error
invalid image operation key
Error message
invalid image operation key
What it means
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.
Source
Thrown at kernel/mcp/tools/image.go:281
return CallToolResult{}, false
}
if record.State == imageOperationStateRunning {
return imageUnknown("a previous image operation was interrupted; its external result is unknown and it must not be retried automatically"), true
}
if record.State != imageOperationStateCompleted || record.Result.IsError {
removeImageOperationRecord(key)
return CallToolResult{}, false
}
if record.AssetPath != "" && !imageOperationAssetExists(record.DocumentID, record.AssetPath) {
removeImageOperationRecord(key)
return CallToolResult{}, false
}
return record.Result, true
}
func saveImageOperationRecord(key string, meta imageOperationMeta, state string, result CallToolResult) error {
if !validImageOperationKey(key) {
return errors.New("invalid image operation key")
}
assetPath := meta.AssetPath
if state == imageOperationStateCompleted {
if resultPath := imageResultAssetPath(result); resultPath != "" {
assetPath = resultPath
}
}
if state != imageOperationStateRunning && state != imageOperationStateCompleted {
return errors.New("invalid image operation state")
}
record := imageOperationRecord{
Version: 1, CreatedAt: time.Now().UnixMilli(), State: state, Action: meta.Action, DocumentID: meta.DocumentID,
AssetPath: assetPath, Result: result,
}
data, err := json.Marshal(record)
if err != nil {
return err
}View on GitHub (pinned to 8641553a1f)
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
Defensive patterns
Strategy: try-catch
Validate before calling
// keep and pass only keys obtained from the original tool response
if (!opKey || typeof opKey !== 'string') throw new Error('operation key missing; start a new image operation'); Type guard
const hasValidKeyShape = (k) => typeof k === 'string' && k.length > 0 && /^[A-Za-z0-9_-]+$/.test(k);
Try / catch
try {
await runImageOperation(opKey);
} catch (e) {
if (String(e.message).includes('invalid image operation key')) {
opKey = await startNewImageOperation(args); // restart to mint a fresh key
} else throw e;
} Prevention
- 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
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- invalid image operation state
- image operation failed
- unsupported or invalid image:
- decode image failed:
- encode image failed:
AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11).
Data as JSON: /api/errors/eee15bb3e3ea782d.
Report an issue: GitHub.