siyuan-note/siyuan · error

save generated image failed: %w

Error message

save generated image failed: %w

What it means

Returned by GenerateDocumentImage when InsertAssetBytes(bt.RootID, ...) fails after a successful image generation. InsertAssetBytes looks up the block tree, computes the assets dir, mkdir's it, hashes/dedups, and writes the file. The error is wrapped with markImageExecutionUnknown because the image has already been generated (and may be billed) — the bytes exist in memory but could not be persisted.

Source

Thrown at kernel/model/assets.go:461

		Data: generated.Data, MIMEType: generated.MIMEType, Extension: generated.Extension, RevisedPrompt: generated.RevisedPrompt,
	}, nil
}

// GenerateDocumentImage 使用通用图片生成能力创建文档资源。
func GenerateDocumentImage(ctx context.Context, request GenerateDocumentImageRequest) (GenerateDocumentImageResult, error) {
	bt, err := resolveMultimodalDocument(request.DocumentID)
	if err != nil {
		return GenerateDocumentImageResult{}, err
	}
	generated, err := GenerateImage(ctx, GenerateImageRequest{
		Prompt: request.Prompt, Size: request.Size, Quality: request.Quality, OutputFormat: request.OutputFormat,
	})
	if err != nil {
		return GenerateDocumentImageResult{}, err
	}
	assetPath, _, err := InsertAssetBytes(bt.RootID, "ai-image"+generated.Extension, generated.Data)
	if err != nil {
		return GenerateDocumentImageResult{}, markImageExecutionUnknown(fmt.Errorf("save generated image failed: %w", err))
	}
	return GenerateDocumentImageResult{
		Artifact: ImageArtifactRef{
			Kind: "image", Path: assetPath, MIMEType: generated.MIMEType, DocumentID: bt.RootID,
		},
		RevisedPrompt: generated.RevisedPrompt,
	}, nil
}

func resolveMultimodalDocument(documentID string) (*treenode.BlockTree, error) {
	bt := treenode.GetBlockTree(strings.TrimSpace(documentID))
	if bt == nil {
		return nil, errors.New("document not found: " + documentID)
	}
	return bt, nil
}

func validateImageModel(provider *conf.Provider, imageModel *conf.Model) error {

View on GitHub (pinned to 251596fc0d)

Solutions

  1. Free disk space and ensure util.DataDir is writable.
  2. Check the wrapped error: a 'block not found' (Conf.Language(71)) means the doc vanished mid-call — refresh the block tree and re-resolve.
  3. Do not retry blindly: the provider call already succeeded (IsImageExecutionUnknown is true); persist manually if you still have the bytes.

Example fix

// before
res, err := model.GenerateDocumentImage(ctx, req)
if err != nil { return err } // loses the generated bytes

// after — surface the unknown-execution nature to the user
res, err := model.GenerateDocumentImage(ctx, req)
if err != nil {
    if model.IsImageExecutionUnknown(err) {
        // image was generated but not saved; tell the user, offer re-save
        return fmt.Errorf("image generated but save failed (you may be charged): %w", err)
    }
    return err
}
Defensive patterns

Strategy: try-catch

Type guard

func isImageExecUnknown(err error) bool { return model.IsImageExecutionUnknown(err) }

Try / catch

res, err := model.GenerateDocumentImage(ctx, req)
if err != nil {
    if model.IsImageExecutionUnknown(err) {
        // image was generated (possibly billed) but not saved — do not retry blindly
        return fmt.Errorf("generation ok but save failed: %w", err)
    }
    return err
}

Prevention

When it happens

Trigger: The block tree entry for the document disappeared between the resolveMultimodalDocument call and the InsertAssetBytes call; the assets directory cannot be created (read-only data dir, disk full); os.MkdirAll or writeAssetFile fails due to permissions or path length; the asset data is empty (shouldn't happen post-generation but is guarded).

Common situations: Disk full on the volume holding util.DataDir; data dir on a read-only mount; antivirus blocks the write on Windows; the doc was deleted in a concurrent operation.

Related errors


AI-assisted analysis of siyuan-note/siyuan@251596fc0d (2026-08-12). Data as JSON: /api/errors/0f589837dd0d3d37. Report an issue: GitHub.