Pi: /new (session start rebind) fires a false 'finished' toast + Done sound via Unknown→Idle completion transition
Environment
- Herdr 0.9.0 (Homebrew, macOS 26.6.2), server + client both 0.9.0
- Pi integration: current,
HERDR_INTEGRATION_VERSION=8 [ui.toast] delivery = "system",[ui.sound]at defaults (enabled = true)
What happens
Starting a new Pi session — typing /new in an existing pi pane — immediately produces a false pi finished toast and the Done sound, even though no work finished. The same happens on other session-start paths that rebind the session (/resume, /fork). The user-visible effect: "every time I start a new session I instantly get a notification", on top of the legitimate per-turn finished notifications.
The toast is subject to the normal active-tab suppression, but the transition still fires whenever the pane counts as background (background tab, another client's active view, or the Herdr window unfocused — per the unfocused-window allowance), so in practice the false notification shows up most of the time.
Reproduce
- Run pi in a Herdr pane (integration v8). Confirm the pane reports idle.
- Move focus away from that tab (or unfocus the Herdr window).
- Type
/newin the pi pane. - Within ~
delay_seconds, api finishedtoast + Done sound fires, without any turn having run in the new session.
Analysis (master, herdrdev/herdr)
The pi extension's
session_starthandler first reports the new session, then force-publishes the current state:src/integration/assets/pi/herdr-agent-state.tsaround lines 229–241:await reportSession(event?.reason)(which reaches the server aspane.report_agent_sessionwithsession_start_source: "new"), immediately followed bypublishState(true)— a forced state report,idleat a fresh prompt.
The server accepts the rebind for pi:
session_report_allows_session_replacementmatches("herdr:pi", "pi", Some("new" | "resume" | "fork"))(src/terminal/state.rs~1315–1333). In thereplaced_hook_sessionbranch it clears the pane's hook authority (self.hook_authority = None;,src/terminal/state.rs~1586–1593), sorecompute_effective_statefalls back tofallback_state— i.e. the effective state becomesUnknown(src/terminal/state.rs~2149–2189).The extension's forced
idlereport from step 1 then re-establishes hook authority atIdle, producing an effectiveUnknown → Idlechange.is_completion_transition_partsdeliberately treats that as a completion when the agent label is unchanged:src/app/actions.rs~44–55:previous_state == Unknown && state == Idle && previous_agent_label.is_some() && previous_agent_label == agent_labelwhich then yields the Finished toast / Done sound vianotification_toast_for_state_change_with_agent_labels(src/app/actions.rs~132–152).
So the "session just (re)started, agent sitting at the prompt" state is indistinguishable from "a turn just completed" once the rebind has reset the hook state to Unknown.
Expected
A session start (/new, /resume, /fork) should not be a completion transition. Only Working → Idle (or Blocked → Idle) should notify as finished.
Possible directions
- Server-side: after a same-owner session replacement, suppress the completion notification for the first state report that re-binds authority (or mark the rebind so the following idle is not a completion). The rebind already knows
session_start_source; the forced state report that follows it is the one to exempt. - Extension-side:
publishState(true)right afterreportSessioncould be delayed/omitted, but a server-side guard seems more robust since the same Unknown→Idle path can be hit by other rebind flows.
Related: #1231 and #1377 cover the rebind handling on the stuck-status side (fixed via #1189); #49 is the other false-completion family (subagents). This report is specifically about the rebind-driven Unknown→Idle completion on session starts.
Happy to test a preview build or add traces if useful.
Source: herdrdev/herdr