Skip to content

Fix 'Edit In Place' and 'Fork Session' features#6970

Merged
zanesq merged 7 commits intoblock:mainfrom
bzqzheng:fix/6954-edit-in-place-correct-fix
Feb 9, 2026
Merged

Fix 'Edit In Place' and 'Fork Session' features#6970
zanesq merged 7 commits intoblock:mainfrom
bzqzheng:fix/6954-edit-in-place-correct-fix

Conversation

@bzqzheng
Copy link
Copy Markdown
Contributor

@bzqzheng bzqzheng commented Feb 4, 2026

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_far explicitly 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:

  • Auto-submit fired before messages loaded (race condition)
  • initialMessage was a string, not UserInput object
  • Backend didn't receive explicit conversation state

Fix:

  • Wait for messages.length > 0 before auto-submitting
  • Convert string to {msg, images} format
  • Pass conversation_so_far to ensure correct state

Changes

  • src/hooks/useChatStream.ts - Edit In Place logic + explicit conversation passing
  • src/hooks/useAutoSubmit.ts - Fork timing + type safety

Verification

Both features tested and working:

  • ✅ Edit In Place: Message replaces correctly, no duplication
  • ✅ Fork Session: History loads → edited message auto-submits → new response

🤖 Generated with Claude Code

bzqzheng and others added 6 commits February 4, 2026 17:54
…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]>
Copilot AI review requested due to automatic review settings February 4, 2026 23:52
Copy link
Copy Markdown
Contributor

Copilot AI left a 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 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

Comment on lines 756 to +772
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 });
Copy link

Copilot AI Feb 4, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Copilot uses AI. Check for mistakes.
@zanesq zanesq self-assigned this Feb 5, 2026
@zanesq
Copy link
Copy Markdown
Contributor

zanesq commented Feb 6, 2026

Testing locally, may go to monday

Copy link
Copy Markdown
Contributor

@zanesq zanesq left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM! Tested locally and pushed some minor cleanup

@zanesq zanesq added this pull request to the merge queue Feb 9, 2026
Merged via the queue into block:main with commit 17dff13 Feb 9, 2026
19 checks passed
michaelneale added a commit that referenced this pull request Feb 10, 2026
* 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)
  ...
zanesq added a commit that referenced this pull request Feb 10, 2026
…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)
tlongwell-block added a commit that referenced this pull request Feb 10, 2026
* 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
jh-block added a commit that referenced this pull request Feb 10, 2026
* 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)
  ...
lifeizhou-ap added a commit that referenced this pull request Feb 11, 2026
* 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)
  ...
Tyler-Hardin pushed a commit to Tyler-Hardin/goose that referenced this pull request Feb 11, 2026
Signed-off-by: Bright Zheng <[email protected]>
Co-authored-by: Claude Sonnet 4.5 <[email protected]>
Co-authored-by: Zane Staggs <[email protected]>
Tyler-Hardin pushed a commit to Tyler-Hardin/goose that referenced this pull request Feb 11, 2026
Signed-off-by: Bright Zheng <[email protected]>
Co-authored-by: Claude Sonnet 4.5 <[email protected]>
Co-authored-by: Zane Staggs <[email protected]>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

"Edit In Place" feature only appends instead of edits

3 participants