Skip to content

Plane→GitHub sync silently overwrites GitHub bodies with corrupted markdown #9077

Closed

Description

@aspiers

Affected: Plane Cloud (app.plane.so), official GitHub integration, bidirectional sync. Severity: silent data loss on GitHub. Issue and comment bodies authored by humans are overwritten by the makeplane bot user. Recoverable only via the GraphQL userContentEdits API; not visible in the GitHub web Edit-history UI as "corruption" because the corrupted version becomes the displayed body.

Summary

Under bidirectional sync, Plane→GH push fires on certain Plane-side write operations and pushes a fresh markdown serialisation of the work item / comment to GitHub. The serialisation is produced by running Plane's stored TipTap HTML through a tiptap-html → markdown converter. The stored HTML has already been corrupted at GH→Plane import time (see sub-issues below) so what gets pushed back to GitHub is a corrupted version of the original human-authored body.

There is no in-product notification that this has happened.

What triggers a Plane→GH push

Empirically observed on Plane Cloud under bidirectional sync.

Pushes that fire:

  • POST a Plane comment with external_source = "GITHUB" and a real external_id — pushes the comment body to GH.
  • PATCH a Plane comment whose external_source = "GITHUB" — pushes that comment body (corresponding GH comment overwritten ~13-75s later).

Pushes that do NOT fire:

  • PATCH /work-items/<id>/ with priority-only change — tested twice with real priority transitions; no Plane→GH activity in 9 minutes.
  • PATCH with a no-op value — suppressed when stored state matches GH.
  • PATCH of a Plane-native comment (no external_source).
  • Re-enabling bidir after Plane-side writes under unidir — Plane does not flush a queue on re-enable; pending divergence stays inert until a fresh modify event.

Description-body pushes: reliably reproduce within ~90 seconds of a multi-write sequence that includes one or more comment POSTs under bidir. Isolating individual operations did not reproduce within the same window — trigger isolation incomplete and likely needs internal webhook logs to nail down.

What we'd want fixed

Suppress Plane→GH push when the field's value is unchanged from the most recent GH→Plane import for that field. The integration should not be the source of a userContentEdits revision unless a human has actually edited the Plane copy. MVP: string-equality on serialised markdown.

This single fix would prevent the destructive overwrite even if the converters themselves remain buggy — the field-level corruption is already covered separately in the sub-issues below.

Surface destructive overwrites in the product. Currently the makeplane bot edits flood notifications with no signal that user-authored content has been replaced.

Sub-issues

Each pattern below is a distinct corruption mode confirmed reproducible:

The corruption originates at GH → Plane import time, not at the Plane → GH push. We verified this by running Plane's open-source tiptap-html → markdown converter (packages/utils/src/editor/markdown-parser/ in makeplane/plane) locally over real description_html dumps from an affected workspace: the converter faithfully reproduces the observed GH-side corruption. The import-side markdown → tiptap-html converter (in the closed makeplane/silo-commercial image) is what stores the already-broken HTML that the serialiser later faithfully emits.

Recovery for affected GitHub bodies

{
  repository(owner: "<owner>", name: "<repo>") {
    issue(number: <n>) {
      userContentEdits(first: 20) {
        nodes { editedAt editor { login } diff }
      }
    }
  }
}

The oldest diff is the pristine human-authored body. PATCH it back via gh api -X PATCH repos/<owner>/<repo>/issues/<n> -F body=@pristine.md.

Workarounds while waiting for fix

  • Run sync unidirectionally (GH → Plane only). Plane stays a downstream view; GH is canonical and stays clean.
  • For any required Plane-side writes via the API: flip to unidir before the writes, run them, verify GH was untouched via userContentEdits audit, then re-enable bidir. Note re-enabling does not flush a queue, but a subsequent modify event on any affected work item can still trigger a stale push.

Activity

  1. Saurabhkmr98 commented on Aug 31, 2026

    @Saurabhkmr98
    Member

    We've fixed this in our latest release. Please try and let us know, meanwhile closing this issue.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

No labels
No labels

Type

No type

Fields

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