2525import { Command } from 'commander' ;
2626import * as path from 'path' ;
2727import * as fs from 'fs' ;
28+ import { spawn } from 'child_process' ;
2829import CodeGraph , { getCodeGraphDir , findNearestCodeGraphRoot } from '../index' ;
2930import type { IndexProgress } from '../index' ;
3031import { runInstaller } from '../installer' ;
@@ -957,8 +958,11 @@ program
957958/**
958959 * codegraph sync-if-dirty [path]
959960 *
960- * Syncs the index only if .codegraph/.dirty exists.
961- * Removes the marker BEFORE syncing so edits during sync
961+ * Checks if .codegraph/.dirty exists and, if so, spawns a detached
962+ * background process to run `codegraph sync`. The hook process exits
963+ * immediately so Claude Code's Stop hook never blocks.
964+ *
965+ * Removes the marker BEFORE spawning so edits during sync
962966 * create a new marker for the next Stop event.
963967 * Runs silently and always exits 0.
964968 */
@@ -972,7 +976,7 @@ program
972976 if ( ! projectRoot ) {
973977 process . exit ( 0 ) ;
974978 }
975- const dirtyPath = path . join ( getCodeGraphDir ( projectRoot ) , '.dirty' ) ;
979+ const dirtyPath = path . join ( getCodeGraphDir ( projectRoot ! ) , '.dirty' ) ;
976980
977981 // No marker → nothing to do (sub-ms exit)
978982 if ( ! fs . existsSync ( dirtyPath ) ) {
@@ -983,14 +987,24 @@ program
983987 try { fs . unlinkSync ( dirtyPath ) ; } catch { /* ignore */ }
984988
985989 // If not fully initialized (no DB), exit
986- if ( ! CodeGraph . isInitialized ( projectRoot ) ) {
990+ if ( ! CodeGraph . isInitialized ( projectRoot ! ) ) {
987991 process . exit ( 0 ) ;
988992 }
989993
990- // Run sync
991- const cg = await CodeGraph . open ( projectRoot ) ;
992- await cg . sync ( ) ;
993- cg . destroy ( ) ;
994+ // Spawn `codegraph sync` as a detached background process
995+ // so this hook exits immediately and doesn't block Claude Code
996+ const isWindows = process . platform === 'win32' ;
997+ const child = spawn (
998+ isWindows ? 'codegraph' : process . argv [ 0 ] ! ,
999+ isWindows ? [ 'sync' , '--quiet' , projectRoot ! ] : [ process . argv [ 1 ] ! , 'sync' , '--quiet' , projectRoot ! ] ,
1000+ {
1001+ detached : true ,
1002+ stdio : 'ignore' ,
1003+ windowsHide : true ,
1004+ shell : isWindows ,
1005+ }
1006+ ) ;
1007+ child . unref ( ) ;
9941008 } catch {
9951009 // Never fail — this runs at the end of Claude responses
9961010 }
0 commit comments