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

  1. Start the image operation again from the original image tool call to obtain a fresh valid operation key
  2. Do not cache or reuse operation keys across kernel restarts
  3. Ensure the pending operation lookup and record save use the same key string (no trimming/encoding differences)
  4. 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

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


AI-assisted analysis of siyuan-note/siyuan@8641553a1f (2026-09-11). Data as JSON: /api/errors/eee15bb3e3ea782d. Report an issue: GitHub.