Skip to content

notion-md: tree sync non-idempotent on emphasis-marker style and paragraph-after-list (perpetual update) #756

@schickling-assistant

Description

@schickling-assistant

Summary

After a clean local-authoritative tree sync (#747), plan reports some pages as update forever, even though their content on Notion is correct. The delta is pure round-trip canonicalization, not a real change, so the noop oracle never fires for those pages and trees can't reach a clean state.

Two distinct, reproducible canonicalization gaps:

Case A — emphasis-marker style (* vs _)

Local body uses *emphasis*; Notion's enhanced-markdown GET returns _emphasis_. The canonical comparison (semanticEquivalent) does not fold the two emphasis-marker styles, so the page is permanently update.

local : the grammar is *closed*
remote: the grammar is _closed_

Case B — paragraph following a bullet list

A paragraph written after a bullet list (blank-line separated) is attached to the last list item by Notion's enhanced-markdown parser on push. GET then returns one merged block, while the local body has two blocks. They never converge.

local:
- read state through the built-in tools.

  Reads run immediately; writes route to approval.

remote (after round-trip):
- read state through the built-in tools. Reads run immediately; writes route to approval.

Repro

  1. Author an .nmd page containing either shape, bind it, sync (local-authoritative).
  2. plan the same tree again → the page is still classified update.
  3. Pull the page back (sync <page-id> out.nmd) and diff against the local body → the only difference is the canonicalization above.

Impact

Benign for content fidelity (Notion holds the intended text), but:

  • defeats the pushed-hash noop oracle / a clean plan,
  • forces authors to contort source markdown to dodge it, or live with always-dirty trees,
  • makes "tree is in sync" unobservable for affected pages.

Proposed directions

  • Case A: extend the canonical comparison to treat *x*/_x_ (and **x**/__x__) as equivalent.
  • Case B: either canonicalize "trailing paragraph adopted into preceding list item" as equivalent on comparison, or treat paragraph-after-list as a push-fidelity violation that plan/validate warns on (so it's an authoring error, not silent perpetual churn).

Refs #673.

Posted on behalf of @schickling
field value
agent_name 🌵 cl2-acacia
agent_session_id 83deaad9-b22c-455d-9e36-90816d4ac54c
agent_tool Claude Code
agent_tool_version 2.1.165
agent_runtime Claude Code 2.1.165
agent_model claude-opus-4-8
worktree effect-utils/main
machine dev3
tooling_profile dotfiles@unknown-dirty

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:notionNotion API client / react / schema packages · Set: manualorigin:agentFiled or primarily produced by an AI agent · Set: AI agent or manualtype:bugSomething broken or a regression · Set: manual

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions