@@ -299,10 +299,51 @@ export default async function deployGitHubRoutes(server: FastifyInstance) {
299299 const installation = await credentialService . getInstallation ( teamId , 'github' ) ;
300300
301301 if ( ! installation ) {
302- // No database record - team must install the GitHub App via the /install flow
303- const response : ConnectionStatusResponse = { connected : false } ;
304- const jsonString = JSON . stringify ( response ) ;
305- return reply . status ( 200 ) . type ( 'application/json' ) . send ( jsonString ) ;
302+ // No database record - try to auto-detect from the current user's personal GitHub account
303+ const user = request . user as { githubId ?: string | null } | undefined ;
304+ const githubId = user ?. githubId ;
305+
306+ if ( ! githubId ) {
307+ // User authenticated via email, not GitHub - cannot auto-detect
308+ const response : ConnectionStatusResponse = { connected : false } ;
309+ const jsonString = JSON . stringify ( response ) ;
310+ return reply . status ( 200 ) . type ( 'application/json' ) . send ( jsonString ) ;
311+ }
312+
313+ try {
314+ const userInstallations = await githubService . listUserInstallations ( githubId ) ;
315+
316+ if ( userInstallations . length > 0 ) {
317+ const selectedInstallation = userInstallations [ 0 ] ;
318+
319+ await credentialService . storeInstallation ( {
320+ teamId,
321+ source : 'github' ,
322+ installationId : selectedInstallation . id . toString ( )
323+ } ) ;
324+
325+ server . log . info ( {
326+ teamId,
327+ githubId,
328+ installation_id : selectedInstallation . id ,
329+ account_login : selectedInstallation . account . login ,
330+ operation : 'auto_linked_user_installation'
331+ } , 'Auto-linked GitHub installation to team (scoped to user)' ) ;
332+
333+ const response : ConnectionStatusResponse = { connected : true } ;
334+ const jsonString = JSON . stringify ( response ) ;
335+ return reply . status ( 200 ) . type ( 'application/json' ) . send ( jsonString ) ;
336+ }
337+
338+ const response : ConnectionStatusResponse = { connected : false } ;
339+ const jsonString = JSON . stringify ( response ) ;
340+ return reply . status ( 200 ) . type ( 'application/json' ) . send ( jsonString ) ;
341+ } catch ( error ) {
342+ server . log . warn ( { error, teamId, githubId } , 'Failed to auto-detect GitHub installation for user' ) ;
343+ const response : ConnectionStatusResponse = { connected : false } ;
344+ const jsonString = JSON . stringify ( response ) ;
345+ return reply . status ( 200 ) . type ( 'application/json' ) . send ( jsonString ) ;
346+ }
306347 }
307348
308349 // Step 2: Verify installation still exists on GitHub
@@ -319,7 +360,40 @@ export default async function deployGitHubRoutes(server: FastifyInstance) {
319360 operation : 'stale_installation_cleaned'
320361 } , 'Cleaned up stale GitHub installation record' ) ;
321362
322- // Team must re-install the GitHub App via the /install flow
363+ // After cleanup, try user-scoped auto-detection for a valid installation
364+ const user = request . user as { githubId ?: string | null } | undefined ;
365+ const githubId = user ?. githubId ;
366+
367+ if ( githubId ) {
368+ try {
369+ const userInstallations = await githubService . listUserInstallations ( githubId ) ;
370+
371+ if ( userInstallations . length > 0 ) {
372+ const selectedInstallation = userInstallations [ 0 ] ;
373+
374+ await credentialService . storeInstallation ( {
375+ teamId,
376+ source : 'github' ,
377+ installationId : selectedInstallation . id . toString ( )
378+ } ) ;
379+
380+ server . log . info ( {
381+ teamId,
382+ githubId,
383+ installation_id : selectedInstallation . id ,
384+ account_login : selectedInstallation . account . login ,
385+ operation : 'auto_linked_user_installation_after_cleanup'
386+ } , 'Auto-linked GitHub installation to team after cleaning stale record' ) ;
387+
388+ const response : ConnectionStatusResponse = { connected : true } ;
389+ const jsonString = JSON . stringify ( response ) ;
390+ return reply . status ( 200 ) . type ( 'application/json' ) . send ( jsonString ) ;
391+ }
392+ } catch ( retryError ) {
393+ server . log . warn ( { error : retryError , teamId, githubId } , 'Failed to auto-detect installation after cleanup' ) ;
394+ }
395+ }
396+
323397 const response : ConnectionStatusResponse = { connected : false } ;
324398 const jsonString = JSON . stringify ( response ) ;
325399 return reply . status ( 200 ) . type ( 'application/json' ) . send ( jsonString ) ;
0 commit comments