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)| {