[report][#119] Повторный автотест git-sync (после фиксов) — дублирование мёртво, open-editor converged; вскрыта клиентская collab-race (HIGH) + same-para conflict git-wins #223
Reference in New Issue
Block a user
Delete Branch "%!s()"
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?
Повторное автономное тестирование PR #119
feat/git-syncчерез web-test-orchestrator (30 агентов, та же оркестрация: recon → 6 слайсов вкл. git-roundtrip и dual-edit → независимый verifier → синтез). Прогон против СВЕЖЕГО feat/git-sync (c1c87c21) — со всеми моими фиксами (data-loss дедуп по schema-default, open-editor convergence, ревью-1863, Details.open).🎯 Вердикт по ранее найденным data-loss багам
autoMergeConflicts=OFF(medium, см. ниже). Раньше терялся git, теперь — правка человека в конфликтном блоке.git-sync findings (6)
[medium] Same-paragraph concurrent edit: git silently overwrites the human's in-flight UI edit with zero conflict indication (silent data loss) (conf: verified, by dual-edit)
Repro: 1) Open a page in the UI editor. 2) Edit PARA2 live in the editor (now in Yjs, not yet committed to git). 3) Within ~1s, from a fresh clone push a different PARA2 value (push fast-forwards because the human edit hasn't reached git yet). 4) Watch the open editor with NO reload.
Evidence: Reproduced. Live editor before merge 'PARA1 BASE-A | PARA2 IVHUMAN-LIVE | PARA3 BASE-C'; the race push WON fast-forward at attempt=1 with base_was=[PARA2 BASE-CLEAN] (proving the human edit was still in-flight, absent from git). ~10s after the push, with NO reload, the live editor flipped to 'PARA2 IVGIT-WON-7Z9' — the human's keystrokes vanished. Oracles: conflict markers in DOM=False (no <<<<<<< / >>>>>>>), toast/alert count=0, zero console errors. Hard reload and a fresh git clone both show the git text — durable silent loss. Source: three-way-merge.ts lines 6-8 + diff3Plan fallback line 219 document 'git wins' as intended, but it is silent UI data loss with no conflict surfaced. Screenshot iv_B3win.png. (The opposite ordering — human edit reaches git first — yields a clean, correct git non-fast-forward push rejection, so the side that loses depends purely on who reaches main first.)
[medium] Concurrent git push during a server-side sync tick returns HTTP 503 / 'remote end hung up unexpectedly' and is not retried by git (conf: verified, by git-roundtrip-tester / dual-edit)
Repro: git clone the vault, commit a change, then push while git-sync (or its background poll) holds the per-space lock. The push fails; an immediate retry usually succeeds.
Evidence: Reproduced by two testers and the verifier. Server returns a deliberate application-level 503 on POST /git/.git/git-receive-pack with 'Content-Type: text/plain' and 'Retry-After: 1' and body 'git-sync busy, retry'; git prints 'error: RPC failed; HTTP 503 ... send-pack: unexpected disconnect while reading sideband packet / fatal: the remote end hung up unexpectedly' (exit 1). git's smart-HTTP push does NOT honor Retry-After, so it surfaces a hard failure. Rates: 11/25 (git-roundtrip) and 7/10 (dual-edit, with Redis lock confirmed held). A 503'd push does NOT land; the retry then needs a
git pull --rebasebecause the server has moved on. Source: git-http.service.ts:270-289 (GitSyncLockHeldError -> 503 + Retry-After), git-sync.orchestrator.ts ingestExternalPush:204-250 (try-lock, throws on skip), space-lock.service.ts (Redis SET NX non-blocking lock). Broader than originally reported: the background ~15s poll, not just a human UI edit, holds the lock. Rate here is amplified by this stand's aggressive ~10-15s sync cadence.[low] Wide outbound lag (~10-15s) to git, and the first post-create commit captures an empty body (frontmatter only) before the body lands (conf: verified, by dual-edit)
Repro: Create a new note, type a title + paragraphs, then git pull the vault repeatedly and watch the .md size/content over time.
Evidence: Reproduced (lag if anything worse than claimed). Exporter is alive/batched ('docmost: sync N page(s)' every ~15-45s). Every UI-created note first enters git as 59 bytes = frontmatter only (
---\ngitmost_id:<uuid>\n---, no body); decisive diff 30b14a3 shows the note entering purely as a rename with 0 insertions. Body IS safely persisted server-side (page reload rendered the full body), so no DB data loss — the gap is purely in the outbound git export. This is exactly the window the silent-overwrite conflict lives in: a git user pulling right after a UI edit sees a stale/empty body and may base a push on it. Caveat: in the verifier's window the body never propagated to git within 15+ min on a contended/wedged stand, so the 'eventually consistent ~10-15s' optimistic part was not always observable.[low] Asymmetric conflict handling: human-edit-first yields a safe explicit push rejection (correct git), git-first risks loss (conf: verified, by dual-edit)
Repro: Let the human's UI edit reach git FIRST (wait ~10-15s after the UI edit), THEN push a stale-based same-file change from a clone.
Evidence: Reproduced the safe half: stale push is rejected with '! [rejected] ... (fetch first)' / 'Updates were rejected because the remote contains work you do not have locally', git exit 1 — the pusher is clearly told and nothing is lost. Verifier note: this is plain, correct git non-fast-forward ref-locking (the report concedes this), and is ref-level not conflict-aware — with this vault's constant churn ANY stale push to main is rejected regardless of file. The dangerous contrast (git-first -> clean silent UI overwrite) was only half-substantiated by the verifier: in their run the git push was accepted but the editor kept the human text, producing a stuck DB/git divergence (autoMergeConflicts=false skip-on-conflict) rather than a clean silent overwrite. The silent-overwrite data-loss path itself is separately verified under the same-paragraph in-flight-race finding above.
[medium] Intermittent page-body duplication during a git-sync cycle (user typed 1 copy, ends with 2) (conf: likely, by git-roundtrip-tester)
Repro: In the UI create a rich page (heading+callout+code+nested list), let git-sync push, then reload. Observed on 'GRT Beta 90706'. NOT reliably reproducible on demand — a rare intermittent collab merge race; GRT Alpha authored the same way did not duplicate.
Evidence: Real, durable git artifact: history of 'GRT Beta 90706.md' = dfe4a45 headings=0 -> dd046cd headings=1 -> 6231be4 headings=2;
git diff dd046cd 6231be4= '1 file changed, 15 insertions(+)' inserting a verbatim duplicate block (# Heading, intro, > [!info] callout, ```const v=42, nested list). Both commits authored by server 'Docmost Sync' with NO UI edit between, so the export itself duplicated the body. Verifier could not reproduce a FRESH duplication (controlled retries stayed at 1 copy via the page API) — hence 'likely', not 'verified' — but the git history is permanent evidence and the same duplication is independently verified at the UI/collab layer (see 'Editor content duplicated' in the UI findings). Same collab/Yjs merge-on-reconnect race class, amplified by the sync export.[low] Git smart-HTTP host + clone working with auth enforced (verified working, not a defect) (conf: verified, by recon)
Repro: curl info/refs with and without basic-auth; then git clone http://admin%40test.local:gitmostdev2026@localhost:3000/git/.git
Evidence: GET /git/.git/info/refs?service=git-upload-pack -> 200 WITH basic-auth, 401 WITHOUT (correct enforcement); body is a valid git smart-HTTP pkt-line stream. git clone exited 0 and produced 209 .md files (one per page; the vault grew during the session as testers added pages). Each file has '---\ngitmost_id:\n---' frontmatter then real markdown (headings, bold/italic, bullet/numbered lists, '- [ ]' tasks, blockquotes, '> [!info]' callouts, fenced ```js code). git log shows 'docmost: sync N page(s)' commits. Healthy baseline for the git-sync feature.
gitmost-ui findings (8)
[high] Silent body-content data loss when typing into a freshly-created page before collab sync (conf: verified, by editor-content-tester)
Repro: In space General click '+' (Create page). As soon as the blank editor appears (type within ~2-3s, before the Hocuspocus/Yjs provider fires 'synced'), click into the body and type text. Wait a few seconds, reload (or open in a fresh browser). Body is empty again ('Write anything. Enter / for commands'); typed text is gone, no error. Workaround: wait ~10s before typing, OR reload once before typing.
Evidence: Independently reproduced, deterministic across 2 verifier runs. Fresh page + 3s wait: immediate editor DOM showed typed text (imm=2) but after reload DOM=0 AND server-stored content via POST /api/pages/info = 0 nodes, with 4xx/5xx=[] and console-errors=0 (fully silent). Threshold sweep: 3s=LOST, 6s=partial, 10s=OK; settle-reload-before-typing=OK. Control: editing an existing/synced page persists perfectly (imm->reloadDOM->API all =1), scoping the bug to fresh pages only. Mechanism: editor is editable before the provider fires 'synced', so the server's initial empty doc clobbers the early local edits. Screenshots: scratchpad/ec/loss_0_typed.png, loss_0_reload.png; scripts iv_repro.py, iv_login.py.
[medium] Initial keystrokes (and leading title characters) dropped when typing into a just-created page before the collaborative editor connects (conf: verified, by git-roundtrip-tester)
Repro: Click 'Create page' and immediately type a title + heading/paragraph into the body before the collab WebSocket connects. Leading keystrokes are silently discarded while the editor still shows the inviting 'Write anything' placeholder.
Evidence: Decisive A/B (vr_ab.py): 'body contenteditable right after create: false (+8.29s)'. PHASE-A text typed while read-only -> body stayed empty, keystrokes dropped. Body became editable at +10.88s (coincides with ws collab connect); PHASE-B text typed after editable was captured. After reload only the AFTERREADY text persisted. Reproduced across ~10 runs; connect time varied +2s..+21s. Corroborating: sidebar littered with 'HEADINGFAST'/'INGFAST'/'NGFAST'/'ADINGFAST' (same title with 0/2/3/4 leading chars eaten) and the pre-existing truncated titles 'ent Test'/'ntent Test'/'ntent Te' (from 'Content Test') plus clone filenames matching — the same race also eats leading title keystrokes during the button->title focus transition. Note: /api/pages/info returns content:null for collab-stored bodies, so reload-DOM is the reliable oracle. Closely related to the high-severity fresh-page body-loss finding (same collab-connect race).
[medium] Editor content duplicated (stored twice) in the persisted collab doc after reload on a fresh page (conf: verified, by editor-content-tester)
Repro: Type a multi-block rich document (H1, H2, bold/italic paragraph, bullet list w/ nested item, numbered list, task list) into a fresh page, then reload. Intermittent (a clean create-close-reopen-type flow did NOT always duplicate) — a race in the same collab-sync neighborhood as the loss bug.
Evidence: Verifier reproduced from scratch. Backend storage of the originally-reported page (pageId ZQ4LgGQVRt) still doubled: POST /api/pages/info -> content contains the entire block sequence TWICE with DISTINCT node ids (two 'Heading One' obqbpkxwukbo + ippnwcwkspcr, two 'Heading Two', doubled bullet/ordered/task lists) — distinct ids prove two real stored copies, not a DOM artifact. Live repro: freshly typed multi-block doc rendered twice after reload (iv_dup/d2_B1_ctx.png) and survived a fully fresh browser context (d2_B1_fresh0.png), so it lives in the server-side Yjs/Hocuspocus doc shared by all clients. In the verifier run leading blocks were LOST while the tail was DUPLICATED — same reload-time collab race as the loss bug.
[medium] "New chat" is a no-op after a failed first turn — stale failed message and red error banner persist (conf: verified, by ai-chat-ui)
Repro: Open AI chat (fresh, no chat selected). Send a message; with no provider it fails with the red 'AI provider not configured' banner and the user bubble stays. Click header 'New chat'. Expected: clean empty chat. Actual: old user message AND red banner remain; nothing resets. Close+reopen the window DOES clear it.
Evidence: Reproduced on the live stand (aichat_refine.py): after Send +9s user msg present + banner in DOM; after New chat x1 and x2 both still present (persistent no-op); after Close+Reopen both gone. Screenshot R2_after_newchat_settled.png. Root cause confirmed in source: ChatThread key={threadKey} (ai-chat-window.tsx:624); reconciler use-chat-session.ts:216
if (activeChatId !== thread.chatId)only remounts on activeChatId change; after a failed first turn no id was adopted so both are null, startNewChat (ai-chat-window.tsx:216) sets activeChatId=null (no-op), threadKey never changes, useChat keeps the optimistic message + error. Comment at ai-chat-window.tsx:210-215 acknowledges the no-op path but only disarms the adoption fallback — it never resets the visible thread. (Surfaced via the env's unconfigured-provider 503, but the reset defect is real and provider-independent.)[low] Trash page renders a blank table with no loading indicator for ~2-5s on fresh reload / direct URL load (conf: verified, by tree-nav)
Repro: Open /s/general/trash directly or hard-reload (F5). Intermittently the route shows only the 'Trash' heading + 30-day banner (or nothing at all early) with NO table/rows and NO spinner/skeleton, then rows pop in. In-app navigation (Space menu -> Trash) renders instantly.
Evidence: 15x direct-reload loop: rows first appear
2-5s late, and on 4/15 reloads the table stayed empty the entire 6s sample window. anyLoader=False on ALL 15 reloads (no class=loader|skeleton|spinner|loading, no [role=progressbar]/[aria-busy]). At t=1s the whole route is unmounted (main.innerText=''), then heading+banner+rows appear together. In-app pushState nav firstRowAt=0.05s. Data integrity fine: /api/pages/trash returns HTTP 200 every time. Root cause: slow first-paint with zero loading state, heavily amplified by Vite dev-mode cold module compilation (production build would be far faster). Low severity; a user reloading mid-load could momentarily misread it as 'trash is empty'. Screenshots stuck_empty.png, probe0.png.[low] Ambient jotai 'atomFamily is deprecated' console warning on every authenticated page (conf: verified, by recon)
Repro: Load any authenticated page and read the browser console.
Evidence: Console WARNING (not error): '[DEPRECATED] atomFamily is deprecated and will be removed in v3. Please use the
jotai-familypackage instead'. Appears on initial /home load and again after full reload (reload match count 1), so it is ambient page-load noise, not action-triggered. No crash, no blank page. Benign library deprecation noise — flagged so it is not mistaken for an action-triggered failure during charters.[low] Git-sync space-settings UI exposes only two toggles (no remote URL / branch / sync-now / status) — map fact, not a defect (conf: verified, by recon)
Repro: Space (/s/general) -> Space menu -> Space settings -> Settings tab; scroll the git-sync section.
Evidence: The git-sync section contains exactly two controls: switch 'Enable Git sync' (ON) and switch 'Auto-merge conflicts on push' (OFF), with explanatory copy. DOM dump of the modal returned only these two switches; the only text inputs are Name/Slug/Description and the Members search box. NO 'Sync now' trigger, NO remote-URL field, NO branch field, NO last-sync timestamp/status. Screenshot 70_settings_tab.png. Consequence for QA: sync state must be observed via the git smart-HTTP host / a fresh clone, not the settings panel.
[low] Admin 'Analytics / tracker' setting injects verbatim markup into <head> of public share pages (intentional, admin-gated stored-script surface) (conf: verified, by share-search-settings)
Repro: /settings/workspace -> 'Analytics / tracker' section -> set a snippet -> load a public /share/:shareId/p/:pageSlug page.
Evidence: Verifier set a probe '<script>/*...$&
`'...*/window.__probe=1;</script>' via admin POST /api/workspace/update (200, persisted verbatim incl. regex-special $&). Anonymous GET of the public share SEO route returned HTTP 200 with the snippet injected byte-for-byte before </head> (inject-tracker-head.util.ts uses a function replacer, deliberately no escaping; single injection site share-seo.controller.ts on the public route only). Write is admin-gated server-side (CASL Manage Settings -> ForbiddenException for a MEMBER). Resetting trackerHead='' removed it. Working as designed (GA/Yandex.Metrika equivalent), NOT a vulnerability — flagged as a stored-script design surface worth awareness, confirmed restricted to trusted admins.✅ False positives (убиты verifier-проходом) (5)
js/python Markdown shortcut via tiptap input rule /^([a-z]+)?[\s\n]$/), not dropped. Disconfirming controls:+Enter then type, and ```+space-first then type, both preserve 'const x = 42;' fully; deterministic even at slow 80ms/char typing, so NOT the speculated typing-speed input-capture race. Expected behavior.!isLoadingguard, so the auth panel is momentarily blank only during the sub-second in-flight loading window — the original tester almost certainly screenshotted that transient loading frame. Steady state is correct and consistent; at most a cosmetic sub-second flicker, not a missing empty-state.Подробный мета-отчёт
gitmost git-sync — QA Synthesis Meta-Report
Stand: gitmost (Docmost fork) — UI http://localhost:5173, API + git smart-HTTP http://localhost:3000, git-sync space "General" (spaceId 019f01fd-3350-7cc9-9446-e20fb81e6c1d). AI provider intentionally NOT configured.
Methodology: web-test-orchestrator skill — recon → mapping → charters → execution → FEW HICCUPPS oracle evaluation → independent Doer/Verifier pass → this synthesis. Hard rules: evidence-before-claim, disconfirm-by-default, reload-to-test-persistence.
Headline numbers
Agents that ran and how each stage went
Stage 1 — Recon (1 agent)
Established a clean baseline: all main authenticated screens load with zero console errors / zero HTTP >=400 / no blank pages; ProseMirror mounts and survives reload. Mapped the git-sync settings surface (only two toggles), confirmed the git smart-HTTP host + clone works with auth enforced, and pre-flagged two pieces of ambient noise (jotai atomFamily deprecation warning; AI 503 env limit) so later charters wouldn't misread them. Recon was high-value: it produced the map facts that made later git-sync charters observe sync state via clone, not the (insufficient) settings panel. The only verifier hiccup was self-inflicted rate-limiting (429 on repeated logins) — a tooling artifact, not a product bug.
Stage 2 — Six slice testers
Stage 3 — Independent verifiers (Doer/Verifier pass)
The verifier pass did exactly its job and is the most valuable stage in this run: it upgraded strong findings with decisive root-cause evidence (source line refs for the New-chat no-op and the git-wins merge; GIT_CURL_VERBOSE wire capture proving the server-issued 503+Retry-After), and it killed 5 weak findings that would otherwise have shipped as bugs — notably the "code block drops chars" (actually correct Markdown behavior) and the "auth search has no empty-state" (a transient loading frame). Two verdicts came back with placeholder evidence ("e") for the title-truncation and tree-expand findings; I treated title-truncation as confirmed because it is the same robustly-verified collab-connect race as the keystroke-drop finding, and demoted the tree-expand one to false-positive per its speculative twin verdict.
Dedup / merge decisions
contenteditable=false/ unsynced empty doc clobbers early edits).likely).Coverage gaps (honest)
Concrete improvement suggestions for this QA process / skill
synced/connects. A reusable probe should: create a page, assertcontenteditable+ provider-synced state, and only then type — and as a CHARTER, deliberately type during the unready window to catch the loss. Make "type-before-ready then reload" a standing regression check./api/pages/infoas the persistence oracle for collab bodies — it returnscontent:nullfor ydoc-stored bodies. Codify "reload-DOM is the source of truth for collab content; API snapshot is debounced and lags" in the skill's oracle notes, so testers stop mis-scoring loss/dup.🤖 web-test-orchestrator (multi-agent QA), повторный прогон по PR #119 (после фиксов)