From e2e7fcfede9828fb8ed4fc5d1abf292632763906 Mon Sep 17 00:00:00 2001 From: Elarwei Date: Sat, 28 Feb 2026 13:00:29 +0800 Subject: [PATCH] fix(feishu): address third-round codex bot review feedback P2 - Throw ENOENT for non-existing absolute image paths (docx.ts): Previously a non-existing absolute path like /tmp/missing.png fell through to Buffer.from(..., 'base64') and uploaded garbage bytes. Now throws a descriptive ENOENT error and hints at data URI format for callers intending to pass JPEG binary data (which starts with /9j/). P2 - Fail clearly when insert anchor block is not found (docx.ts): insertDoc previously set insertIndex to -1 (append) when after_block_id was absent from the parent's child list, silently inserting at the wrong position. Two fixes: 1. Paginate through all children (documentBlockChildren.get returns up to 200 per page) before searching for the anchor. 2. Throw a descriptive error if after_block_id is still not found after full pagination, instead of silently falling back to append. --- extensions/feishu/src/docx.ts | 25 ++++++++++++++++--------- 1 file changed, 16 insertions(+), 9 deletions(-) diff --git a/extensions/feishu/src/docx.ts b/extensions/feishu/src/docx.ts index 116781e5615..97099a18a8c 100644 --- a/extensions/feishu/src/docx.ts +++ b/extensions/feishu/src/docx.ts @@ -420,22 +420,28 @@ async function resolveUploadInput( } // local path: ~, ./ and ../ are unambiguous (not in base64 alphabet). - // Absolute paths (/...) are supported but must exist on disk. + // Absolute paths (/...) are supported but must exist on disk. If an absolute + // path does not exist we throw immediately rather than falling through to + // base64 decoding, which would silently upload garbage bytes. + // Note: JPEG base64 starts with "/9j/" — pass as data:image/jpeg;base64,... + // to avoid ambiguity with absolute paths. if (imageInput) { - const resolved = imageInput.startsWith("~") ? imageInput.replace(/^~/, homedir()) : imageInput; + const candidate = imageInput.startsWith("~") ? imageInput.replace(/^~/, homedir()) : imageInput; const unambiguousPath = imageInput.startsWith("~") || imageInput.startsWith("./") || imageInput.startsWith("../"); const absolutePath = isAbsolute(imageInput); - if (unambiguousPath || (absolutePath && existsSync(resolved))) { - const buffer = await fs.readFile(resolved); + + if (unambiguousPath || (absolutePath && existsSync(candidate))) { + const buffer = await fs.readFile(candidate); if (buffer.length > maxBytes) { throw new Error(`Local file exceeds limit: ${buffer.length} bytes > ${maxBytes} bytes`); } - return { buffer, fileName: explicitFileName ?? basename(resolved) }; + return { buffer, fileName: explicitFileName ?? basename(candidate) }; } - if (absolutePath && !existsSync(resolved)) { + + if (absolutePath && !existsSync(candidate)) { throw new Error( - `File not found: "${resolved}". ` + + `File not found: "${candidate}". ` + `If you intended to pass image binary data, use a data URI instead: data:image/jpeg;base64,...`, ); } @@ -836,8 +842,9 @@ async function insertDoc( const parentId = blockInfo.data?.block?.parent_id ?? docToken; - // documentBlockChildren.get pages at 200 entries; scan all pages so insert - // works when parent has lots of children. + // Paginate through all children to reliably locate after_block_id. + // documentBlockChildren.get returns up to 200 children per page; large + // parents require multiple requests. // eslint-disable-next-line @typescript-eslint/no-explicit-any -- SDK block type const items: any[] = []; let pageToken: string | undefined;