1+ /* eslint-disable no-await-in-loop */
2+
13/**
24 * @fileoverview Claude Code-powered utilities for Socket projects.
35 * Provides various AI-assisted development tools and automations using Claude Code CLI.
@@ -173,6 +175,140 @@ async function checkClaude() {
173175 return false
174176}
175177
178+ /**
179+ * Ensure Claude Code is authenticated, prompting for login if needed.
180+ * Returns true if authenticated, false if unable to authenticate.
181+ */
182+ async function ensureClaudeAuthenticated ( claudeCmd ) {
183+ let attempts = 0
184+ const maxAttempts = 3
185+
186+ while ( attempts < maxAttempts ) {
187+ // Check if Claude is working by checking version
188+ log . progress ( 'Checking Claude Code status' )
189+ const versionCheck = await runCommandWithOutput ( claudeCmd , [ '--version' ] )
190+
191+ if ( versionCheck . exitCode === 0 ) {
192+ // Claude Code is installed and working
193+ // Check if we need to login by looking for specific error patterns
194+ const testPrompt = 'echo "test"'
195+ const testResult = await runCommandWithOutput ( claudeCmd , [ ] , {
196+ input : testPrompt ,
197+ stdio : [ 'pipe' , 'pipe' , 'pipe' ] ,
198+ env : { ...process . env , CLAUDE_OUTPUT_MODE : 'text' } ,
199+ timeout : 10000
200+ } )
201+
202+ // Check for authentication errors
203+ const output = ( testResult . stdout + testResult . stderr ) . toLowerCase ( )
204+ const needsAuth = output . includes ( 'not logged in' ) ||
205+ output . includes ( 'authentication' ) ||
206+ output . includes ( 'unauthorized' ) ||
207+ output . includes ( 'login required' ) ||
208+ output . includes ( 'please login' )
209+
210+ if ( ! needsAuth && ( testResult . exitCode === 0 || testResult . stdout . length > 0 ) ) {
211+ log . done ( 'Claude Code ready' )
212+ return true
213+ }
214+
215+ if ( ! needsAuth ) {
216+ // Claude seems to be working, even if the test had an odd response
217+ log . done ( 'Claude Code ready' )
218+ return true
219+ }
220+ }
221+
222+ attempts ++
223+
224+ if ( attempts >= maxAttempts ) {
225+ log . error ( `Failed to setup Claude Code after ${ maxAttempts } attempts` )
226+ return false
227+ }
228+
229+ // Not authenticated, prompt for login
230+ log . warn ( 'Claude Code login required' )
231+ console . log ( colors . yellow ( '\nClaude Code needs to be authenticated.' ) )
232+ console . log ( 'This will open your browser for authentication.\n' )
233+
234+ // Run claude login interactively
235+ log . progress ( 'Starting Claude Code login process' )
236+ const loginResult = await runCommand ( claudeCmd , [ '--login' ] , {
237+ stdio : 'inherit'
238+ } )
239+
240+ if ( loginResult === 0 ) {
241+ log . done ( 'Login process completed' )
242+ // Give it a moment for the auth to register
243+ await new Promise ( resolve => setTimeout ( resolve , 3000 ) )
244+ } else {
245+ log . failed ( 'Login process failed or was cancelled' )
246+
247+ if ( attempts < maxAttempts ) {
248+ console . log ( colors . yellow ( `\nWould you like to try again?` ) )
249+ console . log ( colors . gray ( `Attempt ${ attempts } of ${ maxAttempts } ` ) )
250+ // Wait a bit before retrying
251+ await new Promise ( resolve => setTimeout ( resolve , 2000 ) )
252+ }
253+ }
254+ }
255+
256+ return false
257+ }
258+
259+ /**
260+ * Ensure GitHub CLI is authenticated, prompting for login if needed.
261+ * Returns true if authenticated, false if unable to authenticate.
262+ */
263+ async function ensureGitHubAuthenticated ( ) {
264+ let attempts = 0
265+ const maxAttempts = 3
266+
267+ while ( attempts < maxAttempts ) {
268+ log . progress ( 'Checking GitHub authentication' )
269+ const authCheck = await runCommandWithOutput ( 'gh' , [ 'auth' , 'status' ] )
270+
271+ if ( authCheck . exitCode === 0 ) {
272+ log . done ( 'GitHub CLI authenticated' )
273+ return true
274+ }
275+
276+ attempts ++
277+
278+ if ( attempts >= maxAttempts ) {
279+ log . error ( `Failed to authenticate with GitHub after ${ maxAttempts } attempts` )
280+ return false
281+ }
282+
283+ // Not authenticated, prompt for login
284+ log . warn ( 'GitHub authentication required' )
285+ console . log ( colors . yellow ( '\nYou need to authenticate with GitHub.' ) )
286+ console . log ( 'Follow the prompts to complete authentication.\n' )
287+
288+ // Run gh auth login interactively
289+ log . progress ( 'Starting GitHub login process' )
290+ const loginResult = await runCommand ( 'gh' , [ 'auth' , 'login' ] , {
291+ stdio : 'inherit'
292+ } )
293+
294+ if ( loginResult === 0 ) {
295+ log . done ( 'Login process completed' )
296+ // Give it a moment for the auth to register
297+ await new Promise ( resolve => setTimeout ( resolve , 2000 ) )
298+ } else {
299+ log . failed ( 'Login process failed' )
300+ console . log ( colors . red ( '\nLogin failed. Please try again.' ) )
301+
302+ if ( attempts < maxAttempts ) {
303+ console . log ( colors . yellow ( `\nAttempt ${ attempts + 1 } of ${ maxAttempts } ` ) )
304+ await new Promise ( resolve => setTimeout ( resolve , 1000 ) )
305+ }
306+ }
307+ }
308+
309+ return false
310+ }
311+
176312/**
177313 * Prepare Claude command arguments for Claude Code.
178314 * Claude Code uses natural language prompts, not the same flags.
@@ -608,7 +744,8 @@ async function scanProjectForIssues(claudeCmd, project, options = {}) {
608744 const extensions = [ '.js' , '.mjs' , '.ts' , '.mts' , '.jsx' , '.tsx' ]
609745
610746 async function findFiles ( dir , depth = 0 ) {
611- if ( depth > 5 ) { return } // Limit depth to avoid excessive scanning.
747+ // Limit depth to avoid excessive scanning
748+ if ( depth > 5 ) { return }
612749
613750 const entries = await fs . readdir ( dir , { withFileTypes : true } )
614751
@@ -691,7 +828,8 @@ Provide ONLY the JSON array, nothing else.`
691828 const result = await runCommandWithOutput ( claudeCmd , prepareClaudeArgs ( [ ] , options ) , {
692829 input : prompt ,
693830 stdio : [ 'pipe' , 'pipe' , 'pipe' ] ,
694- maxBuffer : 1024 * 1024 * 10 // 10MB buffer for large responses.
831+ // 10MB buffer for large responses
832+ maxBuffer : 1024 * 1024 * 10
695833 } )
696834
697835 if ( result . exitCode !== 0 ) {
@@ -703,7 +841,7 @@ Provide ONLY the JSON array, nothing else.`
703841
704842 try {
705843 return JSON . parse ( result . stdout . trim ( ) )
706- } catch ( _e ) {
844+ } catch {
707845 log . warn ( `Failed to parse scan results for ${ name } ` )
708846 return null
709847 }
@@ -1665,7 +1803,8 @@ Fix this issue now by making the necessary changes.`
16651803
16661804 // Run Claude non-interactively with timeout and progress
16671805 const startTime = Date . now ( )
1668- const timeout = 120000 // 2 minute timeout
1806+ // 2 minute timeout
1807+ const timeout = 120000
16691808 log . substep ( `[${ repoName } ] Analyzing error...` )
16701809
16711810 const claudeProcess = spawn ( claudeCmd , prepareClaudeArgs ( [ ] , opts ) , {
@@ -1686,7 +1825,8 @@ Fix this issue now by making the necessary changes.`
16861825 } else {
16871826 log . substep ( `[${ repoName } ] Claude working... (${ Math . round ( elapsed / 1000 ) } s)` )
16881827 }
1689- } , 10000 ) // Update every 10 seconds
1828+ } , 10000 )
1829+ // Update every 10 seconds
16901830
16911831 await new Promise ( ( resolve ) => {
16921832 claudeProcess . on ( 'close' , ( ) => {
@@ -1732,7 +1872,8 @@ Let's work through this together to get CI passing.`
17321872 await runCommand ( claudeCmd , prepareClaudeArgs ( [ ] , opts ) , {
17331873 input : interactivePrompt ,
17341874 cwd : rootPath ,
1735- stdio : 'inherit' // Interactive mode
1875+ // Interactive mode
1876+ stdio : 'inherit'
17361877 } )
17371878
17381879 // Try once more after interactive session
@@ -1801,7 +1942,7 @@ Let's work through this together to get CI passing.`
18011942 return true
18021943 }
18031944
1804- // Check for GitHub CLI and authentication
1945+ // Check for GitHub CLI
18051946 const ghCheck = await runCommandWithOutput ( 'which' , [ 'gh' ] )
18061947 if ( ghCheck . exitCode !== 0 ) {
18071948 log . error ( 'GitHub CLI (gh) is required for CI monitoring' )
@@ -1818,25 +1959,14 @@ Let's work through this together to get CI passing.`
18181959 return false
18191960 }
18201961
1821- // Check if gh is authenticated
1822- log . progress ( 'Checking GitHub authentication' )
1823- const authCheck = await runCommandWithOutput ( 'gh' , [ 'auth' , 'status' ] )
1824- if ( authCheck . exitCode !== 0 ) {
1825- log . failed ( 'GitHub CLI is not authenticated' )
1826- console . log ( colors . yellow ( '\nYou need to authenticate with GitHub:' ) )
1827- console . log ( ` 1. Run: ${ colors . green ( 'gh auth login' ) } ` )
1828- console . log ( ' 2. Choose "GitHub.com"' )
1829- console . log ( ' 3. Choose your preferred authentication method' )
1830- console . log ( ' 4. Follow the prompts to complete authentication' )
1831- console . log ( ` 5. Try again: ${ colors . green ( 'pnpm claude --green' ) } ` )
1832-
1833- if ( authCheck . stderr ) {
1834- console . log ( colors . gray ( '\nError details:' ) )
1835- console . log ( colors . gray ( authCheck . stderr ) )
1836- }
1962+ // Ensure GitHub is authenticated (will handle login automatically)
1963+ const isGitHubAuthenticated = await ensureGitHubAuthenticated ( )
1964+ if ( ! isGitHubAuthenticated ) {
1965+ log . error ( 'Unable to authenticate with GitHub' )
1966+ console . log ( colors . red ( '\nGitHub authentication is required for CI monitoring.' ) )
1967+ console . log ( 'Please ensure you can login to GitHub CLI and try again.' )
18371968 return false
18381969 }
1839- log . done ( 'GitHub CLI authenticated' )
18401970
18411971 // Get current commit SHA
18421972 const shaResult = await runCommandWithOutput ( 'git' , [ 'rev-parse' , 'HEAD' ] , {
@@ -1873,16 +2003,33 @@ Let's work through this together to get CI passing.`
18732003 }
18742004
18752005 // Check workflow runs using gh CLI
1876- const runsResult = await runCommandWithOutput ( 'gh' , [
2006+ // First try to find runs for the specific commit
2007+ let runsResult = await runCommandWithOutput ( 'gh' , [
18772008 'run' , 'list' ,
18782009 '--repo' , `${ owner } /${ repo } ` ,
18792010 '--commit' , currentSha ,
18802011 '--limit' , '1' ,
1881- '--json' , 'databaseId,status,conclusion,name'
2012+ '--json' , 'databaseId,status,conclusion,name,headSha '
18822013 ] , {
18832014 cwd : rootPath
18842015 } )
18852016
2017+ // If no runs found for commit, get recent runs and check if any match our SHA
2018+ if ( runsResult . exitCode === 0 ) {
2019+ const runs = JSON . parse ( runsResult . stdout || '[]' )
2020+ if ( runs . length === 0 ) {
2021+ // Fallback: get latest runs and find our commit
2022+ runsResult = await runCommandWithOutput ( 'gh' , [
2023+ 'run' , 'list' ,
2024+ '--repo' , `${ owner } /${ repo } ` ,
2025+ '--limit' , '10' ,
2026+ '--json' , 'databaseId,status,conclusion,name,headSha'
2027+ ] , {
2028+ cwd : rootPath
2029+ } )
2030+ }
2031+ }
2032+
18862033 if ( runsResult . exitCode !== 0 ) {
18872034 log . failed ( 'Failed to fetch workflow runs' )
18882035
@@ -1916,13 +2063,33 @@ Let's work through this together to get CI passing.`
19162063 return false
19172064 }
19182065
1919- if ( runs . length === 0 ) {
2066+ // Filter runs to find one matching our commit SHA
2067+ let matchingRun = null
2068+ for ( const run of runs ) {
2069+ if ( run . headSha && run . headSha . startsWith ( currentSha . substring ( 0 , 7 ) ) ) {
2070+ matchingRun = run
2071+ break
2072+ }
2073+ }
2074+
2075+ if ( ! matchingRun && runs . length > 0 ) {
2076+ // If no exact match, take the most recent run if it was triggered recently
2077+ // This handles cases where the workflow was triggered by the push but headSha isn't set yet
2078+ const latestRun = runs [ 0 ]
2079+ if ( retryCount === 0 ) {
2080+ // On first attempt, assume the latest run might be ours if triggered very recently
2081+ matchingRun = latestRun
2082+ log . substep ( `Monitoring latest workflow run: ${ latestRun . name } ` )
2083+ }
2084+ }
2085+
2086+ if ( ! matchingRun ) {
19202087 log . substep ( 'No workflow runs found yet, waiting...' )
19212088 await new Promise ( resolve => setTimeout ( resolve , 30000 ) )
19222089 continue
19232090 }
19242091
1925- const run = runs [ 0 ]
2092+ const run = matchingRun
19262093 lastRunId = run . databaseId
19272094
19282095 log . substep ( `Workflow "${ run . name } " status: ${ run . status } ` )
@@ -1978,7 +2145,8 @@ Fix all CI failures now by making the necessary changes.`
19782145 await runCommand ( claudeCmd , prepareClaudeArgs ( [ ] , opts ) , {
19792146 input : fixPrompt ,
19802147 cwd : rootPath ,
1981- stdio : 'pipe' // Run silently
2148+ // Run silently
2149+ stdio : 'pipe'
19822150 } )
19832151
19842152 // Give Claude's changes a moment to complete
@@ -2259,6 +2427,16 @@ async function main() {
22592427 }
22602428 log . done ( `Found Claude Code CLI: ${ claudeCmd } ` )
22612429
2430+ // Ensure Claude is authenticated
2431+ const isClaudeAuthenticated = await ensureClaudeAuthenticated ( claudeCmd )
2432+ if ( ! isClaudeAuthenticated ) {
2433+ log . error ( 'Unable to authenticate with Claude Code' )
2434+ console . log ( colors . red ( '\nAuthentication is required to use Claude utilities.' ) )
2435+ console . log ( 'Please ensure you can login to Claude Code and try again.' )
2436+ process . exitCode = 1
2437+ return
2438+ }
2439+
22622440 // Execute requested operation.
22632441 let success = true
22642442 const options = { ...values , positionals }
0 commit comments