@@ -25,9 +25,10 @@ import {
2525} from '@sim/db/schema'
2626import { readTestRedisUrl } from '@sim/db/testing/test-infrastructure'
2727import { sha256Hex } from '@sim/security/hash'
28+ import { createDeferred } from '@sim/testing/helpers/deferred'
2829import { generateId } from '@sim/utils/id'
2930import { toArray , toRecord } from '@sim/utils/object'
30- import { eq , inArray } from 'drizzle-orm'
31+ import { eq , inArray , sql } from 'drizzle-orm'
3132import { afterAll , beforeAll , beforeEach , describe , expect , it , vi } from 'vitest'
3233import {
3334 claimOrganizationAssistantAction ,
@@ -639,6 +640,56 @@ describe('organization Assistant tool permissions', () => {
639640 }
640641 )
641642
643+ it ( 'reads a run that closes during the review read as nothing to review' , async ( ) => {
644+ await change ( 0 , 'ask' )
645+ await prepare ( )
646+ const held = createDeferred < number > ( )
647+ const release = createDeferred < void > ( )
648+ const closing = db . transaction ( async ( tx ) => {
649+ const [ blocker ] = await tx . execute < { pid : number } > ( sql `SELECT pg_backend_pid() AS pid` )
650+ if ( ! blocker ) throw new Error ( 'Missing closing transaction' )
651+ await tx . update ( copilotChats ) . set ( { conversationId : null } ) . where ( eq ( copilotChats . id , chatId ) )
652+ await tx
653+ . update ( copilotRuns )
654+ . set ( { status : 'complete' , toolAdmissionClosedAt : new Date ( ) } )
655+ . where ( eq ( copilotRuns . id , runId ) )
656+ held . resolve ( blocker . pid )
657+ await release . promise
658+ } )
659+ void closing . catch ( ( error : unknown ) => held . reject ( error ) )
660+ try {
661+ const blockerPid = await held . promise
662+ const reading = review ( )
663+ void reading . catch ( ( ) => undefined )
664+ await vi . waitFor (
665+ async ( ) => {
666+ const [ waiting ] = await db . execute < { pid : number } > ( sql `
667+ SELECT pid FROM pg_stat_activity
668+ WHERE datname = current_database()
669+ AND ${ blockerPid } ::int = ANY(pg_blocking_pids(pid))
670+ ` )
671+ expect ( waiting ) . toBeDefined ( )
672+ } ,
673+ { timeout : 10_000 , interval : 25 }
674+ )
675+ release . resolve ( )
676+ await closing
677+ expect ( await reading ) . toEqual ( {
678+ runId : null ,
679+ streamId : null ,
680+ actions : [ ] ,
681+ nextCursor : null ,
682+ } )
683+ } finally {
684+ release . resolve ( )
685+ await Promise . allSettled ( [ closing ] )
686+ await db
687+ . update ( copilotChats )
688+ . set ( { conversationId : streamId } )
689+ . where ( eq ( copilotChats . id , chatId ) )
690+ }
691+ } )
692+
642693 it ( 'pages eligible proposals past expired rows and keeps canonical turn metadata' , async ( ) => {
643694 await change ( 0 , 'ask' )
644695 const expired = [ ]
0 commit comments