Skip to content

[bug] Windows: пробел или %VAR% в имени файла ломают конвертацию, и это рапортуется как «скорее всего скан → OCR» #3

Description

@istrami

Что произошло

На Windows convert.js не конвертирует файл, в имени которого есть пробел — в том числе пример из справки самого скрипта (node convert.js a.pdf b.xlsx "отчёт за март.pptx").

Причина: spawnSync('npx', ['-y', '@firecrawl/anydoc', f, '-o', out], { shell: isWin }). Под shell: true аргументы уходят в cmd.exe одной строкой, cmd режет её по пробелам, и anydoc получает второй «вход».

Тот же корень даёт второй случай: имя с %VAR% (например отчёт %TEMP% март.docx) — cmd раскрывает переменную, и в anydoc уходит уже другой путь (io error: The filename, directory name, or volume label syntax is incorrect. (os error 123)).

Отдельно, и по-моему важнее самого бага: исход категоризируется неверно. Оба случая печатаются как ПРОПУСК с советом «Пропущенные, скорее всего, сканы или PDF-картинки без текстового слоя — прогони их через OCR», хотя файл идеально читаемый. А process.exit(fail > 0 ? 1 : 0) даёт exit 0, потому что это skip, а не fail. В пакетном режиме по папке такой файл молча теряется при статусе «успех», а пользователя отправляют на бессмысленный OCR.

Как воспроизвести

Команда:

node skills/doc2md/scripts/convert.js "a b.docx"

Ожидал: OK

Получил:

ПРОПУСК C:\...\a b.docx — anydoc: one document per invocation: unexpected second input 'b.docx'

Готово: 0 конвертировано, 1 пропущено (не читается/зашифровано/скан), 0 ошибок вызова
Пропущенные, скорее всего, сканы или PDF-картинки без текстового слоя — прогони их через OCR (скилл pdf, шаг «сделать PDF searchable») и повтори.

Кириллица тут не при чём — проверял раздельно, данные.csv и отчёт.docx без пробела конвертируются нормально. Ломает именно пробел.

Окружение

  • ОС и версия: Windows 11 Enterprise 26200, проверял и из cmd.exe, и из PowerShell
  • Node.js (node -v): v26.2.0, npm 11.13.0
  • Версия обёртки: skills/doc2md/scripts/convert.js (кросс-платформенная Node-версия), клон main от 06.08.2026
  • anydoc: через npx -y @firecrawl/anydoc, версия 0.1.6 (глобально не ставил)

Секция «Протестировано» в SKILL.md просит перепроверить и дополнить её при первом боевом запуске на Windows — этим и занимаюсь. Вывод «convert.js написан без shell-специфичного синтаксиса … но это рассуждение о корректности, а не проверка» оказался ровно в точку.

Что помогло у меня — не настаиваю на способе

  1. Квотировать аргументы под shell: true — лечит пробелы, но не лечит %VAR%: раскрытие переменных на командной строке cmd не экранируется.

  2. Корневой фикс — убрать cmd.exe из цепочки. shell: true нужен только чтобы нашёлся npx.cmd; вместо этого можно позвать npx-cli.js напрямую тем же node, без shell:

    const npxCli = path.join(path.dirname(process.execPath), 'node_modules', 'npm', 'bin', 'npx-cli.js');
    spawnSync(process.execPath, [npxCli, '-y', '@firecrawl/anydoc', f, '-o', out]);

    После этого у меня прошли все имена: a b.docx, отчёт за март.docx, a&b^c%TEMP% d.docx — результат байт-в-байт совпал с эталоном. Оговорка: путь к npx-cli.js — деталь раскладки npm-бандла, а не публичный контракт, и на nvm-windows/Volta он может лежать не рядом с node.exe.

  3. Глубже, и это, кажется, интереснее всего для самого скилла: у @firecrawl/anydoc есть официальный Node API. В установленном 0.1.6 есть toMarkdown(path), toMarkdownBytes, toDocument, formatFromPath и типизированный ConvertErrorCode (unsupported | malformed | encrypted | resourceLimit | missingPart | io), таргет x86_64-pc-windows-msvc. Если звать его вместо CLI:

    • исчезает cmd.exe и вместе с ним весь класс проблем с именами — чинить нечего, потому что ломаться больше нечему;
    • исчезают накладные npx на каждый файл. Замерил у себя: через npx ~1.02 с на файл (5 тривиальных CSV = 5.11 с) при собственной медиане anydoc ~4.4 мс; после перехода на API 20 файлов конвертируются за 111 мс. На пачке это порядка 100×, а пакетная обработка — ровно то, ради чего скилл и нужен;
    • вместо угадывания по exit-коду приходит точная причина, и malformed document больше не советует OCR. Сейчас, например, лок-файл Word ~$договор.docx попадает в обработку и рапортуется как «скорее всего скан».

    Я в итоге так и сделал у себя и был неправ, когда сначала полез чинить обёртку вокруг CLI: правильный ход оказался не «квотировать лучше», а не звать cmd вообще.

Две мелочи, если пригодятся

  • -o последним аргументом молча игнорируется: outDir = argv[++i] даёт undefined, дальше if (outDir) ложно → всё пишется рядом с исходниками, без предупреждения, exit 0. Человек думает, что собрал результат в отдельную папку.
  • При -o и рекурсивном обходе имя выхода строится из path.basename(f), поэтому 2025\акт.pdf и 2026\акт.pdf дают один акт.pdf.md — второй молча перезаписывает первый, оба со статусом OK. Это тот же класс, что уже поправлен для отчёт.csv / отчёт.docx (сохранением расширения), только по другой оси — по подпапкам.

Спасибо за скилл: идея «сконвертировать пачку до того, как её начнёт читать агент» оказалась ровно тем, что было нужно, и basename + .md против коллизий, и отсечение циклов по симлинкам — приятно аккуратные детали.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions