On Fri, Apr 24, 2026 at 03:12:34PM -0400, Zi Yan wrote:
> On 24 Apr 2026, at 15:01, Peter Zijlstra wrote:
> 
> > On Fri, Apr 24, 2026 at 11:50:03AM -0700, Yosry Ahmed wrote:
> >
> >> But I imagine it's useful for reviewers to see Sashiko's feedback as
> >> well (without having to go look on the website).
> >
> > That's why I have a script; if I press 'S' in mutt on an 0/n email, said
> > script goes and does webrequest to sashiko and inserts the review
> > comments into my local maildir as properly threaded replies to the
> > series at hand.
> >
> > No looking at website needed.
> 
> Do you mind sharing that script? It looks like a great work flow. Thanks.

Sure, it is in a 'works-for-me' state and probably should get a once
over before you run it locally to make sure it fits your environment (at
the very least you should probably change my email address :-)

This is cobbled toghether from a script I got from Thomas and staring at
the JSON with generous hints from Gemini (because I can't write Python
at all).

.muttrc has:

macro index S "<pipe-entry>grep -i \"\^Message-ID:\" | cut -d: -f2- | tr -d \" 
<>\" | ~/bin/sashiko.py<enter>"

sashiko.py:

---
#!/usr/bin/env python3

import sys
import mailbox
import requests
from argparse import ArgumentParser
from email.message import EmailMessage
from email.utils import formatdate
from datetime import datetime

def getsash(url, msgid):
    surl = f'{url.rstrip("/")}/api/patch'
    try:
        session = requests.Session()
        session.headers.update({'User-Agent': 'sash2txt_0.2'})
        resp = session.get(surl, params={'id': msgid}, timeout=30)

        if resp.status_code == 404:
            print(f"Error: Patch ID {msgid} not found (404).", file=sys.stderr)
            return

        resp.raise_for_status()
        data = resp.json()
    except requests.RequestException as ex:
        print(f"Request failed: {ex}", file=sys.stderr)
        return

    patches = data.get('patches', [])
    if not patches:
        print(f"No patches found for (msgid).", file=sys.stderr)
        return

    msgids = {}
    subjects = {}

    for p in patches:
        msgids[p['id']] = p['message_id']
        subjects[p['id']] = p['subject']

    reviews = data.get('reviews', [])
    if not reviews:
        print(f"No reviews found for {msgid}.", file=sys.stderr)
        return

    mdir = mailbox.Maildir('~/Maildir/');

    for i, r in enumerate(reviews):
        inline = r.get('inline_review', '') or ''
        if not inline:
            continue

        author_name = r.get('author_name', 'Sashiko Reviewer')
        author_email = r.get('author_email', '[email protected]')

        msgid = msgids[r['patch_id']]
        subject = subjects[r['patch_id']]

        # Create the Email object
        msg = EmailMessage()
        msg['Subject'] = f"Re: {subject}"
        msg['From'] = f"{author_name} <{author_email}>"
        msg['To'] = "[email protected]"
        msg['Date'] = formatdate(localtime=True)

        # The critical threading headers
        msg['Message-ID'] = f"<review-{i}-{msgid}>"
        msg['References'] = f"<{msgid}>"
        msg['In-Reply-To'] = f"<{msgid}>"

        msg.set_content(inline)

        mdir.add(msg)

    mdir.flush()

if __name__ == '__main__':
    url = 'https://sashiko.dev/'

    # Check if there's data in stdin (piped input)
    if not sys.stdin.isatty():
        for line in sys.stdin:
            msgid = line.strip()
            if msgid:
                getsash(url, msgid)
    else:
        # Fallback to arguments if no pipe is detected
        parser = ArgumentParser(description='Sashiko retriever to mbox')
        parser.add_argument('msgid', nargs='?', help='Message id of the patch 
series')
        args = parser.parse_args()

        if args.msgid:
            getsash(url, args.msgid)
        else:
            print("Usage: echo <msgid> | ./sashiko.py OR ./sashiko.py <msgid>", 
file=sys.stderr)

Reply via email to