@@ -289,108 +289,234 @@ export class PatchClient {
289289 console . log ( '- OPENAI_API_KEY:' , env . OPENAI_API_KEY ? 'Set' : 'Not set' ) ;
290290 console . log ( '- ANTHROPIC_API_KEY:' , env . ANTHROPIC_API_KEY ? 'Set' : 'Not set' ) ;
291291
292- // Run Aider with the issue file
293- const { stdout, stderr } = await execa ( 'aider' , aiderArgs , {
294- cwd : this . workingDir ,
295- timeout : this . options . timeout ,
296- env
297- } ) ;
292+ // Run Aider with the issue file and stream output in real-time
293+ console . log ( 'Starting Aider process - streaming output:' ) ;
294+ console . log ( '--------------------------------------------------' ) ;
298295
299- // Log Aider output for debugging
300- console . log ( 'Aider stdout:' , stdout ) ;
301- if ( stderr ) console . error ( 'Aider stderr:' , stderr ) ;
296+ // Setup a check interval to detect if Aider is stuck
297+ let lastOutputTime = Date . now ( ) ;
298+ let aiderActive = true ;
299+ const activityInterval = setInterval ( ( ) => {
300+ const timeSinceLastOutput = Date . now ( ) - lastOutputTime ;
301+ if ( aiderActive && timeSinceLastOutput > 30000 ) { // 30 seconds
302+ console . log ( `[MONITOR] No output from Aider for ${ Math . floor ( timeSinceLastOutput / 1000 ) } seconds. Still working...` ) ;
303+ }
304+ } , 30000 ) ;
302305
303- // Check if Aider made any changes
304- const gitStatus = await execa ( 'git' , [ 'status' , '--porcelain' ] , { cwd : this . workingDir } ) ;
305- const hasChanges = gitStatus . stdout . trim ( ) . length > 0 ;
306+ let progressInterval : NodeJS . Timeout | null = null ;
306307
307- if ( hasChanges ) {
308- // Get the list of changed files
309- const changedFiles = gitStatus . stdout
310- . split ( '\n' )
311- . filter ( Boolean )
312- . map ( line => line . substring ( 3 ) ) ;
308+ try {
309+ // Use execa with streaming output
310+ const aiderProcess = execa ( 'aider' , aiderArgs , {
311+ cwd : this . workingDir ,
312+ env,
313+ timeout : this . options . timeout ,
314+ // Stream the output
315+ stdout : 'pipe' ,
316+ stderr : 'pipe' ,
317+ buffer : false // Important for real-time streaming
318+ } ) ;
319+
320+ // Stream stdout with timestamp updates
321+ if ( aiderProcess . stdout ) {
322+ aiderProcess . stdout . on ( 'data' , ( data ) => {
323+ const output = data . toString ( ) ;
324+ if ( output . trim ( ) ) {
325+ console . log ( `[AIDER] ${ output . trim ( ) } ` ) ;
326+ lastOutputTime = Date . now ( ) ;
327+ }
328+ } ) ;
329+ }
330+
331+ // Stream stderr
332+ if ( aiderProcess . stderr ) {
333+ aiderProcess . stderr . on ( 'data' , ( data ) => {
334+ const output = data . toString ( ) ;
335+ if ( output . trim ( ) ) {
336+ console . error ( `[AIDER ERROR] ${ output . trim ( ) } ` ) ;
337+ lastOutputTime = Date . now ( ) ;
338+ }
339+ } ) ;
340+ }
313341
314- console . log ( `Changes detected in ${ changedFiles . length } files:` , changedFiles ) ;
342+ // Start a progress indicator
343+ let progressDots = 0 ;
344+ progressInterval = setInterval ( ( ) => {
345+ if ( aiderActive ) {
346+ process . stdout . write ( '.' ) ;
347+ progressDots ++ ;
348+ if ( progressDots % 60 === 0 ) {
349+ process . stdout . write ( '\n' ) ;
350+ }
351+ }
352+ } , 5000 ) ;
353+
354+ // Wait for Aider to complete
355+ const { stdout, stderr } = await aiderProcess ;
356+
357+ // Clear intervals once Aider is done
358+ clearInterval ( activityInterval ) ;
359+ if ( progressInterval ) clearInterval ( progressInterval ) ;
360+ aiderActive = false ;
361+
362+ // Add a newline if we were printing dots
363+ if ( progressDots > 0 && progressDots % 60 !== 0 ) {
364+ console . log ( '' ) ;
365+ }
315366
316- // Commit the changes
317- await execa ( 'git' , [ 'add' , '.' ] , { cwd : this . workingDir } ) ;
318- await execa ( 'git' , [ 'commit' , '-m' , `Fix: ${ issueTitle } ` ] , { cwd : this . workingDir } ) ;
367+ console . log ( '--------------------------------------------------' ) ;
368+ console . log ( 'Aider process completed' ) ;
319369
320- // Configure push URL with auth if needed
321- if ( authToken ) {
322- const url = new URL ( repoUrl ) ;
323- // Use proper GitHub authentication format based on token type
324- let authenticatedPushUrl ;
370+ // Check if Aider made any changes
371+ console . log ( 'Checking for changes made by Aider...' ) ;
372+ const gitStatus = await execa ( 'git' , [ 'status' , '--porcelain' ] , { cwd : this . workingDir } ) ;
373+ const hasChanges = gitStatus . stdout . trim ( ) . length > 0 ;
374+
375+ if ( hasChanges ) {
376+ // Get the list of changed files
377+ const changedFiles = gitStatus . stdout
378+ . split ( '\n' )
379+ . filter ( Boolean )
380+ . map ( line => line . substring ( 3 ) ) ;
381+
382+ console . log ( `Changes detected in ${ changedFiles . length } files:` , changedFiles ) ;
383+
384+ // Commit the changes
385+ await execa ( 'git' , [ 'add' , '.' ] , { cwd : this . workingDir } ) ;
386+ await execa ( 'git' , [ 'commit' , '-m' , `Fix: ${ issueTitle } ` ] , { cwd : this . workingDir } ) ;
325387
326- if ( url . hostname === 'github.com' ) {
327- const tokenType = this . identifyTokenType ( authToken ) ;
388+ // Configure push URL with auth if needed
389+ if ( authToken ) {
390+ const url = new URL ( repoUrl ) ;
391+ // Use proper GitHub authentication format based on token type
392+ let authenticatedPushUrl ;
328393
329- if ( tokenType === 'GitHub App Installation Token' ) {
330- // For GitHub App installation tokens (ghs_*)
331- authenticatedPushUrl = `https://x-access-token:${ authToken } @github.com${ url . pathname } ` ;
332- } else if ( tokenType === 'OAuth App Token' ) {
333- // For OAuth tokens
334- authenticatedPushUrl = `https://oauth2:${ authToken } @github.com${ url . pathname } ` ;
335- } else if ( tokenType === 'Personal Access Token' || tokenType === 'Fine-grained Personal Access Token' ) {
336- // For PATs
337- authenticatedPushUrl = `https://oauth2:${ authToken } @github.com${ url . pathname } ` ;
394+ if ( url . hostname === 'github.com' ) {
395+ const tokenType = this . identifyTokenType ( authToken ) ;
396+
397+ if ( tokenType === 'GitHub App Installation Token' ) {
398+ // For GitHub App installation tokens (ghs_*)
399+ authenticatedPushUrl = `https://x-access-token:${ authToken } @github.com${ url . pathname } ` ;
400+ } else if ( tokenType === 'OAuth App Token' ) {
401+ // For OAuth tokens
402+ authenticatedPushUrl = `https://oauth2:${ authToken } @github.com${ url . pathname } ` ;
403+ } else if ( tokenType === 'Personal Access Token' || tokenType === 'Fine-grained Personal Access Token' ) {
404+ // For PATs
405+ authenticatedPushUrl = `https://oauth2:${ authToken } @github.com${ url . pathname } ` ;
406+ } else {
407+ // Fallback for unknown token types - try x-access-token format
408+ authenticatedPushUrl = `https://x-access-token:${ authToken } @github.com${ url . pathname } ` ;
409+ console . log ( 'Using x-access-token format for GitHub authentication' ) ;
410+ }
338411 } else {
339- // Fallback for unknown token types - try x-access-token format
340- authenticatedPushUrl = `https://x-access-token:${ authToken } @github.com${ url . pathname } ` ;
341- console . log ( 'Using x-access-token format for GitHub authentication' ) ;
412+ // For non-GitHub repositories
413+ authenticatedPushUrl = repoUrl . replace ( `${ url . protocol } //` , `${ url . protocol } //${ authToken } @` ) ;
342414 }
343- } else {
344- // For non-GitHub repositories
345- authenticatedPushUrl = repoUrl . replace ( `${ url . protocol } //` , `${ url . protocol } //${ authToken } @` ) ;
415+
416+ // Apply the authenticated URL for push
417+ console . log ( `Configuring authenticated remote for pushing to ${ url . hostname } ${ url . pathname } ` ) ;
418+ await execa ( 'git' , [ 'remote' , 'set-url' , 'origin' , authenticatedPushUrl ] , { cwd : this . workingDir } ) ;
346419 }
347420
348- // Apply the authenticated URL for push
349- console . log ( `Configuring authenticated remote for pushing to ${ url . hostname } ${ url . pathname } ` ) ;
350- await execa ( 'git' , [ 'remote' , 'set-url' , 'origin' , authenticatedPushUrl ] , { cwd : this . workingDir } ) ;
421+ // Push the changes
422+ console . log ( `Pushing changes to branch: ${ branchName } ` ) ;
423+ // Set environment variables for Git to prevent prompting
424+ const pushEnv = {
425+ ...process . env ,
426+ GIT_TERMINAL_PROMPT : '0' ,
427+ GIT_ASKPASS : 'echo' ,
428+ GCM_INTERACTIVE : 'never'
429+ } ;
430+
431+ try {
432+ await execa ( 'git' , [ 'push' , 'origin' , branchName ] , {
433+ cwd : this . workingDir ,
434+ env : pushEnv ,
435+ timeout : 60000 // 1 minute timeout for push
436+ } ) ;
437+ } catch ( pushError ) {
438+ const errorMessage = pushError instanceof Error ? pushError . message : String ( pushError ) ;
439+ console . error ( `Push error (sanitized): ${ this . sanitizeErrorMessage ( errorMessage ) } ` ) ;
440+
441+ if ( errorMessage . includes ( 'could not read Username' ) ||
442+ errorMessage . includes ( 'Authentication failed' ) ||
443+ errorMessage . includes ( '403' ) ||
444+ errorMessage . includes ( '401' ) ) {
445+ throw new Error ( 'Failed to push changes: Authentication error. The token may not have write access to this repository.' ) ;
446+ }
447+ throw new Error ( `Failed to push changes: ${ this . sanitizeErrorMessage ( errorMessage ) } ` ) ;
448+ }
449+
450+ return {
451+ success : true ,
452+ changes : changedFiles ,
453+ message : 'Successfully applied fixes'
454+ } ;
455+ } else {
456+ console . log ( 'No changes were made by Aider' ) ;
457+ return {
458+ success : false ,
459+ changes : [ ] ,
460+ message : 'Aider did not make any changes to the codebase'
461+ } ;
351462 }
463+ } catch ( error ) {
464+ // Make sure to clean up intervals if there's an error
465+ clearInterval ( activityInterval ) ;
466+ if ( progressInterval ) clearInterval ( progressInterval ) ;
467+ aiderActive = false ;
352468
353- // Push the changes
354- console . log ( `Pushing changes to branch: ${ branchName } ` ) ;
355- // Set environment variables for Git to prevent prompting
356- const pushEnv = {
357- ...process . env ,
358- GIT_TERMINAL_PROMPT : '0' ,
359- GIT_ASKPASS : 'echo' ,
360- GCM_INTERACTIVE : 'never'
361- } ;
469+ console . error ( 'Error running Aider:' , error ) ;
362470
363- try {
364- await execa ( 'git' , [ 'push' , 'origin' , branchName ] , {
365- cwd : this . workingDir ,
366- env : pushEnv ,
367- timeout : 60000 // 1 minute timeout for push
368- } ) ;
369- } catch ( pushError ) {
370- const errorMessage = pushError instanceof Error ? pushError . message : String ( pushError ) ;
371- console . error ( `Push error (sanitized): ${ this . sanitizeErrorMessage ( errorMessage ) } ` ) ;
372-
373- if ( errorMessage . includes ( 'could not read Username' ) ||
374- errorMessage . includes ( 'Authentication failed' ) ||
375- errorMessage . includes ( '403' ) ||
376- errorMessage . includes ( '401' ) ) {
377- throw new Error ( 'Failed to push changes: Authentication error. The token may not have write access to this repository.' ) ;
471+ // Try to get additional information from the error
472+ let errorMessage = error instanceof Error ? error . message : String ( error ) ;
473+
474+ // Sanitize error message to remove any tokens
475+ errorMessage = this . sanitizeErrorMessage ( errorMessage ) ;
476+
477+ const fullError = errorMessage ; // Keep the sanitized error for logging
478+
479+ // Log the sanitized error for debugging
480+ console . error ( 'Full Aider error:' , fullError ) ;
481+
482+ // Check for specific error types
483+ if ( errorMessage . includes ( 'OPENAI_API_KEY' ) || errorMessage . includes ( 'openai.error.AuthenticationError' ) ) {
484+ if ( this . isClaudeModel ( ) ) {
485+ errorMessage = 'API key error with Claude model. Trying these fixes:\n' +
486+ '1. Set both ANTHROPIC_API_KEY and a dummy OPENAI_API_KEY\n' +
487+ '2. Try different Claude flags: --anthropic, --claude, or --use-anthropic\n' +
488+ '3. Update to the latest version of Aider: pip install -U aider-chat' ;
489+ } else {
490+ errorMessage = 'OpenAI API key is missing or invalid. Please set the OPENAI_API_KEY environment variable.' ;
378491 }
379- throw new Error ( `Failed to push changes: ${ this . sanitizeErrorMessage ( errorMessage ) } ` ) ;
492+ } else if ( errorMessage . includes ( 'ANTHROPIC_API_KEY' ) ||
493+ errorMessage . includes ( 'anthropic.AuthenticationError' ) ||
494+ errorMessage . includes ( 'anthropic.api_key' ) ) {
495+ errorMessage = 'Anthropic API key is missing or invalid. Please set the ANTHROPIC_API_KEY environment variable.' ;
496+ } else if ( errorMessage . includes ( 'ENOENT' ) && errorMessage . includes ( 'aider' ) ) {
497+ errorMessage = 'Aider executable not found. Please install Aider with: pip install aider-chat' ;
498+ } else if ( errorMessage . includes ( 'ETIMEDOUT' ) || errorMessage . includes ( 'timeout' ) ) {
499+ errorMessage = `Aider operation timed out after ${ this . options . timeout ? this . options . timeout / 1000 : 300 } seconds` ;
500+ } else if ( errorMessage . includes ( 'unrecognized arguments' ) ) {
501+ errorMessage = `Aider command line error: ${ errorMessage } \n\nThis may be due to version differences. Try updating Aider: pip install -U aider-chat` ;
502+ }
503+
504+ // Log Claude-specific advice to console, but don't include it in the user-facing error message
505+ if ( this . isClaudeModel ( ) ) {
506+ console . log ( 'Additional Claude troubleshooting (for developers):' ) ;
507+ console . log ( '• Make sure ANTHROPIC_API_KEY is set and valid' ) ;
508+ console . log ( '• Try different flags for Claude in AIDER_EXTRA_ARGS (--anthropic or --claude)' ) ;
509+ console . log ( '• Check Aider version compatibility with Claude models' ) ;
380510 }
381511
382- return {
383- success : true ,
384- changes : changedFiles ,
385- message : 'Successfully applied fixes'
386- } ;
387- } else {
388- console . log ( 'No changes were made by Aider' ) ;
389512 return {
390513 success : false ,
391514 changes : [ ] ,
392- message : 'Aider did not make any changes to the codebase'
515+ message : `Error running Aider: ${ errorMessage } `
393516 } ;
517+ } finally {
518+ // Clean up
519+ await this . cleanup ( ) ;
394520 }
395521 } catch ( error ) {
396522 console . error ( 'Error running Aider:' , error ) ;
@@ -441,9 +567,6 @@ export class PatchClient {
441567 changes : [ ] ,
442568 message : `Error running Aider: ${ errorMessage } `
443569 } ;
444- } finally {
445- // Clean up
446- await this . cleanup ( ) ;
447570 }
448571 }
449572
0 commit comments