@@ -114,7 +114,7 @@ export class TraceWatcher extends vscode.Disposable {
114114
115115 private activeSessionWatcher : fs . FSWatcher | null = null ;
116116 private eventsWatcher : fs . FSWatcher | null = null ;
117- private pollTimer : NodeJS . Timeout | null = null ;
117+ private pollTimer : ReturnType < typeof setInterval > | null = null ;
118118
119119 constructor ( workspaceRoot : string , traceDir : string ) {
120120 super ( ( ) => this . dispose ( ) ) ;
@@ -140,23 +140,23 @@ export class TraceWatcher extends vscode.Disposable {
140140 // -------------------------------------------------------------------------
141141
142142 private _watchActiveSessionFile ( ) : void {
143- // Watch the parent directory so we catch creation of .active-session
144143 const dir = this . traceDir ;
145- if ( ! fs . existsSync ( dir ) ) {
146- // Retry when the directory appears
147- this . pollTimer = setTimeout ( ( ) => this . _watchActiveSessionFile ( ) , 2000 ) ;
148- return ;
149- }
150144
151- try {
152- this . activeSessionWatcher = fs . watch ( dir , ( _event , filename ) => {
153- if ( filename === ".active-session" ) {
154- this . _checkActiveSession ( ) ;
155- }
156- } ) ;
157- } catch {
158- // Fall back to polling if fs.watch fails (e.g. network FS)
159- this . pollTimer = setInterval ( ( ) => this . _checkActiveSession ( ) , 1000 ) ;
145+ // Always use polling as primary — fs.watch silently fails on network/FUSE
146+ // filesystems (e.g. Gitpod, WSL, Docker volumes) without throwing.
147+ this . pollTimer = setInterval ( ( ) => this . _checkActiveSession ( ) , 500 ) ;
148+
149+ // Also try fs.watch as a faster secondary trigger (best-effort)
150+ if ( fs . existsSync ( dir ) ) {
151+ try {
152+ this . activeSessionWatcher = fs . watch ( dir , ( _event , filename ) => {
153+ if ( filename === ".active-session" ) {
154+ this . _checkActiveSession ( ) ;
155+ }
156+ } ) ;
157+ } catch {
158+ // polling already running, ignore
159+ }
160160 }
161161 }
162162
@@ -226,21 +226,26 @@ export class TraceWatcher extends vscode.Disposable {
226226
227227 private _watchEventsFile ( sessionId : string ) : void {
228228 const eventsFile = path . join ( this . traceDir , sessionId , "events.ndjson" ) ;
229- if ( ! fs . existsSync ( eventsFile ) ) { return ; }
230229
231- try {
232- this . eventsWatcher = fs . watch ( eventsFile , ( ) => {
233- this . _readNewEvents ( ) ;
234- } ) ;
235- } catch {
236- // Polling fallback
237- const timer = setInterval ( ( ) => {
238- if ( this . currentSessionId !== sessionId ) {
239- clearInterval ( timer ) ;
240- return ;
241- }
242- this . _readNewEvents ( ) ;
243- } , 500 ) ;
230+ // Always poll — fs.watch silently fails on network/FUSE filesystems.
231+ // 300ms gives snappy updates without hammering the FS.
232+ const timer = setInterval ( ( ) => {
233+ if ( this . currentSessionId !== sessionId ) {
234+ clearInterval ( timer ) ;
235+ return ;
236+ }
237+ this . _readNewEvents ( ) ;
238+ } , 300 ) ;
239+
240+ // Also try fs.watch as a faster secondary trigger (best-effort)
241+ if ( fs . existsSync ( eventsFile ) ) {
242+ try {
243+ this . eventsWatcher = fs . watch ( eventsFile , ( ) => {
244+ this . _readNewEvents ( ) ;
245+ } ) ;
246+ } catch {
247+ // polling already running, ignore
248+ }
244249 }
245250 }
246251
@@ -384,7 +389,7 @@ export class TraceWatcher extends vscode.Disposable {
384389 override dispose ( ) : void {
385390 this . activeSessionWatcher ?. close ( ) ;
386391 this . eventsWatcher ?. close ( ) ;
387- if ( this . pollTimer ) { clearTimeout ( this . pollTimer ) ; }
392+ if ( this . pollTimer ) { clearInterval ( this . pollTimer ) ; }
388393 this . _onSessionStart . dispose ( ) ;
389394 this . _onSessionEnd . dispose ( ) ;
390395 this . _onEvent . dispose ( ) ;
0 commit comments