@@ -82,37 +82,15 @@ export const POST = withRouteHandler(
8282 const title = `Fork | ${ baseTitle } `
8383 const now = new Date ( )
8484
85- const [ newChat ] = await db
86- . insert ( copilotChats )
87- . values ( {
88- id : newId ,
89- userId,
90- workspaceId : parent . workspaceId ,
91- workflowId : parent . workflowId ,
92- type : parent . type ,
93- title,
94- model : parent . model ,
95- messages : forkedMessages ,
96- resources : parentResources ,
97- previewYaml : parent . previewYaml ,
98- planArtifact : parent . planArtifact ,
99- config : parent . config ,
100- conversationId : null ,
101- updatedAt : now ,
102- lastSeenAt : now ,
103- } )
104- . returning ( { id : copilotChats . id , workspaceId : copilotChats . workspaceId } )
105-
106- if ( ! newChat ) {
107- return createInternalServerErrorResponse ( 'Failed to create forked chat' )
108- }
109-
110- // Clone copilot-service conversation state (messages, active_messages, memory files).
85+ // Clone copilot-service conversation state first. If this fails we never
86+ // insert the Sim row, so there is no orphaned UI entry to clean up.
87+ // (The inverse order — Sim INSERT first — required a compensating delete
88+ // and still left a brief window where the row was visible but Go state
89+ // wasn't ready.)
11190 const copilotHeaders : Record < string , string > = { 'Content-Type' : 'application/json' }
11291 if ( env . COPILOT_API_KEY ) {
11392 copilotHeaders [ 'x-api-key' ] = env . COPILOT_API_KEY
11493 }
115- let copilotFailed = false
11694 try {
11795 const copilotRes = await fetchGo ( `${ SIM_AGENT_API_URL } /api/chats/fork` , {
11896 method : 'POST' ,
@@ -129,24 +107,43 @@ export const POST = withRouteHandler(
129107 if ( ! copilotRes . ok ) {
130108 const text = await copilotRes . text ( ) . catch ( ( ) => '' )
131109 logger . error ( 'Copilot fork returned non-OK' , { status : copilotRes . status , body : text } )
132- copilotFailed = true
110+ return createInternalServerErrorResponse ( 'Failed to fork chat' )
133111 }
134112 } catch ( err ) {
135113 logger . error ( 'Failed to call copilot fork endpoint' , { err } )
136- copilotFailed = true
114+ return createInternalServerErrorResponse ( 'Failed to fork chat' )
137115 }
138116
139- if ( copilotFailed ) {
140- // Compensating delete — remove the orphaned Sim row.
141- await db
142- . delete ( copilotChats )
143- . where ( eq ( copilotChats . id , newId ) )
144- . catch ( ( e : unknown ) => {
145- logger . error ( 'Failed to delete orphaned forked chat after copilot failure' , {
146- error : e ,
147- } )
148- } )
149- return createInternalServerErrorResponse ( 'Failed to fork chat' )
117+ // Go state is ready — now persist the Sim metadata row. If this insert
118+ // fails the Go conversation is orphaned but permanently inaccessible
119+ // (no Sim row = no UI entry), which is harmless.
120+ const [ newChat ] = await db
121+ . insert ( copilotChats )
122+ . values ( {
123+ id : newId ,
124+ userId,
125+ workspaceId : parent . workspaceId ,
126+ workflowId : parent . workflowId ,
127+ type : parent . type ,
128+ title,
129+ model : parent . model ,
130+ messages : forkedMessages ,
131+ resources : parentResources ,
132+ previewYaml : parent . previewYaml ,
133+ planArtifact : parent . planArtifact ,
134+ config : parent . config ,
135+ conversationId : null ,
136+ updatedAt : now ,
137+ lastSeenAt : now ,
138+ } )
139+ . returning ( { id : copilotChats . id , workspaceId : copilotChats . workspaceId } )
140+
141+ if ( ! newChat ) {
142+ logger . error ( 'Failed to insert forked chat row after successful Go fork' , {
143+ newId,
144+ chatId,
145+ } )
146+ return createInternalServerErrorResponse ( 'Failed to create forked chat' )
150147 }
151148
152149 if ( newChat . workspaceId ) {
0 commit comments