Open
agent_coder
wants to merge 4 commits from
feat/437-error-diagnostics into develop
pull from: feat/437-error-diagnostics
merge into: vvzvlad:develop
vvzvlad:main
vvzvlad:fix/409-invalid-node-validation
vvzvlad:refactor/448-generate-inventory
vvzvlad:refactor/445-execute-mapping
vvzvlad:refactor/446-derive-client-types
vvzvlad:fix/447-registry-stamp-ci
vvzvlad:fix/444-agent-loop-guards
vvzvlad:fix/396-sendnow-detached-run
vvzvlad:fix/452-immutable-cache-control
vvzvlad:develop
vvzvlad:docs/footnote-authoring-comment-cleanup
vvzvlad:fix/442-cursor-pagination
vvzvlad:feat/419-footnote-normalize
vvzvlad:feat/424-drawio-rules
vvzvlad:perf/399-comment-resolve-async
vvzvlad:perf/435-collab-token-cache
vvzvlad:perf/344-background-rerenders
vvzvlad:perf/348-backend-lowhanging
vvzvlad:feat/423-drawio-crud
vvzvlad:perf/401-collab-hotpath
vvzvlad:feat/430-live-reconnect
vvzvlad:perf/400-collab-session
vvzvlad:refactor/414-dedup-node-ops
vvzvlad:feat/417-new-comments-signal
vvzvlad:feat/408-createcomment-hints
vvzvlad:feat/407-persist-tool-errors
vvzvlad:feat/420-footnote-render
vvzvlad:dummy-review/mcp-hang-fix
vvzvlad:dummy-review/mcp-hang-base
vvzvlad:feat/381-resumable-sse-pr1
vvzvlad:feat/git-sync-2
vvzvlad:docs/agents-workspace-build-order
vvzvlad:feature/offline-sync
vvzvlad:perf/342-code-splitting
vvzvlad:perf/346-compression-cache
vvzvlad:feat/196-multi-cursor
vvzvlad:feat/370-page-versioning
vvzvlad:perf/343-typing-latency
vvzvlad:docs/how-to-test
vvzvlad:fix/ai-sdk-partial-output-oom
vvzvlad:test/351-generative-converter
vvzvlad:feat/371-roles-catalog
vvzvlad:refactor/345-server-converter
vvzvlad:refactor/294-spec-registry-cont
vvzvlad:fix/363-migration-order
vvzvlad:fix/e2e-callout-and-gate-build
vvzvlad:fix/docker-re2-toolchain
vvzvlad:feat/git-sync
vvzvlad:fix/media-roundtrip-stability
vvzvlad:fix/340-comment-panel-perf
vvzvlad:fix/332-deferred-tools
vvzvlad:fix/329-ephemeral-suggestions
vvzvlad:fix/330-search-in-page
vvzvlad:fix/328-resolved-anchor-spam
vvzvlad:fix/331-intraline-diff
vvzvlad:fix/324-coverage-gate
vvzvlad:fix/325-mobile-390
vvzvlad:feat/293-A-git-sync-package
vvzvlad:feat/300-avatar-oklch
vvzvlad:fix/321-banner-mobile
vvzvlad:feat/300-avatar-colors
vvzvlad:feat/315-comment-suggestions
vvzvlad:feat/scroll-restore-stable-wait
vvzvlad:feat/300-agent-avatar-stack
vvzvlad:feat/300-avatar-polish
vvzvlad:refactor/294-tool-spec-registry
vvzvlad:feat/scroll-restore-ux
vvzvlad:fix/responsive-tablet-sidebar
vvzvlad:feature/ai-chat-page-change-observability
vvzvlad:image-inline-center
vvzvlad:fix/283-short-remap-title
vvzvlad:fix/283-slash-layout
vvzvlad:image-inline-row
vvzvlad:feat/276-ai-chat-dock
vvzvlad:fix/269-table-menu-refocus
vvzvlad:docs/dev-stand-guide
vvzvlad:feat/266-scroll-position
vvzvlad:fix/260-collab-docname-slugid
vvzvlad:test/244-phase2-tail
vvzvlad:fix/262-reindex-progress-realtime
vvzvlad:fix/258-changelog-compare-links
vvzvlad:fix/244-dataloss-bugs
vvzvlad:feat/246-spoiler
vvzvlad:feat/221-image-captions
vvzvlad:test/244-part-b
vvzvlad:feat/251-intentional-clear
vvzvlad:fix/embeddings-reindex-progress
vvzvlad:refactor/193-tool-spec-registry
vvzvlad:fix/255-ws-redis-adapter-leak
vvzvlad:fix/252-e2e-open-handles
vvzvlad:feat/229-catalog-yaml
vvzvlad:feat/243-blob-sandbox
vvzvlad:feat/228-inline-footnotes
vvzvlad:fix/qa-ui-bugs-216-218
vvzvlad:feature/agent-roles-catalog
vvzvlad:fix/share-alias-rename
vvzvlad:fix/ai-chat-empty-render
vvzvlad:feat/191-chat-doc-binding
vvzvlad:feat/201-temporary-notes
vvzvlad:feat/198-interrupt-agent
vvzvlad:feat/ai-chat-full-history
vvzvlad:feat/199-ai-generate-title
vvzvlad:feat/205-share-aliases
vvzvlad:batch/issues-189-187-170
vvzvlad:feat/170-mcp-test-button
vvzvlad:feat/189-context-badge
vvzvlad:feat/198-interrupt-agent-send-now
vvzvlad:fix/issues-190-159
vvzvlad:fix/ai-chat-new-chat-during-stream
vvzvlad:fix/ai-chat-stream-perf
vvzvlad:batch/issues-2026-06-25
vvzvlad:feat/ai-chat-persistent-history
vvzvlad:fix/ai-chat-copy-chat-wysiwyg
vvzvlad:fix/ai-stream-reset-resilience
vvzvlad:fix/ai-stream-undici-timeout
vvzvlad:fix/footnote-review-1227-followup
vvzvlad:fix/ai-chat-token-counter-realtime
vvzvlad:docs/manual-qa-test-plan
No Reviewers
Labels
Clear labels
epic
needs-human
review/approved
review/changes-requested
review/needs
возможно, баг
Large multi-phase effort spanning many changes
эскалация: нужно решение человека
в последнем ревью нет открытых blocking-находок
последнее ревью оставило открытые blocking-находки
head не ревьюился (head != reviewed_head)
Похоже на баг, но требует подтверждения/воспроизведения; чиним, если повторится
No Label
review/approved
Milestone
No items
No Milestone
Projects
Clear projects
No project
No Assignees
Notifications
Due Date
No due date set.
Dependencies
No dependencies set.
Reference: vvzvlad/gitmost#441
Reference in New Issue
Block a user
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Delete Branch "feat/437-error-diagnostics"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Внятная диагностика ошибок тулов: модель получает от упавших тулов серверное сообщение вместо «Request failed with status code N» / «timeout of 30000ms exceeded», плюс fail-fast по обрезанному comment-UUID. Инцидент 2026-07-10 (агенты в петле, флуд дублей комментов) — модель не могла самокорректироваться из бесполезных текстов. Чиним в единственной авторитетной точке рождения текста —
DocmostClient(packages/mcp/src/client.ts) +CollabSession; слои index.ts / ai-chat-tools.service / normalizeToolError НЕ тронуты (проверено).closes #437, closes #436
Фаза A — центральный axios response-интерцептор. Регистрируется ПОСЛЕ re-login-интерцептора, МУТИРУЕТ
error.messagein place (не подкласс — живыеaxios.isAxiosError/error.response?.status/config._retryне ломаются), ставит guard_docmostFormatted. Формат<METHOD> <path> failed (<status> <statusText>): <serverMessage>.serverMessage— ТОЛЬКО из whitelist:data.message(массив→'; '/строка) →data.error→statusText; arraybuffer-тело JSON-парсится под 4KB-cap; сырая строка/HTML отбрасывается в statusText; общий cap 300 симв.;pathбез хоста и query; нет ответа →<METHOD> <path> failed: <code> (no response from server). Полное тело — только в stderr подDEBUG. Безопасность: тело ответа, заголовки (Authorization!), config, query, host в текст не попадают никогда.Фаза B — fail-fast
assertFullUuid(поглощает #436). Кидает ДО любого сетевого вызова на 5 точках:getComment,createComment.parentCommentId(только при truthy),updateComment,deleteComment,resolveComment. Текст подсказывает скопировать полный UUID изlist_comments/create_comment.pageIdНЕ валидируем (легитимный slugId). Regex канонический 8-4-4-4-12, version-agnostic (принимает UUIDv7), anchored (режет префиксы), без ReDoS.Фаза C — обогащение collab-ошибок (отдельный коммит). connect/persist/closed-таймауты в
CollabSession(там, где реально живут строки после #400/#431 — не в collaboration.ts) получаютpageId+ подсказку «transient — retry once; persistent = collab server unreachable/overloaded». Yjs-encode не тронут.How verified
Прогон на стенде (
node --test):pnpm --filter @docmost/mcp build→ чисто (tsc).node --test test/unit/error-diagnostics.test.mjs→ 21/21 (message массив→join, строка, только error, пусто→statusText, HTML отсутствует, Buffer JSON распарсен, Buffer-мусор→statusText, oversized-Buffer пропущен, no-response code+path, cap длины, guard-флаг без двойной обработки, non-axios passthrough, host/query вырезаны; assertFullUuid valid/prefix/garbage/empty; 401→relogin→успех (message не тронут); 401→relogin→повторный отказ (формат применён,_retryцел); все 5 comment-id сайтов кидают с НУЛЁМ сетевого трафика).test/unit/* + test/mock/*→ 579 pass, 2 fail — обе предсуществующие вmarkdown-converter.test.mjs(отступы вложенных списков), подтверждено на базе (stash → те же 2 fail), к диагностике отношения не имеют.Внутренний цикл: 1 проход внутреннего ревью (APPROVE WITH SUGGESTIONS). Правка по ревью: no-response ветка использовала
error.code ?? error.message— при отсутствииcodeсырое axios-сообщение содержит host:port, нарушая инвариант «host не течёт в модель». Ужесточил: толькоerror.code ?? "network error", сырой текст — в stderr под DEBUG; тест обновлён (+ассерт отсутствия host). Микро-nit ревью (копия Buffer до проверки размера) — по факту копии нет (Buffer.from на ArrayBuffer — view), сознательно не трогал.Checklist
Ревью — #441
feat(mcp): внятная диагностика ошибок тулов + fail-fast валидация comment-id (#437, #436)Вердикт: ПРАВКИ (CHANGES) — 1 правка (тест на обогащение collab-ошибок).
Полный веер из 9 аспектов по всему диффу PR (merge-base
ee33a293). Гейт зелёный: mcp build (tsc) 0, node --test 582/582 (error-diagnostics +450), server tsc 0.Безопасность (info-leak в модель) — крux, проверено и БЕЗОПАСНО (клик)
Строки ошибок тулов уходят в LLM, поэтому info-leak — главный риск. Проверено (2 субагента независимо + сверка):
requestPath(config)берёт ТОЛЬКО pathname (без host/query/fragment);extractServerMessageвайтлистит толькоmessage/error, сырой HTML/string-body отвергает. No-response путь используетerror.code(неerror.message) — фикс4aa2278e. Тест: IP «10.0.0.5»/хост «docs.example.com»/query-токен «secret» НЕ появляются в сообщении (мутационно-стойко: смена наerror.message→ тест падает).formatDocmostAxiosErrorНИКОГДА не читаетerror.request/error.config.headers(где bearer). Только method/pathname/status/statusText/вайтлист-data.assertFullUuid(каноничный UUID-регекс) на всех 5 comment-методах, срабатывает ДО сети/логина (тест: ноль сетевых на битом id).hint()добавляет только pageId (UUID, юзер его и так дал) + retry-подсказку; ни wsUrl, ни токена, ни host._docmostFormattedанти-двойная-обработка, сырьё логируется только под DEBUG; крэш-путей нет. Регрессий нет (49/49; правка test-data create-comment: обрезанные id → валидный UUID из-за нового гарда — необходимо, не регресс). Architecture (central diagnostics в interceptor, единая точка), conventions, documentation, simplification — LGTM. drawio-дрейф-гард зелёный.Правка (сделать, потом ре-ревью)
packages/mcp/test/unit/collab-session.test.mjs(напр. :251). Коммит #437 добавляетhint()(collab-session.ts:213) → к текстам connect-timeout/persist-timeout/closed дописывается(pageId …; transient — retry once; …). Но тесты матчат/Connection timeout to collaboration server/— БЕЗ хвоста-подсказки, т.е. проходят и С hint, и БЕЗ. Проверил: ни один assert не содержит «transient»/«retry»/«pageId». Enrichment (смысл коммита) не защищён — рефактор, убравшийhint(), тесты не поймают. Fix: ужесточить reject-регекспы, чтобы ассертить хвост (/transient — retry once/+ pageId).DO/ESCALATE нет сверх этого. Security-crux (no-leak диагностика) закрыт и мутационно-покрыт, fail-fast гард полон, всё остальное LGTM; недостаёт только ассерта на collab-hint (vacuous-тест). После правки — PASS.
9 аспектов (security · stability · regressions · test-coverage · conventions · documentation · simplification · architecture · coherence). No-leak scrub (host/token/body) проверен двумя субагентами + мутационно; collab-hint-gap подтверждён мной (регекспы матчат без хвоста). Escalate нет.
Поправил единственную правку + ребейзнул на актуальный develop.
[test-coverage] обогащение collab-ошибок (hint) не проверялось — исправлено. Ужесточил два reject-ассерта в
collab-session.test.mjs(connection-closed, connect-timeout) до<база> (pageId page-1; transient— теперь ассерт ловит хвост-подсказку из Phase C, а не проходит vacuous и с hint, и без. Мутационно:hint()→""→ ровно эти 2 теста краснеют (18→16). (persist-timeout-текст в тестах не фигурировал — добавлять пустой кейс только ради него не стал.)Ребейз на develop (был позади на 32 коммита): один конфликт в
client.ts— develop влил #435 (readCollabTokenTtlMsна том же месте, что мои #437-хелперы). Разрешил объединением: сохранил обе функции (закрылreadCollabTokenTtlMsсвоим}, следом мои диагностические хелперы). Мой фикс утечки host в no-response-ветке (error.code ?? "network error") уцелел.Гейт:
pnpm --filter @docmost/mcp buildчисто; полный сьютtest/unit/* + test/mock/*→ 618/618.no-response ветка формата использовала error.code ?? error.message: при отсутствии code axios-сообщения сетевых ошибок содержат host:port ('connect ECONNREFUSED 127.0.0.1:3000', 'getaddrinfo ENOTFOUND host'), что нарушает инвариант #437 «host никогда не попадает в видимое модели сообщение». Теперь только error.code (?? 'network error'); полный нативный текст уходит в stderr под DEBUG. code проставлен фактически для всех реальных no-response ошибок. Тест обновлён: сырое host-содержащее сообщение -> нейтральный reason, плюс ассерт что host в сообщении отсутствует. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>4aa2278e58to4809348457Ревью (раунд 2: ребейз + фикс) — #441
feat(mcp): внятная диагностика ошибок тулов + fail-fast comment-id (#437, #436)Вердикт: PASS ✅ — правка раунда 1 (тест collab-hint) закрыта.
Полный веер из 9 аспектов по всему диффу PR относительно НОВОГО merge-base (develop
0108dec0; PR ребейзнут — drawio-wire теперь в develop, из дельты #441 ушёл). Гейт зелёный (clean-room): mcp build (tsc) 0 против нового develop, collab-session + error-diagnostics 39/39.Закрытие правки раунда 1 (мутационно подтверждено)
collab-session.test.mjs: reject-тесты теперь ассертят хвост(pageId page-1; transientна connection-closed И connect-timeout. Мутация: сделалhint()возвращающим "" → оба теста падают (16/18). Т.е. полное удаление enrichment ловится. Проверил сам + субагент (регекспы верны, не переужаты).Ребейз чистый, security-crux цел (клик)
formatDocmostAxiosErrorчитает только method/pathname/status/statusText/вайтлист-data — НИКОГДАconfig.headers/config.data/сыройerror.message. UUID-гард на всех 5 comment-методах до сети. Interceptor-порядок (диагностика после re-login) сохранён.⛔ DROP — кодеру НЕ делать (лог калибровки)
[below-threshold]low[test-coverage] путь persist-timeout тоже используетhint(), но отдельного ассерта на его хвост нет (в отличие от 2 покрытых). Enrichment уже гардирован (полное удаление hint() ловят 2 теста); отдельное удаление только из persist-timeout — узкий под-кейс. Для полноты можно дописать 3-й ассерт, но не отдельный раунд.DO/ESCALATE нет. Правка раунда 1 закрыта и мутационно-держится; ребейз чистый, security-crux (no-leak) цел; гейт зелёный. Approve.
9 аспектов. collab-hint фикс проверен адверсариально (neuter hint → 2 теста падают) + ребейз-интеракция/no-leak-scrub — консолидированным субагентом. persist-timeout-ассерт — below-threshold DROP. Escalate нет.
View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.