From 3464ee03e5dede5d595353f9eb3425f37e2d1a20 Mon Sep 17 00:00:00 2001 From: "Claude Fable 5.1 (claude-fable-5-1)" Date: Mon, 28 Sep 2026 03:24:49 +0200 Subject: [PATCH] fix(sign): one alerter banner at a time, ended after its timeout The sign notifier spawned alerter with a 600 s timeout and never reaped it; when the banner never showed, the process stayed for days, one per sign run. The shell wrapper now ends a previous alerter of the rune-sign group before starting, and a watchdog ends the new one 620 s in. Co-Authored-By: Martin Zeman Claude-Session: https://claude.ai/code/session_01Fds3tf5rS1Ep47q5DGhsr3 --- CHANGELOG.md | 1 + src/cli/sign/queue/mod.rs | 7 +++++-- src/cli/sign/queue/tests.rs | 4 ++++ 3 files changed, 10 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 54ba7aa..2e055c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -209,6 +209,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/). ### Fixed +- Keep one `alerter` banner per sign run: the previous banner of the group is ended before a new one starts, and a watchdog ends a banner that outlives its timeout. - Fix `rune run --clean` for codex: the clean `config.toml` keeps every `[model_providers.*]` table with its `auth` and names the one custom provider as the route. - Fix `rune run grok@grok --clean-harness-state` exiting 127 through the `harness-run` shim: the clean run names the real `~/.grok/bin` in `HARNESS_REAL_BIN_DIR` before it moves `HOME`. - Fix the identity tests to expect the versioned display names the synced `author-identity.py` derives (`Claude Fable 5.2`, not `Claude`). diff --git a/src/cli/sign/queue/mod.rs b/src/cli/sign/queue/mod.rs index db442ae..1450540 100644 --- a/src/cli/sign/queue/mod.rs +++ b/src/cli/sign/queue/mod.rs @@ -1264,11 +1264,14 @@ fn notifier_commands(message: &str, icon: Option<&Path>, activate: Option<&str>) // The banner is alerter's own, with the runedeck mark as its icon; // impersonating the terminal through `--sender` needs that app's // notification permission, which a terminal never asked for, and - // the banner then never shows. + // the banner then never shows. One banner at a time: a previous + // alerter of the group is ended first, and a watchdog ends this one + // a little after its own timeout, because alerter has stayed alive + // for days when the notification never showed. let mut alerter = Command::new("/bin/sh"); alerter.args([ "-c", - r#"out=$("$0" "$@"); case "$out" in @CONTENTCLICKED|@ACTIONCLICKED) [ -n "${RUNE_NOTIFY_OPEN:-}" ] && exec open -b "$RUNE_NOTIFY_OPEN" ;; esac"#, + r#"pkill -f "^alerter .*--group rune-sign" 2>/dev/null; ( sleep 620; pkill -f "^alerter .*--group rune-sign" ) >/dev/null 2>&1 & wd=$!; out=$("$0" "$@"); pkill -P "$wd" 2>/dev/null; kill "$wd" 2>/dev/null; case "$out" in @CONTENTCLICKED|@ACTIONCLICKED) [ -n "${RUNE_NOTIFY_OPEN:-}" ] && exec open -b "$RUNE_NOTIFY_OPEN" ;; esac"#, "alerter", "--title", NOTIFICATION_TITLE, diff --git a/src/cli/sign/queue/tests.rs b/src/cli/sign/queue/tests.rs index e85901a..9f21788 100644 --- a/src/cli/sign/queue/tests.rs +++ b/src/cli/sign/queue/tests.rs @@ -408,6 +408,10 @@ fn a_click_activates_the_terminal_the_owner_typed_in() { "no impersonation" ); assert!(args[1][1].contains("@CONTENTCLICKED") && args[1][1].contains("open -b")); + // One banner at a time: the previous group member is ended first + // and a watchdog ends this one after its timeout. + assert!(args[1][1].starts_with("pkill -f \"^alerter .*--group rune-sign\"")); + assert!(args[1][1].contains("sleep 620")); let env: Vec<(String, String)> = commands[1] .get_envs() .filter_map(|(key, value)| {