Sync: a refused flag or move fails the pass after its suggestions were filed, and shows the server's own words #22
Labels
No labels
bug
documentation
enhancement
performance
security
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Dependencies
No dependencies set
Reference
Postulo/postulo-imap#22
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Found on 2026-09-29 in a read-only review of postulo-imap at
cc09c6381, following every mailbox call to what the person is shown when it fails, and runningsync_connectionagainst a fake client that refuses the flag and the move.Related to postulo/postulo#335.
Opening the folder, searching and fetching are wrapped into
MailboxErrorwith a sentence for the person and the server's answer in the log. Flagging, moving, and the folder checks are not. They run after the suggestions of the pass were created, so a refusal there throws away the report of a pass that did its work, marks the connection as failed, and puts the far side's text in front of the person, which the module's docstring says never happens.sync()also letsMailboxErroritself escape, so even the wrapped failures reach the person with a class name in front.Related to #1 (closed), which took server errors out of Test for connecting and signing in; the calls below were left out.
src/postulo_imap/mailbox.py:210-219,Mailbox.markandMailbox.move:add_flags,folder_exists,create_folderandmovewith notry, unlikeselect,unread_uidsandfetchabove them.src/postulo_imap/sync.py:214-217: the flag or the move, after the loop that callssuggest().sync()has onlyfinally: close(client).src/postulo_imap/sync.py:140,test():client.folder_exists(folder)is inside atrythat catchesMailboxErroronly.src/postulo_imap/sync.py:167-175:connect,select,unread_uidsandfetchraiseMailboxError, andsync()does not turn it intoSyncReport(error=…).src/postulo_imap/mailbox.py:179-184: the docstring ofunread_uidsdescribes what happens with "a server that refuses keywords", butuse_keywordcomes only from the setting (sync.py:173);PERMANENTFLAGSis never read.src/postulo_imap/mailbox.py:20-24: "Whatever the far side answered … goes to the log … and never into the message the person sees".plugins/syncing.py:83-85recordsf"{type(error).__name__}: {error}"for any exception a sync raises, andplugins/views.py:402-404does the same for Test.sync_connection.IMAPClient.moveis decorated@require_capability("MOVE")(imapclient 3.1.0) and there is no copy-and-delete fallback. One suggestion is filed, then:last_errorisCapabilityError: Server does not support MOVE capability,last_summaryisnothing to do,last_ok_atstays empty. The report that said one message was pulled is gone. Move mode does not use the keyword, so the next pass reads the whole window again and fails the same way, for as long as the setting stands. The same follows when the server will not create the subfolder.select_foldersucceeds, the suggestions are filed, and the refusedSTOREraisesIMAPClientError("store failed: <the server's answer>"). With the fake raising that,last_errorwas the answer word for word,error: store failed: [READ-ONLY] Mailbox is read-only, …, and one suggestion had been filed. The could not be matched note of such a pass is lost with the report.STOREbut does not keep new keywords (RFC 3501 allows it to ignore the change or keep it for the session only; reasoned, no real server was contacted) raises nothing: flag mode silently behaves as untouched mode and every pass reads the whole window again, which is the case the docstring claims to handle.test()withfolder_existsraising returned noTestResult; the exception left the plugin, and the core shows Test failed: followed by the exception's class and text.last_errorisMailboxError: The folder “Nope” could not be opened.The sentence written for the person arrives with a Python class name in front, and the core logs a traceback at error level for what is an ordinary wrong setting.tests/test_imap.py:633asserts only the substring, so it passes, andtests/test_imap.py:650-655pins the exception leavingsync().sync(), catchMailboxErrorand returnSyncReport(error=str(error)), so the person's sentence arrives without the class name and the log is not given a traceback for it.select_folder, readPERMANENTFLAGSfrom its answer; when\*is absent in flag mode, use the unflagged search and say so in a note, as the docstring already describes.\Deletedand expunge of those uids, or refuse the setting in Test with a sentence that says the server cannot move messages.folder_existsintest()the same way as the calls around it.Done when
tests/test_imap.pyhas a test in which the fake client'smoveraises after one recognised message was read:sync()returns a report withpulled == 1, noerror, and a note that the messages could not be moved. Today the exception leavessync().add_flagsraising in flag mode, asserting that the server's text is in the log and not in the report.tests/test_imap.py:627-633assertsconnection.last_error == "The folder “Nope” could not be opened."exactly; today the class nameMailboxErrorand a colon come first. The test at:650-655asserts the same sentence on the returned report and still that the client was logged out.folder_existsraises:ImapSync().test(CONFIG)returns aTestResultthat is not ok and does not contain the exception's text. Today it raises.Fixed by pull request #25.