@@ -698,6 +698,11 @@ extension AppState {
698698 let markUnread = !isFg && !resultEvent. isError
699699
700700 let stopProject = projects. first ( where: { $0. id == projectId } )
701+ // Capture queued-followup state now, synchronously, before
702+ // `finalizeStreamSession` schedules the auto-flush that pops
703+ // the next queued message. Stop hooks (review/commit) use this
704+ // to defer until the queue has fully drained.
705+ let hasQueuedFollowups = !threadStore. loadQueue ( sessionKey: sessionKey) . isEmpty
701706
702707 finalizeStreamSession ( for: sessionKey) { state in
703708 if let cost = resultEvent. totalCostUsd { state. costUsd = cost }
@@ -726,7 +731,8 @@ extension AppState {
726731 sessionId: resultEvent. sessionId,
727732 reason: . completed,
728733 turnDidError: resultEvent. isError,
729- lastAssistantText: lastAssistantResponseText ( in: stateForSession ( sessionKey) . messages)
734+ lastAssistantText: lastAssistantResponseText ( in: stateForSession ( sessionKey) . messages) ,
735+ hasQueuedFollowups: hasQueuedFollowups
730736 ) )
731737 if stopResult. hasError {
732738 stopHookFailureOutput = stopResult. combinedOutput
@@ -762,6 +768,18 @@ extension AppState {
762768 reconcileFromDisk ( sessionId: resultEvent. sessionId, projectId: projectId, cwd: cwd)
763769 }
764770
771+ // Whether this turn was the synthetic commit/push follow-up the
772+ // Commit & Push hook injected. Captured BEFORE the after-stop
773+ // dispatch below, which is where CommitPushHook consumes the
774+ // marker. A hook-injected turn's last "user" message is the
775+ // commit prompt, not the user's words — summarizing or
776+ // extracting memories from it pollutes the briefing/memories
777+ // with "Commit the changes from this session…" boilerplate.
778+ let wasHookInjectedTurn = isSetupSession (
779+ kind: HookSetupKind . commitPush,
780+ sessionKey: sessionKey
781+ )
782+
765783 // After-session-stop hooks: shown only, not re-saved. This
766784 // dispatch also drives the response-complete notification
767785 // (ResponseNotificationHook), which self-suppresses unless
@@ -773,7 +791,8 @@ extension AppState {
773791 sessionId: resultEvent. sessionId,
774792 reason: . completed,
775793 turnDidError: resultEvent. isError,
776- lastAssistantText: lastAssistantResponseText ( in: stateForSession ( sessionKey) . messages)
794+ lastAssistantText: lastAssistantResponseText ( in: stateForSession ( sessionKey) . messages) ,
795+ hasQueuedFollowups: hasQueuedFollowups
777796 ) )
778797 }
779798
@@ -801,17 +820,23 @@ extension AppState {
801820 // ResponseNotificationHook via the after-session-end
802821 // dispatch above.
803822
804- scheduleThreadSummaryUpdate (
805- sessionId: resultEvent. sessionId,
806- projectId: projectId,
807- cwd: cwd,
808- messages: stateForSession ( sessionKey) . messages
809- )
810- scheduleMemoryExtraction (
811- sessionId: resultEvent. sessionId,
812- projectId: projectId,
813- messages: stateForSession ( sessionKey) . messages
814- )
823+ // Skip summary/memory updates for the hook-injected
824+ // commit & push turn — its last user message is the
825+ // commit prompt, not the user's, and would otherwise
826+ // leak into the thread summary and extracted memories.
827+ if !wasHookInjectedTurn {
828+ scheduleThreadSummaryUpdate (
829+ sessionId: resultEvent. sessionId,
830+ projectId: projectId,
831+ cwd: cwd,
832+ messages: stateForSession ( sessionKey) . messages
833+ )
834+ scheduleMemoryExtraction (
835+ sessionId: resultEvent. sessionId,
836+ projectId: projectId,
837+ messages: stateForSession ( sessionKey) . messages
838+ )
839+ }
815840
816841 // If this session is running in the background, automatically process any queued messages.
817842 // Foreground sessions are handled by InputBarView via isStreaming onChange.
0 commit comments