Fix 'Edit In Place' and 'Fork Session' features#6970
Conversation
…appending This fixes issue block#6954 where the 'Edit In Place' feature was appending edited content as a new message instead of replacing the existing message. Root Causes Identified: 1. Incorrect assumption that the last message after truncation is the user message being edited. In reality, truncation removes the target message and everything after it, so the last message is the one BEFORE it (often an assistant message). 2. The check `if (lastMessage.role === 'user')` on line 758 fails in the common case where the edited message is followed by an assistant response, causing the code to fall through to handleSubmit() which appends. 3. Even when the check passed, the code didn't pass conversation_so_far to the backend, causing the backend to load the old session state and ignore local modifications. The Fix: - Remove incorrect role check that fails in common cases - Create a NEW message with edited content instead of modifying wrong message - Explicitly pass conversation_so_far parameter to backend - Simplify logic: truncated conversation + new message = edited conversation Flow: 1. forkSession truncates conversation at message timestamp 2. getSession returns truncated conversation (messages before edited one) 3. Create new user message with edited content 4. Pass truncated conversation + new message to backend 5. Backend replaces session conversation and appends new message 6. Result: Message is properly edited, not appended ✅ Tested via UI and verified working. Fixes block#6954 Co-Authored-By: Claude Sonnet 4.5 <[email protected]> Signed-off-by: Bright Zheng <[email protected]>
This fixes the Fork Session issues where users experienced lag and missing messages. The problem was that auto-submit fired immediately after the forked session loaded, before users could see the conversation history. Root Cause: When a user clicked "Fork Session", the flow was: 1. Backend creates forked session with conversation history 2. Frontend navigates to new session 3. Session loads with messages 4. Auto-submit IMMEDIATELY fires with edited message 5. User never sees the forked conversation history 6. Creates perception of lag and "messages gone" The Fix: - Remove auto-submit behavior for forked sessions - Allow users to see the full conversation history - Users can manually review and decide when to submit Changes: - Modified useAutoSubmit.ts to detect forked sessions (shouldStartAgent + initialMessage + messages.length > 0) - Mark as handled to prevent fallthrough to resume scenario - Clear initialMessage to prevent memory leaks Benefits: - Users see the forked conversation immediately - No race conditions or timing issues - Clear user intent before submission - Consistent with fork UX expectations Related: Bug block#1 from fork session investigation Co-Authored-By: Claude Sonnet 4.5 <[email protected]> Signed-off-by: Bright Zheng <[email protected]>
…itions' This reverts commit 680bbb2. The original fix removed auto-submit entirely, but users expect the edited message to be automatically submitted in the forked session. Reverting to implement a better solution that keeps auto-submit but fixes the race condition.
This fixes the Fork Session issues where messages appeared to be gone or the session seemed to update the existing one. The root cause was a race condition where auto-submit fired before the forked conversation loaded. Root Causes Fixed: 1. Auto-submit fired immediately, before messages loaded 2. Backend wasn't passed explicit conversation, potentially using stale data 3. User never saw the forked conversation history before it was modified The Fix (Two Parts): Part 1: Wait for Messages to Load (useAutoSubmit.ts) - Check messages.length > 0 before auto-submitting for forked sessions - This ensures the conversation history is loaded and visible - If messages haven't loaded, return and wait for next render - Once loaded, auto-submit fires with the edited message Part 2: Pass conversation_so_far Explicitly (useChatStream.ts) - When handleSubmit is called with existing messages, pass conversation_so_far - This ensures backend uses the correct conversation state - Prevents backend from loading potentially stale session data - Fixes both fork and edit-in-place scenarios Expected Behavior After Fix: 1. User clicks "Fork Session" with edited message 2. Backend creates forked session with conversation history 3. Frontend loads session, displays conversation history 4. Auto-submit waits until messages.length > 0 5. Auto-submit fires with edited message + explicit conversation 6. Backend processes with correct conversation context 7. New response streams back 8. User sees: [history] + [edited message] + [new response] This maintains the expected automatic behavior while fixing the race condition and ensuring data consistency. Fixes both Bug block#1 and Bug block#2 from fork session investigation Co-Authored-By: Claude Sonnet 4.5 <[email protected]> Signed-off-by: Bright Zheng <[email protected]>
Critical bug fix: initialMessage was being passed as a string from the
navigate state, but handleSubmit expects a UserInput object { msg, images }.
This caused "Cannot read properties of undefined (reading 'trim')" error
when auto-submitting forked sessions, preventing the edited message from
being submitted.
Root Cause:
- BaseChat.tsx line 282: passes editedMessage as a string
- useAutoSubmit calls handleSubmit(initialMessage) with string
- handleSubmit destructures: const { msg: userMessage, images } = input
- userMessage is undefined when input is a string
- userMessage.trim() throws TypeError
The Fix:
- Check if initialMessage is a string
- Convert to UserInput format: { msg: initialMessage, images: [] }
- Pass proper object to handleSubmit
This fixes both Scenario 1 (Hub) and Scenario 2 (Fork) auto-submit cases.
Co-Authored-By: Claude Sonnet 4.5 <[email protected]>
Signed-off-by: Bright Zheng <[email protected]>
There was a problem hiding this comment.
Pull request overview
This PR fixes two critical bugs in the desktop chat interface: the "Edit In Place" feature was appending messages instead of replacing them, and the "Fork Session" feature had a race condition and TypeError. The fix refactors the edit logic to properly handle conversation truncation and message replacement, adds explicit conversation state passing to the backend, and fixes type handling and timing issues in auto-submission.
Changes:
- Refactored "Edit In Place" to create a new message and explicitly pass truncated conversation state to backend
- Added conversation_so_far parameter to ensure backend uses correct state for fork/edit operations
- Fixed TypeError by converting string initialMessage to UserInput format
- Fixed race condition by waiting for messages to load before auto-submitting in forked sessions
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.
| File | Description |
|---|---|
| ui/desktop/src/hooks/useChatStream.ts | Added conversation_so_far parameter for existing messages; refactored edit-in-place logic to properly handle session truncation and message creation |
| ui/desktop/src/hooks/useAutoSubmit.ts | Fixed TypeError by converting string to UserInput format; fixed race condition by waiting for messages.length > 0 before auto-submitting |
| if (sessionResponse.data?.conversation) { | ||
| const updatedMessages = [...sessionResponse.data.conversation]; | ||
|
|
||
| if (updatedMessages.length > 0) { | ||
| const lastMessage = updatedMessages[updatedMessages.length - 1]; | ||
| if (lastMessage.role === 'user') { | ||
| lastMessage.content = [{ type: 'text', text: newContent }]; | ||
|
|
||
| for (const content of message.content) { | ||
| if (content.type === 'image') { | ||
| lastMessage.content.push(content); | ||
| } | ||
| } | ||
|
|
||
| dispatch({ type: 'SET_MESSAGES', payload: updatedMessages }); | ||
| dispatch({ type: 'START_STREAMING' }); | ||
| abortControllerRef.current = new AbortController(); | ||
|
|
||
| try { | ||
| const placeholderMessage = createUserMessage(newContent); | ||
|
|
||
| const { stream } = await reply({ | ||
| body: { | ||
| session_id: targetSessionId, | ||
| user_message: placeholderMessage, | ||
| }, | ||
| throwOnError: true, | ||
| signal: abortControllerRef.current.signal, | ||
| }); | ||
|
|
||
| await streamFromResponse( | ||
| stream, | ||
| updatedMessages, | ||
| dispatch, | ||
| onFinish, | ||
| targetSessionId | ||
| ); | ||
| } catch (error) { | ||
| if (error instanceof Error && error.name === 'AbortError') { | ||
| dispatch({ type: 'SET_CHAT_STATE', payload: ChatState.Idle }); | ||
| } else { | ||
| throw error; | ||
| } | ||
| } | ||
| return; | ||
| // The session has been truncated - it contains messages BEFORE the edited message | ||
| const truncatedMessages = [...sessionResponse.data.conversation]; | ||
|
|
||
| // Create the updated user message with new content | ||
| const updatedUserMessage = createUserMessage(newContent); | ||
|
|
||
| // Preserve any existing image content from the original message | ||
| for (const content of message.content) { | ||
| if (content.type === 'image') { | ||
| updatedUserMessage.content.push(content); | ||
| } | ||
| } | ||
|
|
||
| dispatch({ type: 'SET_MESSAGES', payload: sessionResponse.data.conversation }); | ||
| await handleSubmit({ msg: newContent, images: [] }); | ||
| // Update UI to show the edited message appended to truncated conversation | ||
| const messagesForUI = [...truncatedMessages, updatedUserMessage]; | ||
| dispatch({ type: 'SET_MESSAGES', payload: messagesForUI }); |
There was a problem hiding this comment.
After editing a message in place, the session state is not updated with the new session object. The getSession response contains the full Session object (sessionResponse.data), but only the conversation is extracted. This means state.session still has the old session ID, which will break subsequent operations like setRecipeUserParams (line 662), updateFromSession (line 694), and any future handleSubmit calls that use the sessionId prop. Add: dispatch({ type: 'SET_SESSION', payload: sessionResponse.data }) after line 758 to update the session state.
|
Testing locally, may go to monday |
zanesq
left a comment
There was a problem hiding this comment.
LGTM! Tested locally and pushed some minor cleanup
* main: (125 commits) chore: add a new scenario (#7107) fix: Goose Desktop missing Calendar and Reminders entitlements (#7100) Fix 'Edit In Place' and 'Fork Session' features (#6970) Fix: Only send command content to command injection classifier (excluding part of tool call dict) (#7082) Docs: require auth optional for custom providers (#7098) fix: improve text-muted contrast for better readability (#7095) Always sync bundled extensions (#7057) feat: Add tom (Top Of Mind) platform extension (#7073) chore(docs): update GOOSE_SESSION_ID -> AGENT_SESSION_ID (#6669) fix(ci): switch from cargo-audit to cargo-deny for advisory scanning (#7032) chore(deps): bump @isaacs/brace-expansion from 5.0.0 to 5.0.1 in /evals/open-model-gym/suite (#7085) chore(deps): bump @modelcontextprotocol/sdk from 1.25.3 to 1.26.0 in /evals/open-model-gym/mcp-harness (#7086) fix: switch to windows msvc (#7080) fix: allow unlisted models for CLI providers (#7090) Use goose port (#7089) chore: strip posthog for sessions/models/daily only (#7079) tidy: clean up old benchmark and add gym (#7081) fix: use command.process_group(0) for CLI providers, not just MCP (#7083) added build notify (#6891) test(mcp): add image tool test and consolidate MCP test fixtures (#7019) ...
…tensions-deeplinks * 'main' of github.com:block/goose: [docs] update authors.yaml file (#7114) Implement manpage generation for goose-cli (#6980) docs: tool output optimization (#7109) Fix duplicated output in Code Mode by filtering content by audience (#7117) Enable tom (Top Of Mind) platform extension by default (#7111) chore: added notification for canary build failure (#7106) fix: fix windows bundle random failure and optimise canary build (#7105) feat(acp): add model selection support for session/new and session/set_model (#7112) fix: isolate claude-code sessions via stream-json session_id (#7108) ci: enable agentic provider live tests (claude-code, codex, gemini-cli) (#7088) docs: codex subscription support (#7104) chore: add a new scenario (#7107) fix: Goose Desktop missing Calendar and Reminders entitlements (#7100) Fix 'Edit In Place' and 'Fork Session' features (#6970) Fix: Only send command content to command injection classifier (excluding part of tool call dict) (#7082) Docs: require auth optional for custom providers (#7098) fix: improve text-muted contrast for better readability (#7095) Always sync bundled extensions (#7057)
* origin/main: feat: add AGENT=goose environment variable for cross-tool compatibility (#7017) fix: strip empty extensions array when deeplink also (#7096) [docs] update authors.yaml file (#7114) Implement manpage generation for goose-cli (#6980) docs: tool output optimization (#7109) Fix duplicated output in Code Mode by filtering content by audience (#7117) Enable tom (Top Of Mind) platform extension by default (#7111) chore: added notification for canary build failure (#7106) fix: fix windows bundle random failure and optimise canary build (#7105) feat(acp): add model selection support for session/new and session/set_model (#7112) fix: isolate claude-code sessions via stream-json session_id (#7108) ci: enable agentic provider live tests (claude-code, codex, gemini-cli) (#7088) docs: codex subscription support (#7104) chore: add a new scenario (#7107) fix: Goose Desktop missing Calendar and Reminders entitlements (#7100) Fix 'Edit In Place' and 'Fork Session' features (#6970) Fix: Only send command content to command injection classifier (excluding part of tool call dict) (#7082) # Conflicts: # crates/goose/src/agents/extension.rs
* origin/main: (30 commits) docs: GCP Vertex AI org policy filtering & update OnboardingProviderSetup component (#7125) feat: replace subagent and skills with unified summon extension (#6964) feat: add AGENT=goose environment variable for cross-tool compatibility (#7017) fix: strip empty extensions array when deeplink also (#7096) [docs] update authors.yaml file (#7114) Implement manpage generation for goose-cli (#6980) docs: tool output optimization (#7109) Fix duplicated output in Code Mode by filtering content by audience (#7117) Enable tom (Top Of Mind) platform extension by default (#7111) chore: added notification for canary build failure (#7106) fix: fix windows bundle random failure and optimise canary build (#7105) feat(acp): add model selection support for session/new and session/set_model (#7112) fix: isolate claude-code sessions via stream-json session_id (#7108) ci: enable agentic provider live tests (claude-code, codex, gemini-cli) (#7088) docs: codex subscription support (#7104) chore: add a new scenario (#7107) fix: Goose Desktop missing Calendar and Reminders entitlements (#7100) Fix 'Edit In Place' and 'Fork Session' features (#6970) Fix: Only send command content to command injection classifier (excluding part of tool call dict) (#7082) Docs: require auth optional for custom providers (#7098) ...
* main: (85 commits) Fix 'Edit In Place' and 'Fork Session' features (#6970) Fix: Only send command content to command injection classifier (excluding part of tool call dict) (#7082) Docs: require auth optional for custom providers (#7098) fix: improve text-muted contrast for better readability (#7095) Always sync bundled extensions (#7057) feat: Add tom (Top Of Mind) platform extension (#7073) chore(docs): update GOOSE_SESSION_ID -> AGENT_SESSION_ID (#6669) fix(ci): switch from cargo-audit to cargo-deny for advisory scanning (#7032) chore(deps): bump @isaacs/brace-expansion from 5.0.0 to 5.0.1 in /evals/open-model-gym/suite (#7085) chore(deps): bump @modelcontextprotocol/sdk from 1.25.3 to 1.26.0 in /evals/open-model-gym/mcp-harness (#7086) fix: switch to windows msvc (#7080) fix: allow unlisted models for CLI providers (#7090) Use goose port (#7089) chore: strip posthog for sessions/models/daily only (#7079) tidy: clean up old benchmark and add gym (#7081) fix: use command.process_group(0) for CLI providers, not just MCP (#7083) added build notify (#6891) test(mcp): add image tool test and consolidate MCP test fixtures (#7019) fix: remove Option from model listing return types, propagate errors (#7074) fix: lazy provider creation for goose acp (#7026) (#7066) ...
Signed-off-by: Bright Zheng <[email protected]> Co-authored-by: Claude Sonnet 4.5 <[email protected]> Co-authored-by: Zane Staggs <[email protected]>
Signed-off-by: Bright Zheng <[email protected]> Co-authored-by: Claude Sonnet 4.5 <[email protected]> Co-authored-by: Zane Staggs <[email protected]>
Summary
Fixes #6954 - "Edit In Place" and "Fork Session" features were broken. Both now work correctly.
✅ Verified with local UI testing
The Bugs
1. Edit In Place - Duplicated Instead of Replacing
Issue: Edited messages were appended as duplicates instead of replacing the original.
Cause: Code tried to modify the wrong message (the one before the edit target) due to incorrect assumption about truncation behavior.
Fix: Create new message with edited content + pass
conversation_so_farexplicitly to backend.2. Fork Session - Race Condition & Empty History
Issue: Forked sessions showed no history, appeared laggy, or threw
TypeError: Cannot read property 'trim'.Causes:
initialMessagewas a string, notUserInputobjectFix:
messages.length > 0before auto-submitting{msg, images}formatconversation_so_farto ensure correct stateChanges
src/hooks/useChatStream.ts- Edit In Place logic + explicit conversation passingsrc/hooks/useAutoSubmit.ts- Fork timing + type safetyVerification
Both features tested and working:
🤖 Generated with Claude Code