| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
|
ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing |
Sorry, something went wrong.
PR Summary by QodoFix copyPadWithoutHistory changeset length, trailing newline drift, and async writes 🐞 Bug fix 🧪 Tests 🕐 40+ Minutes AI Description
|
Sorry, something went wrong.
Code Review by Qodo🐞 Bugs (1) 📘 Rule violations (0) 📎 Requirement gaps (0) 📜 Skill insights (0) 1. No cleanup on failure 🐞 Bug ☼ Reliability Description Code Evidence Agent prompt Context ✅ Tickets: 🎫 "Cleanup revisions" fails due to missing revision 🎫 copyPadWithoutHistory writes an invalid changeset: copies grow a newline and fail pad.check() forever
Tip of the day
💡 Did you know, you can turn on the rule miner and Qodo learns your standards from review history |
Sorry, something went wrong.
| // Must be awaited: an un-awaited rejection here (an invalid changeset, | ||
| // a failed write) surfaces as an unhandled rejection instead of failing | ||
| // the copy, which is how the length bug above went unnoticed. | ||
| await dstPad.appendRevision(changeset, authorId); |
There was a problem hiding this comment.
1. No cleanup on failure 🐞 Bug ☼ Reliability
If dstPad.appendRevision() rejects, copyPadWithoutHistory() throws after creating the destination pad and writing author/group metadata, leaving a partially-created destination pad behind. A retry with force=false will then fail early with “destinationID already exists”.Agent Prompt
### Issue description
`copyPadWithoutHistory()` now correctly `await`s `dstPad.appendRevision(...)`, which means failures (db write, hook error, invariant error) will reject the copy. However, the function has already created/persisted the destination pad and updated related metadata (authors + group pad list). If `appendRevision()` fails, there is no rollback, so an incomplete destination pad can remain and a subsequent retry with `force=false` will fail with `destinationID already exists`.
### Issue Context
The destination pad is created via `padManager.getPad(...)` (which initializes and persists rev0). Then author/group side effects are applied before the awaited `appendRevision()`.
### Fix Focus Areas
- src/node/db/Pad.ts[746-843]
- src/node/db/Pad.ts[717-737]
### Suggested fix
- Wrap the destination-pad creation + revision append in a `try { ... } catch (err) { ... }`.
- In the `catch` block, best-effort remove the destination pad that was just created (for example `await dstPad.remove()`), then rethrow the original error.
- Consider moving `copyAuthorInfoToDestinationPad()` and the `group:${destGroupID}.pads` update to *after* the revision append succeeds, to reduce rollback work.
- Add a regression test that forces `appendRevision()` to throw (stub/hook) and asserts the destination pad does not exist afterward (or is retryable without `force=true`).
ⓘ Copy this prompt and use it to remediate the issue with your preferred AI generation tools
Sorry, something went wrong.
pack() takes the TOTAL length of the new document, but the call passed
`assem.getLengthChange()` -- a delta. The resulting revision 1 header
disagreed with its own ops, with two consequences:
1. opsFromAText() skips the source document's final newline, so both
of the destination pad's rev-0 newlines survived and the copy came
out one newline longer than the source. It grew again on every
subsequent copy.
2. The copy's revision 1 failed checkRep(), so the copied pad failed
pad.check() from then on.
(2) is the damaging one. deleteRevisions() -- `cleanup.keepRevisions`,
and compactPad with a keep count -- calls pad.check() before it touches
anything. compactPad's full-collapse mode goes through this function
twice, so the feature meant to reclaim database space produced pads
that could never be cleaned up again.
Pass the total length, drop the surplus newline, and trim the char bank
to the characters actually inserted.
Also await dstPad.appendRevision(): an un-awaited rejection surfaced as
an unhandled rejection rather than failing the copy, which is how the
malformed changeset went unnoticed. Same for the `saveToDatabase()`
that the comment above it calls "flush the source pad".
The existing API test quantified the off-by-one without naming it: the
direct getHTML test strips one trailing '<br>' while the copy test
stripped '<br><br>'. It now strips one, like the source. The compactPad
test's tolerance for "adjusted trailing whitespace" is likewise replaced
with a byte-exact comparison plus a check() assertion.
Found while investigating #8134.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
| Back | FazBrowse Home | New Git URL |
Fixes #8139. Found while investigating #8134.
Problem
copyPadWithoutHistory() packed a length delta into pack()'s newLen parameter, which wants a total:
The resulting revision 1 header disagrees with its own ops. Two consequences:
(2) is the damaging one: deleteRevisions() — cleanup.keepRevisions from the admin UI, and compactPad with a keep count — calls pad.check() before touching anything, and compactPad's full-collapse mode routes through this function twice. The feature that exists to reclaim database space left pads that could never be cleaned up again.
Fix
Tests
New src/tests/backend/specs/copyPadWithoutHistoryIntegrity.ts: text equality with the source, checkRep() on revision 1, pad.check() on the copy, stability across three chained copies, author attribution, and both compactPad full-collapse properties (text preserved, and the pad remains cleanable afterwards). 6 of its 7 cases fail without the Pad.ts change.
Two existing tests encoded the bug rather than catching it, and are corrected here:
Full backend suite: 1628 passing, 0 failing.
Note on existing data
This stops new pads being corrupted; it does not repair pads already copied or compacted by the old code. Those still fail pad.check() and so still refuse keep-count cleanup. Worth a follow-up if we want a repair path — happy to take that on separately.
🤖 Generated with Claude Code