Skip to content

bug(calendar): --remove-zoom leaves description block when description was Zoom-only #591

@alexisperumal

Description

@alexisperumal

Summary

gog calendar update --remove-zoom on an event whose description contains only the gog-managed Zoom block leaves the description block intact, even though the Zoom meeting is correctly cancelled on Zoom's side. Recipients of the event would still see a clickable "Join Zoom Meeting" link pointing at a now-cancelled meeting.

The bug surfaces only when the gog-managed Zoom block is the entire description. Events that have additional user content before/after the block work correctly (block stripped, surrounding content preserved).

Repro (against main, 5afc19f)

# 1. Create an event with --with-zoom (description is the Zoom block only)
gog calendar create primary \
  -a <your-acct>@gmail.com \
  --summary "Repro" \
  --from "<future-time>" --to "<future-time + 30m>" \
  --with-zoom \
  --json | jq -r '.event.id'  # capture EVENT_ID

# 2. Verify the description contains only the gog-zoom-meeting block
gog calendar event primary $EVENT_ID -a <your-acct>@gmail.com --json | jq -r '.event.description'

# 3. --remove-zoom
gog calendar update primary $EVENT_ID -a <your-acct>@gmail.com --remove-zoom

# 4. Re-fetch and observe: description still contains the Zoom block, but the
#    Zoom meeting is cancelled on Zoom's side (audit line confirmed action=delete)
gog calendar event primary $EVENT_ID -a <your-acct>@gmail.com --json | jq -r '.event.description'

Expected: description becomes empty (or whitespace-only).
Actual: description unchanged; still contains the gog-zoom-meeting block referencing the cancelled meeting.

Reproduces 100% of the time on macOS, against main HEAD 5afc19f, against a consumer Gmail account.

Differential (verified)

Test setup --remove-zoom result
Event description was only the gog-managed Zoom block ❌ Block remains; description unchanged
Event description had user content + the gog-managed Zoom block ✅ Block stripped; user content preserved

Root cause (suspected)

In internal/cmd/calendar_edit.go, the --remove-zoom path at ~line 973 correctly produces an empty description via:

patch.Description = applyZoomDescriptionBlock(descriptionForPatch(existing, patch), "")

But the subsequent merge at calendar_edit.go:994-996 treats empty as "no change requested":

if strings.TrimSpace(patch.Description) != "" {
    merged.Description = patch.Description
}

When the post-strip description is empty (only-Zoom-block case), this branch is skipped and the existing description survives the merge.

This is a pre-existing patch-merge gap that the new description-mode path exposes — not a regression introduced by #590. The TrimSpace != "" check is reasonable for "user didn't pass --description" cases but doesn't differentiate from "user wants description cleared," which --remove-zoom newly relies on.

Suggested fix direction

Distinguish between "user didn't set description" and "user wants description cleared." The Google Go client uses ForceSendFields for forcing zero-value strings (vs NullFields for pointer/struct fields), so the likely candidate for clearing the Description string field is patch.ForceSendFields = append(patch.ForceSendFields, "Description") when the post-strip result is empty AND we know we intend to clear (e.g., --remove-zoom path).

For reference, the legacy ConferenceData clearing at line 974-977 uses NullFields correctly — but that's because ConferenceData is a pointer-to-struct, not a string:

if existing != nil && existing.ConferenceData != nil && isZoomConferenceData(existing.ConferenceData) {
    patch.ConferenceData = nil
    patch.NullFields = append(patch.NullFields, "ConferenceData")
}

So the Description field needs analogous intent (force-clear), but via the string-appropriate ForceSendFields mechanism.

Impact

  • Functional: users running --remove-zoom may believe the Zoom info is gone from the event, but recipients still see the (now-dead) Zoom link in the event description
  • Data hygiene: stale Zoom URLs persist in Calendar events long after the meetings are cancelled
  • Audit trail mismatch: the [zoom] action=delete audit line claims success; the Calendar-side state contradicts it

Discovered during

Live-capture validation of PR #590 against real Zoom Pro + real Google Calendar (consumer Gmail). Same setup used to validate the original architecture finding that drove the description-mode pivot. Test infrastructure still in place; happy to re-test any proposed fix against the same setup.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    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