From: Christian Brauner <brauner@kernel.org>
To: "Kernel.org Tools" <tools@kernel.org>
Cc: "Christian Brauner (Amutable)" <brauner@kernel.org>,
Konstantin Ryabitsev <konstantin@linuxfoundation.org>
Subject: [PATCH b4 25/27] review-tui: keep post-send bookkeeping out of the review send error path
Date: Fri, 31 Jul 2026 11:21:24 +0200 [thread overview]
Message-ID: <20260731-work-b4-editor-branch-guard-v1-25-de68a7c8e4cb@kernel.org> (raw)
In-Reply-To: <20260731-work-b4-editor-branch-guard-v1-0-de68a7c8e4cb@kernel.org>
The same shape the thank-you path just lost. action_send() does the SMTP
handoff and everything after it -- the tracking write, the Answered
flags, the outgoing-seen record -- inside one try whose handler reports
"Send failed". The reviews are on the list at that point and we would be
telling the maintainer to send them again.
Here the bookkeeping mostly swallows its own failures, and that turns out
to be the worse half of the problem. _save_tracking() drops the bool
save_tracking_ref() hands back, so a tracking write that does not land is
reported as nothing at all. The next session reads the branch without a
sent-revision on any review, offers all of them as unsent, and the
maintainer sends the series twice.
Split the two. Only the SMTP call keeps that handler; once send_mail()
has returned we report the outcome and run the rest under its own, which
has to catch rather than propagate because this is a screen callback.
Pass _save_tracking()'s answer up and say so when it is False.
The two quick-reply paths get the same treatment. Nothing after the send
can raise there today, but a tight try is what keeps that true, and it
lets the branch that has already tested --email-dry-run stop passing it
on to a helper that would test it again.
Signed-off-by: Christian Brauner (Amutable) <brauner@kernel.org>
---
src/b4/review_tui/_lite_app.py | 19 ++++++-----
src/b4/review_tui/_review_app.py | 74 ++++++++++++++++++++++++----------------
2 files changed, 55 insertions(+), 38 deletions(-)
diff --git a/src/b4/review_tui/_lite_app.py b/src/b4/review_tui/_lite_app.py
index 951057f..51e1437 100644
--- a/src/b4/review_tui/_lite_app.py
+++ b/src/b4/review_tui/_lite_app.py
@@ -813,17 +813,18 @@ class LiteThreadScreen(ModalScreen[None]):
output_dir=None,
reflect=False,
)
- if sent is None:
- self.app.notify('Failed to send reply.', severity='error')
- elif self._email_dryrun:
- self.app.notify(f'Dry-run: reply to {lmsg.fromemail} logged, not sent')
- self._mark_answered(node)
- else:
- mark_outgoing_seen([msg], dryrun=self._email_dryrun)
- self.app.notify(f'Reply sent to {lmsg.fromemail}')
- self._mark_answered(node)
except Exception as ex:
self.app.notify(f'Send failed: {ex}', severity='error')
+ return
+ if sent is None:
+ self.app.notify('Failed to send reply.', severity='error')
+ return
+ if self._email_dryrun:
+ self.app.notify(f'Dry-run: reply to {lmsg.fromemail} logged, not sent')
+ else:
+ mark_outgoing_seen([msg])
+ self.app.notify(f'Reply sent to {lmsg.fromemail}')
+ self._mark_answered(node)
def action_back(self) -> None:
if self._thread_nodes:
diff --git a/src/b4/review_tui/_review_app.py b/src/b4/review_tui/_review_app.py
index 0982f55..1f38325 100644
--- a/src/b4/review_tui/_review_app.py
+++ b/src/b4/review_tui/_review_app.py
@@ -1654,27 +1654,42 @@ class ReviewApp(LoreNodeShutdownMixin, CheckRunnerMixin, App[None]):
output_dir=None,
reflect=False,
)
- if sent is None:
- self.notify('Failed to send review emails.', severity='error')
- else:
- self._reply_sent = True
- self._tracking['series']['status'] = 'replied'
- # Stamp sent-revision on every non-skip review so that
- # if this series is later upgraded to a newer revision,
- # the upgrade step can detect which reviews were already
- # sent and auto-skip the unchanged patches.
- my_email = str(self._usercfg.get('email', 'unknown@example.com'))
- current_rev = int(self._series.get('revision', 1))
- for target in [self._series] + list(self._patches):
- review = target.get('reviews', {}).get(my_email, {})
- if review and review.get('patch-state') != 'skip':
- review['sent-revision'] = current_rev
- self._save_tracking()
- self._mark_patches_answered(msgs)
- mark_outgoing_seen(msgs, dryrun=self._email_dryrun)
- self.notify(f'Sent {sent} review email(s).')
except Exception as ex:
self.notify(f'Send failed: {ex}', severity='error')
+ return
+ if sent is None:
+ self.notify('Failed to send review emails.', severity='error')
+ return
+ self.notify(f'Sent {sent} review email(s).')
+
+ # The mail is out; everything below only records that. It gets its
+ # own handler so a bookkeeping failure is never reported as a send
+ # failure, and never escapes this screen callback.
+ try:
+ self._reply_sent = True
+ self._tracking['series']['status'] = 'replied'
+ # Stamp sent-revision on every non-skip review so that
+ # if this series is later upgraded to a newer revision,
+ # the upgrade step can detect which reviews were already
+ # sent and auto-skip the unchanged patches.
+ my_email = str(self._usercfg.get('email', 'unknown@example.com'))
+ current_rev = int(self._series.get('revision', 1))
+ for target in [self._series] + list(self._patches):
+ review = target.get('reviews', {}).get(my_email, {})
+ if review and review.get('patch-state') != 'skip':
+ review['sent-revision'] = current_rev
+ if not self._save_tracking():
+ # Silence here means the next session offers these reviews
+ # as unsent and the maintainer sends them twice.
+ self.notify(
+ f'Sent, but could not record it on {self._branch}',
+ severity='warning',
+ )
+ self._mark_patches_answered(msgs)
+ mark_outgoing_seen(msgs, dryrun=self._email_dryrun)
+ except Exception as ex:
+ logger.debug('Post-send bookkeeping failed: %s', ex, exc_info=True)
+ self.notify(f'Sent, but recording it failed: {ex}', severity='warning')
self.push_screen(SendScreen(msgs), _on_send_confirmed)
@@ -1751,15 +1766,16 @@ class ReviewApp(LoreNodeShutdownMixin, CheckRunnerMixin, App[None]):
output_dir=None,
reflect=False,
)
- if sent is None:
- self.notify('Failed to send reply.', severity='error')
- elif self._email_dryrun:
- self.notify(f'Dry-run: reply to {entry["fromemail"]} logged, not sent')
- else:
- mark_outgoing_seen([msg], dryrun=self._email_dryrun)
- self.notify(f'Reply sent to {entry["fromemail"]}')
except Exception as ex:
self.notify(f'Send failed: {ex}', severity='error')
+ return
+ if sent is None:
+ self.notify('Failed to send reply.', severity='error')
+ elif self._email_dryrun:
+ self.notify(f'Dry-run: reply to {entry["fromemail"]} logged, not sent')
+ else:
+ mark_outgoing_seen([msg])
+ self.notify(f'Reply sent to {entry["fromemail"]}')
def _load_followup_msgs(self, msgs: List[Any]) -> None:
"""Parse msgs into follow-up comments and refresh the display."""
@@ -2240,9 +2256,9 @@ class ReviewApp(LoreNodeShutdownMixin, CheckRunnerMixin, App[None]):
self.notify('Agent review data loaded')
self._restore_original_branch()
- def _save_tracking(self) -> None:
- """Save tracking data to the review branch."""
- b4.review.save_tracking_ref(
+ def _save_tracking(self) -> bool:
+ """Save tracking data to the review branch. True if it landed."""
+ return b4.review.save_tracking_ref(
self._topdir, self._branch, self._cover_text, self._tracking
)
--
2.53.0
next prev parent reply other threads:[~2026-07-31 9:21 UTC|newest]
Thread overview: 29+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-07-31 9:20 [PATCH b4 00/27] Stop the editor branch guard from eating review replies Christian Brauner
2026-07-31 9:21 ` [PATCH b4 01/27] review-tui: mark all outgoing mail as read, not just " Christian Brauner
2026-07-31 9:21 ` [PATCH b4 02/27] tests: cover the shared outgoing-seen helper Christian Brauner
2026-07-31 9:21 ` [PATCH b4 03/27] review: close the messages database when auto-marking fails Christian Brauner
2026-07-31 9:21 ` [PATCH b4 04/27] review: use the same busy timeout for both review databases Christian Brauner
2026-07-31 9:21 ` [PATCH b4 05/27] review: drop the unused return value from set_flags_bulk() Christian Brauner
2026-07-31 9:21 ` [PATCH b4 06/27] review: don't let archiving a series raise Christian Brauner
2026-07-31 9:21 ` [PATCH b4 07/27] tests: cover an unwritable series archive Christian Brauner
2026-07-31 9:21 ` [PATCH b4 08/27] review-tui: keep post-send bookkeeping out of the send error path Christian Brauner
2026-07-31 9:21 ` [PATCH b4 09/27] tests: cover the thank-you send's post-send bookkeeping Christian Brauner
2026-07-31 9:21 ` [PATCH b4 10/27] review-tui: say when a take didn't complete Christian Brauner
2026-07-31 9:21 ` [PATCH b4 11/27] tests: cover the unaccepted take in the thank-and-archive chain Christian Brauner
2026-07-31 9:21 ` [PATCH b4 12/27] review-tui: use the shared helper to delete a review branch Christian Brauner
2026-07-31 9:21 ` [PATCH b4 13/27] ty: check reachability in the repository the commit landed in Christian Brauner
2026-07-31 9:21 ` [PATCH b4 14/27] tests: cover the publish check using the repository it is given Christian Brauner
2026-07-31 9:21 ` [PATCH b4 15/27] edit_in_editor: make the branch guard opt-in Christian Brauner
2026-07-31 9:21 ` [PATCH b4 16/27] tests: cover the opt-in branch guard in edit_in_editor Christian Brauner
2026-07-31 9:21 ` [PATCH b4 17/27] edit_in_editor: work in the tree the caller names Christian Brauner
2026-07-31 9:21 ` [PATCH b4 18/27] tests: cover edit_in_editor working in the caller's tree Christian Brauner
2026-07-31 9:21 ` [PATCH b4 19/27] tui: route editor launches through one non-fatal helper Christian Brauner
2026-07-31 9:21 ` [PATCH b4 20/27] tests: cover an editor failure leaving the review TUI standing Christian Brauner
2026-07-31 9:21 ` [PATCH b4 21/27] review-tui: only put back a branch b4 checked out itself Christian Brauner
2026-07-31 9:21 ` [PATCH b4 22/27] tests: cover the review TUI's branch-restore guard Christian Brauner
2026-07-31 9:21 ` [PATCH b4 23/27] tests: pin the default branch in the queue-delivery fixture Christian Brauner
2026-07-31 9:21 ` [PATCH b4 24/27] ty: an unknown remote tip is undetermined, not unpublished Christian Brauner
2026-07-31 9:21 ` Christian Brauner [this message]
2026-07-31 9:21 ` [PATCH b4 26/27] tests: cover the review send's post-send bookkeeping Christian Brauner
2026-07-31 9:21 ` [PATCH b4 27/27] review: close the tracking database when archiving fails Christian Brauner
2026-07-31 13:14 ` [PATCH b4 00/27] Stop the editor branch guard from eating review replies Christian Brauner
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260731-work-b4-editor-branch-guard-v1-25-de68a7c8e4cb@kernel.org \
--to=brauner@kernel.org \
--cc=konstantin@linuxfoundation.org \
--cc=tools@kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox