@@ -298,9 +298,9 @@ jobs:
298298 return;
299299 }
300300
301- // Check if PR is from a fork (not supported)
302- if (pr.head.repo.full_name !== `${context.repo.owner}/${context.repo.repo}`) {
303- const msg = `PR #${targetPrNumber} is from a fork. Cannot push to fork branches.`;
301+ // Check if PR is from a fork (not supported) - also handle deleted fork repos
302+ if (!pr.head.repo || pr.head.repo.full_name !== `${context.repo.owner}/${context.repo.repo}`) {
303+ const msg = `PR #${targetPrNumber} is from a fork or the source repository was deleted . Cannot push to fork branches.`;
304304 core.setOutput('error_message', msg);
305305 core.setFailed(msg);
306306 return;
@@ -381,14 +381,13 @@ jobs:
381381 IFS=' ' read -ra TEST_ARRAY <<< "$TEST_NAMES"
382382
383383 # Run the tool and capture output
384- echo "Running: dotnet run --project ${{ github.workspace }}/tools/QuarantineTools/QuarantineTools.csproj -- $FLAG -m $MODE -i \"$ISSUE_URL\" ${TEST_ARRAY[*]}"
385-
386384 if [ "$ACTION" = "quarantine" ]; then
387- OUTPUT=$(dotnet run --project ${{ github.workspace }}/tools/QuarantineTools/QuarantineTools.csproj -- $FLAG -m "$MODE" -i "$ISSUE_URL" "${TEST_ARRAY[@]}" 2>&1 || true)
385+ echo "Running: dotnet run --project ${{ github.workspace }}/tools/QuarantineTools/QuarantineTools.csproj -- $FLAG -m $MODE -i \"$ISSUE_URL\" ${TEST_ARRAY[*]}"
386+ OUTPUT=$(dotnet run --project ${{ github.workspace }}/tools/QuarantineTools/QuarantineTools.csproj -- $FLAG -m "$MODE" -i "$ISSUE_URL" "${TEST_ARRAY[@]}" 2>&1) && EXIT_CODE=0 || EXIT_CODE=$?
388387 else
389- OUTPUT=$(dotnet run --project ${{ github.workspace }}/tools/QuarantineTools/QuarantineTools.csproj -- $FLAG -m "$MODE" "${TEST_ARRAY[@]}" 2>&1 || true)
388+ echo "Running: dotnet run --project ${{ github.workspace }}/tools/QuarantineTools/QuarantineTools.csproj -- $FLAG -m $MODE ${TEST_ARRAY[*]}"
389+ OUTPUT=$(dotnet run --project ${{ github.workspace }}/tools/QuarantineTools/QuarantineTools.csproj -- $FLAG -m "$MODE" "${TEST_ARRAY[@]}" 2>&1) && EXIT_CODE=0 || EXIT_CODE=$?
390390 fi
391- EXIT_CODE=$?
392391
393392 echo "$OUTPUT"
394393
@@ -425,7 +424,7 @@ jobs:
425424 IS_NEW_PR : ${{ steps.determine-target.outputs.is_new_pr }}
426425 with :
427426 script : |
428- const { execSync } = require('child_process');
427+ const { execSync, spawnSync } = require('child_process');
429428 const fs = require('fs');
430429 const path = require('path');
431430
@@ -447,13 +446,26 @@ jobs:
447446 const testList = testNames.length === 1 ? testNames[0] : `${testNames.length} tests`;
448447 const commitMessage = `[automated] ${actionVerb} ${testList}`;
449448
450- // Helper to run git commands safely (avoids shell injection)
449+ // Helper to run git commands safely using spawnSync (avoids shell injection)
450+ function runGit(args) {
451+ const result = spawnSync('git', args, { encoding: 'utf-8', stdio: 'pipe' });
452+ if (result.status !== 0) {
453+ const error = new Error(result.stderr || result.stdout || 'Git command failed');
454+ error.status = result.status;
455+ throw error;
456+ }
457+ return result.stdout;
458+ }
459+
460+ // Helper to commit with a message (uses temp file for safety)
451461 function gitCommit(message) {
452- // Write commit message to a temp file to avoid shell injection
453462 const msgFile = path.join(process.env.RUNNER_TEMP || '/tmp', 'commit-msg.txt');
454463 fs.writeFileSync(msgFile, message);
455- execSync(`git commit -F "${msgFile}"`);
456- fs.unlinkSync(msgFile);
464+ try {
465+ runGit(['commit', '-F', msgFile]);
466+ } finally {
467+ fs.unlinkSync(msgFile);
468+ }
457469 }
458470
459471 let prUrl = '';
@@ -468,12 +480,12 @@ jobs:
468480 prNumber = targetPrNumber;
469481
470482 // Stage and commit
471- execSync('git add -A');
483+ runGit([' add', ' -A'] );
472484 gitCommit(commitMessage);
473485
474- // Push to the PR branch
486+ // Push to the PR branch (checkoutRef comes from PR, use spawnSync for safety)
475487 try {
476- execSync(`git push origin HEAD:${checkoutRef}`);
488+ runGit([' push', ' origin', ` HEAD:${checkoutRef}`] );
477489 } catch (pushError) {
478490 core.setFailed(`Failed to push to PR #${targetPrNumber}: ${pushError.message}. The branch may be protected or have been force-pushed.`);
479491 return;
@@ -484,18 +496,19 @@ jobs:
484496 // Create a new PR
485497 createdNewPr = true;
486498 const timestamp = Date.now();
499+ // Branch name is safe: actionVerb is from controlled lookup (Quarantine/Disable/etc), timestamp is numeric
487500 const branchName = `automated/${actionVerb.toLowerCase()}-test-${timestamp}`;
488501
489- // Create and checkout branch
490- execSync(`git checkout -b ${ branchName}` );
502+ // Create and checkout branch (use spawnSync for safety)
503+ runGit([' checkout', '-b', branchName] );
491504
492505 // Stage and commit changes
493- execSync('git add -A');
506+ runGit([' add', ' -A'] );
494507 gitCommit(commitMessage);
495508
496509 // Push branch
497510 try {
498- execSync(`git push origin ${ branchName}` );
511+ runGit([' push', ' origin', branchName] );
499512 } catch (pushError) {
500513 core.setFailed(`Failed to push branch '${branchName}': ${pushError.message}`);
501514 return;
@@ -537,7 +550,7 @@ jobs:
537550 owner: context.repo.owner,
538551 repo: context.repo.repo,
539552 issue_number: prNumber,
540- labels: ['automated', ' area-codeflow']
553+ labels: ['area-codeflow']
541554 });
542555 } catch (labelError) {
543556 console.log(`Note: Could not add labels: ${labelError.message}`);
@@ -600,18 +613,23 @@ jobs:
600613 - name : Post failure comment
601614 if : failure()
602615 uses : actions/github-script@v7
616+ env :
617+ ACTION_VERB : ${{ steps.extract-command.outputs.action_verb || '' }}
618+ TOOL_OUTPUT : ${{ steps.run-tool.outputs.tool_output || '' }}
619+ EXTRACT_ERROR : ${{ steps.extract-command.outputs.error_message || '' }}
620+ TARGET_ERROR : ${{ steps.determine-target.outputs.error_message || '' }}
603621 with :
604622 script : |
605623 const comment_user = context.payload.comment.user.login;
606624 const workflow_run_url = `${context.serverUrl}/${context.repo.owner}/${context.repo.repo}/actions/runs/${context.runId}`;
607625 const actionVerb = process.env.ACTION_VERB || 'Operation';
608626 const toolOutput = process.env.TOOL_OUTPUT || '';
609627 const extractError = process.env.EXTRACT_ERROR || '';
628+ const targetError = process.env.TARGET_ERROR || '';
610629
611630 let body = `@${comment_user} ❌ ${actionVerb} failed.\n\n`;
612631
613632 // Check for errors from various steps
614- const targetError = process.env.TARGET_ERROR || '';
615633 const errorMessage = extractError || targetError;
616634 if (errorMessage) {
617635 body += `**Error:** ${errorMessage}\n\n`;
@@ -639,11 +657,6 @@ jobs:
639657 repo: context.repo.repo,
640658 body: body
641659 });
642- env :
643- ACTION_VERB : ${{ steps.extract-command.outputs.action_verb }}
644- TOOL_OUTPUT : ${{ steps.run-tool.outputs.tool_output }}
645- EXTRACT_ERROR : ${{ steps.extract-command.outputs.error_message }}
646- TARGET_ERROR : ${{ steps.determine-target.outputs.error_message }}
647660
648661 - name : Re-lock issue/PR if it was locked
649662 if : github.event.issue.locked == true && (success() || failure())
0 commit comments