-
Notifications
You must be signed in to change notification settings - Fork 2.5k
Fix: Update session name in UI in real-time #6533
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Fix: Update session name in UI in real-time #6533
Conversation
The backend auto-generates session names after each of the first 3 user messages to refine the name as more context becomes available. However, the UI was not updating to show these names until the session was closed and reopened. This fix adds two changes: 1. In useChatStream.ts: Refresh session name after each reply for the first 3 user messages by counting user messages and fetching updated session data. Stops after 3rd message to avoid unnecessary API calls. 2. In BaseChat.tsx: Propagate session name changes to the global ChatContext so the window title updates immediately via AppSidebar. The UI now matches the backend behavior perfectly: - After 1st message: Name appears (e.g., "Query performance") - After 2nd message: Name refines (e.g., "PostgreSQL performance") - After 3rd message: Name finalizes (e.g., "PostgreSQL missing index") - After 4th+ messages: No more updates (stops permanently) Performance impact: Exactly 3 API calls per session (~1.5KB, <15ms total), then zero overhead. Orders of magnitude cheaper than existing operations.
…enders Changed from updating entire chat object to only updating the name field using the updater function pattern. This prevents the effect from firing on every message change. Benefits: - Effect only fires when session.name actually changes (3x per session) - Avoids overwriting messages/recipe that may be updated elsewhere - Reduces unnecessary re-renders of ChatContext consumers - Prevents potential stale data bugs from closure captures
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR fixes a UX issue where session names generated by the backend weren't reflected in the UI until users closed and reopened sessions. The solution adds real-time session name updates by fetching the session name after each of the first three user messages, matching the backend's name refinement behavior.
Changes:
- Added session name refresh logic that triggers after replies complete for the first 3 user messages
- Implemented useEffect hook to propagate session name changes to the global ChatContext for window title updates
- Added user message counting to limit API calls to exactly 3 per session
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| ui/desktop/src/hooks/useChatStream.ts | Adds session name refresh logic in onFinish callback with user message counting |
| ui/desktop/src/components/BaseChat.tsx | Propagates session name updates to global ChatContext via useEffect |
The setChat prop type signature doesn't support updater functions, so we need to explicitly set all fields. This approach works correctly and passes TypeScript validation.
Changed from session?.name to session to satisfy react-hooks/exhaustive-deps rule, since we access session.name inside the effect.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
Updated comment from 'when session name changes' to 'keep it in sync with local state' to accurately reflect that the effect runs whenever any dependency changes (session, messages, recipe, sessionId, setChat), not just when the session name changes.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
The useEffect was firing on every message because messages/recipe/sessionId were in the dependency array. This caused performance issues and made the UI sluggish with the dev console open. Solution: Use refs to store current values of messages, recipe, and sessionId. The main useEffect only depends on session?.name and setChat, so it only fires when the session name actually changes (3 times per session). This eliminates the infinite loop and excessive re-renders while still keeping the global chat context in sync with the session name.
44b444e to
ceafe06
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
- Extract session?.name into currentSessionName at start of useEffect - Use currentSessionName consistently in condition and setChat call - Satisfies react-hooks/exhaustive-deps by only accessing session?.name - Maintains same logic while keeping dependencies clean
This file was for local reference only and shouldn't be in the PR
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
- Move ref updates from useEffect (with no deps) to render body - More efficient: no extra effect running after every render - Refs are updated synchronously during render - Aligns with React best practices for ref management - Addresses Copilot feedback about unnecessary useEffect
d8a4682 to
1b56b88
Compare
This file should remain in the repository as it exists in main
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| const messagesRef = useRef(messages); | ||
| const recipeRef = useRef(recipe); | ||
| const sessionIdRef = useRef(sessionId); | ||
|
|
||
| messagesRef.current = messages; | ||
| recipeRef.current = recipe; | ||
| sessionIdRef.current = sessionId; | ||
|
|
||
| useEffect(() => { | ||
| const currentSessionName = session?.name; | ||
| if (currentSessionName && currentSessionName !== lastSetNameRef.current) { | ||
| lastSetNameRef.current = currentSessionName; | ||
| setChat({ | ||
| messages: messagesRef.current, | ||
| recipe: recipeRef.current, | ||
| sessionId: sessionIdRef.current, | ||
| name: currentSessionName, | ||
| }); |
Copilot
AI
Jan 16, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The refs pattern here creates unnecessary complexity and could lead to confusion. Since this effect only triggers on session name changes, you're propagating potentially stale values for messages, recipe, and sessionId to the global chat context.
Consider simplifying to only update the name field, or include all dependencies in the effect's dependency array. If you need to update the entire chat object, you should either:
- Add messages, recipe, and sessionId to the dependency array
- Or use a functional update:
setChat(prev => ({ ...prev, name: currentSessionName }))
The functional update approach would be safer as it preserves any other concurrent updates to the chat state.
| const messagesRef = useRef(messages); | |
| const recipeRef = useRef(recipe); | |
| const sessionIdRef = useRef(sessionId); | |
| messagesRef.current = messages; | |
| recipeRef.current = recipe; | |
| sessionIdRef.current = sessionId; | |
| useEffect(() => { | |
| const currentSessionName = session?.name; | |
| if (currentSessionName && currentSessionName !== lastSetNameRef.current) { | |
| lastSetNameRef.current = currentSessionName; | |
| setChat({ | |
| messages: messagesRef.current, | |
| recipe: recipeRef.current, | |
| sessionId: sessionIdRef.current, | |
| name: currentSessionName, | |
| }); | |
| useEffect(() => { | |
| const currentSessionName = session?.name; | |
| if (currentSessionName && currentSessionName !== lastSetNameRef.current) { | |
| lastSetNameRef.current = currentSessionName; | |
| setChat((prevChat) => | |
| prevChat | |
| ? { ...prevChat, name: currentSessionName } | |
| : { name: currentSessionName }, | |
| ); |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Leaving this one open in case individuals prefer how we did this before Copilot gave its opinion. This also worked just fine.
const messagesRef = useRef(messages);
const recipeRef = useRef(recipe);
const sessionIdRef = useRef(sessionId);
messagesRef.current = messages;
recipeRef.current = recipe;
sessionIdRef.current = sessionId;
useEffect(() => {
const currentSessionName = session?.name;
if (currentSessionName && currentSessionName !== lastSetNameRef.current) {
lastSetNameRef.current = currentSessionName;
setChat({
messages: messagesRef.current,
recipe: recipeRef.current,
sessionId: sessionIdRef.current,
name: currentSessionName,
});
- Use messages, recipe, sessionId directly from closure - Effect still only runs when session?.name changes - Simpler and clearer code - Add eslint-disable for intentional dependency omission - Addresses Copilot feedback about unnecessary complexity
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated no new comments.
- Remove 'session' from onFinish dependency array - Check only sessionId instead of session && sessionId - Remove name comparison (always update if name exists) - Makes callback more stable (doesn't recreate on every session change) - Addresses Copilot feedback about unnecessary callback recreation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
|
As mentioned by @arielahdoot on Slack, this fixes a quirk around session names not updating in real-time, which has made switching between goose windows a lot more confusing in their opinion. Tagging for review @block/goose-core-maintainers @block/goose-maintainers |
|
Nice, @arielahdoot! This has been bugging me for some time now... thanks for solving a great, little detail like this. |
Signed-off-by: fbalicchia <fbalicchia@cuebiq.com>
Fix: Update session name in UI in real-time
Problem
Currently, Goose's backend automatically generates descriptive session names (e.g., "PostgreSQL missing index fix" instead of "New session 1") after the first few messages. However, the UI doesn't update to show these names until you close and reopen the session.
This defeats the purpose of having auto-generated names, especially when working with multiple sessions simultaneously. Users see generic "New session 1" titles in the window/dock, making it impossible to distinguish between sessions.
Current Behavior (Before Fix)
This is especially problematic because:
Solution
This PR makes the UI update the session name in real-time by:
ui/desktop/src/hooks/useChatStream.ts: After each reply completes, fetch the latest session data from the backend (but only for the first 3 user messages, matching when the backend generates/refines names)ui/desktop/src/components/BaseChat.tsx: Propagate the updated session name to the globalChatContextso the window title reflects the current nameHow It Works
The backend's auto-naming logic (
maybe_update_name()insession_manager.rs) generates and refines the session name after each of the first 3 user messages:The UI now matches this behavior by refreshing the session name after each of the first 3 replies, then stopping. This ensures:
New Behavior (After Fix)
Before (Initial state):
After 1st message:
After 2nd message:
After 3rd message:
After 4th message:
Summary of behavior:
Technical Notes
ESLint Disable Comment
You'll notice an
eslint-disable-next-line react-hooks/exhaustive-depscomment inBaseChat.tsx. This is intentional and necessary. Here's why:The
useEffectthat updates the global chat context usesmessages,recipe, andsessionIdfrom the component scope, but only includessession?.nameandsetChatin its dependency array:Why we can't add them to the dependency array:
messagesto the deps would cause the effect to run on every message (infinite loop during testing showed 114 triggers)Alternatives we tried:
useRefto store values: This worked but added unnecessary complexity (Copilot feedback: "creates unnecessary complexity")setChat(prev => ({...prev, name})): TypeScript error - thesetChatprop type doesn't support updater functionsWhy this approach is correct:
session?.namechangesmessages,recipe,sessionId) are always current at the time the effect runsWe're open to suggestions from Goose maintainers if there's a better pattern that satisfies both ESLint and avoids the infinite loop!
Testing
Manual Testing Steps
Expected Behavior
Impact