Skip to content

fix: false verify-move failure when a move empties the source mailbox#11

Merged
akeemjenkins merged 2 commits into
mainfrom
fix/verify-move-empty-mailbox
Jul 9, 2026
Merged

fix: false verify-move failure when a move empties the source mailbox#11
akeemjenkins merged 2 commits into
mainfrom
fix/verify-move-empty-mailbox

Conversation

@akeemjenkins

Copy link
Copy Markdown
Contributor

Fixes the regression reported while cleaning up labeling tests: higgs move "Labels/Needs-Reply-Test" Trash --uid 1 printed error[imap]: MOVE ...: verify move on ...: no such message even though the move fully succeeded.

Root cause

Proton Bridge's IMAP server (gluon) answers a UID-referencing SEARCH on an empty mailbox with NO no such message instead of an empty result: buildSearchOpUID resolves the UID set via snapMsgList.resolveUID, which errors whenever the snapshot holds zero messages (gluon snapshot_messages.go). Nonexistent UIDs in a non-empty mailbox resolve fine and match nothing — which is why bulk archives from INBOX always verified cleanly and this only surfaced when moving the last message out of a mailbox: the empty source is the expected success state, and the verification SEARCH misread it as a hard failure. The inverse of the original silent-failure bug, as reported.

Fix

Verification searches (presentUIDs, wrongFlagState) now go through a uidSearch helper: on a SEARCH error it re-checks with a criteria-less SEARCH (which gluon answers normally on empty mailboxes) — if the mailbox is provably empty, no UIDs can be present, which is exactly the confirmation the verifier needs; otherwise the original error still surfaces. No error-string matching, and genuine SEARCH failures on non-empty mailboxes still abort (covered by a regression test). SetFlagVerified gets the same guard: flag ops targeting an empty mailbox now produce per-UID failure rows instead of a hard abort.

TDD: internal/imapwrite/gluon_quirk_test.go adds a backend wrapper reproducing gluon's exact behavior (UID-criteria SEARCH errors iff mailbox empty) — the three regression tests fail with the exact reported error message before the fix. Full go test ./... -race passes.

Also investigated: extract --apply-as-label vs classify --apply labeling

Not a code divergence: all three label paths (classify --apply, apply-labels, extract --apply-as-label) call the identical imapapply.ApplyLabels (UID COPY to Labels/<name>). The behavioral difference observed is Proton's server-side semantics of the destination: COPY to a Labels/ mailbox adds a label view onto the single underlying message (so moving it from the label mailbox moves the one real message), while COPY to a folder (e.g. Archive) creates an independent copy, since a Proton message lives in exactly one folder. The earlier "genuine duplicates in Archive" came from folder copies (likely the since-fixed partial-MOVE fallback), not from labeling. So the single-message multi-view behavior is intentional, consistent across both paths, and is what makes labels work as persistent searchable tags.

🤖 Generated with Claude Code

akeemjenkins and others added 2 commits July 9, 2026 09:03
…ilbox

Proton Bridge (gluon) answers a UID-referencing SEARCH on an empty
mailbox with 'NO no such message' instead of an empty result: SEARCH UID
keys resolve against the mailbox snapshot, and resolution errors when
the snapshot has no messages (gluon snapMsgList.resolveUID). Moving the
last message out of a mailbox therefore made the post-move verification
SEARCH error, and MoveVerified reported a hard failure for a fully
successful move.

Verification searches now tolerate that answer only when a criteria-less
SEARCH proves the mailbox is empty — no UIDs can be present, which is
the confirmation the verifier wanted. Real SEARCH failures on non-empty
mailboxes still abort. The same guard covers SetFlagVerified, where
targeting an empty mailbox now yields per-UID failure rows instead of a
hard error.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@akeemjenkins
akeemjenkins merged commit 379770c into main Jul 9, 2026
8 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant