fix: close race conditions in script builder session and slug creation
- script_builder endpoint: pg_advisory_xact_lock on user_id before session count check, preventing concurrent creates from both passing the MAX_SESSIONS_PER_USER guard - script_builder_service send_message: pg_advisory_xact_lock on session_id before message count check, preventing concurrent sends from both passing the MAX_MESSAGES_PER_SESSION guard - script_builder_service save_to_library: replace check-then-insert slug logic with IntegrityError retry loop (3 attempts with fresh UUID suffix); add unique constraint on script_templates.slug (migration 070) - ScriptBuilderPage: add creatingSessionRef to serialize concurrent handleSend calls that would otherwise both call createSession() while session is still null Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { useState, useEffect } from 'react'
|
||||
import { useState, useEffect, useRef } from 'react'
|
||||
import { useSearchParams } from 'react-router-dom'
|
||||
import { Terminal } from 'lucide-react'
|
||||
import { cn } from '@/lib/utils'
|
||||
@@ -21,6 +21,11 @@ export default function ScriptBuilderPage() {
|
||||
const [messages, setMessages] = useState<ScriptBuilderMessage[]>([])
|
||||
const [language, setLanguage] = useState('powershell')
|
||||
const [isLoading, setIsLoading] = useState(false)
|
||||
// Ref-based lock: prevents two concurrent handleSend calls (e.g. FlowPilot
|
||||
// handoff useEffect + user keystroke) from each calling createSession() and
|
||||
// creating two orphaned sessions. React state updates are async so isLoading
|
||||
// alone can't guard across two calls in the same render cycle.
|
||||
const creatingSessionRef = useRef(false)
|
||||
const [previewScript, setPreviewScript] = useState<{ script: string; filename: string | null } | null>(null)
|
||||
const [showSaveDialog, setShowSaveDialog] = useState(false)
|
||||
const [handoffProcessed, setHandoffProcessed] = useState(false)
|
||||
@@ -75,8 +80,19 @@ export default function ScriptBuilderPage() {
|
||||
// Create session if needed
|
||||
let currentSession = session
|
||||
if (!currentSession) {
|
||||
currentSession = await scriptBuilderApi.createSession(effectiveLanguage)
|
||||
setSession(currentSession)
|
||||
if (creatingSessionRef.current) {
|
||||
// Another concurrent call is already creating the session; drop this send.
|
||||
setIsLoading(false)
|
||||
setMessages((prev) => prev.slice(0, -1))
|
||||
return
|
||||
}
|
||||
creatingSessionRef.current = true
|
||||
try {
|
||||
currentSession = await scriptBuilderApi.createSession(effectiveLanguage)
|
||||
setSession(currentSession)
|
||||
} finally {
|
||||
creatingSessionRef.current = false
|
||||
}
|
||||
}
|
||||
|
||||
// Send message
|
||||
|
||||
Reference in New Issue
Block a user