Skip to content

Commit 1ef067c

Browse files
committed
fix(page): gate sync when snapshot has unknown block IDs
rollbackSyncedPage previously guarded only on snapshot.Truncated. If a page contains blocks that cannot be represented in markdown, the PageMarkdown snapshot carries their IDs in UnknownBlockIDs, and a replace_content rollback would replay a lossy markdown body and permanently drop those blocks. Treat non-empty UnknownBlockIDs as an unsafe rollback source and fail early at the pre-sync gate with the same pattern used for truncation, so page sync does not silently lose content on recovery.
1 parent 391db41 commit 1ef067c

3 files changed

Lines changed: 23 additions & 0 deletions

File tree

cmd/page.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -604,6 +604,11 @@ func runPageSync(ctx *Context, file, title, parent, parentDB, icon string, skipL
604604
output.PrintError(finalErr)
605605
return finalErr
606606
}
607+
if len(snapshot.UnknownBlockIDs) > 0 {
608+
finalErr := fmt.Errorf("cannot sync local images safely: page %s contains %d block(s) that cannot be represented in markdown, so rollback on a failed substitution would drop them. Retry without local images or remove the unsupported blocks before syncing", fm.NotionID, len(snapshot.UnknownBlockIDs))
609+
output.PrintError(finalErr)
610+
return finalErr
611+
}
607612
}
608613

609614
body, localUploads, err = prepareLocalImageUploads(ctx, bgCtx, file, body)

cmd/page_local_images.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -232,6 +232,9 @@ func rollbackSyncedPage(ctx context.Context, client pageUpdater, pageID string,
232232
if snapshot.Truncated {
233233
return fmt.Errorf("skipped rollback: page markdown snapshot was truncated; replaying would lose content")
234234
}
235+
if len(snapshot.UnknownBlockIDs) > 0 {
236+
return fmt.Errorf("skipped rollback: page markdown snapshot omits %d block(s) that cannot be represented in markdown; replaying would drop them", len(snapshot.UnknownBlockIDs))
237+
}
235238
return client.UpdatePage(ctx, mcp.UpdatePageRequest{
236239
PageID: pageID,
237240
Command: "replace_content",

cmd/page_local_images_test.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -199,6 +199,21 @@ func TestRollbackSyncedPageSkipsTruncatedSnapshot(t *testing.T) {
199199
}
200200
}
201201

202+
func TestRollbackSyncedPageSkipsUnknownBlocks(t *testing.T) {
203+
snapshot := &api.PageMarkdown{
204+
Markdown: "# Title\n\nLossy content\n",
205+
UnknownBlockIDs: []string{"block-1", "block-2"},
206+
}
207+
208+
err := rollbackSyncedPage(context.Background(), nil, "page-id", snapshot)
209+
if err == nil {
210+
t.Fatalf("rollbackSyncedPage returned nil error; expected unknown-blocks error")
211+
}
212+
if !strings.Contains(err.Error(), "cannot be represented in markdown") {
213+
t.Fatalf("rollbackSyncedPage error = %q, want it to mention unrepresentable blocks", err.Error())
214+
}
215+
}
216+
202217
func TestRollbackSyncedPageSkipsNilSnapshot(t *testing.T) {
203218
if err := rollbackSyncedPage(context.Background(), nil, "page-id", nil); err != nil {
204219
t.Fatalf("rollbackSyncedPage returned %v, want nil", err)

0 commit comments

Comments
 (0)