Skip to content

Commit e44f4e7

Browse files
dsymeCopilot
andauthored
fix: propagate context through all CLI subprocess calls for Ctrl-C cancellation (#26372)
* fix: propagate context through all CLI subprocess calls for Ctrl-C cancellation Thread context.Context through the full call chain in pkg/cli/ so that Ctrl-C (via signal.NotifyContext in main.go) correctly terminates all background gh CLI subprocesses and git operations. Previously, many functions called workflow.RunGH / RunGHCombined (no context) or exec.Command (no context), so Ctrl-C had no effect on in-flight network operations like artifact downloads, log fetches, and workflow file downloads. Changes: - pkg/workflow/github_cli.go: add RunGHCombinedContext - pkg/cli/download_workflow.go: add ctx to downloadWorkflowContent, downloadWorkflowContentViaGit, downloadWorkflowContentViaGitClone; all exec.Command -> exec.CommandContext - pkg/cli/update_workflows.go: pass ctx to all downloadWorkflowContent calls - pkg/cli/update_actions.go: exec.Command(git ls-remote) -> exec.CommandContext - pkg/cli/audit.go: add ctx to fetchWorkflowRunMetadata, resolveWorkflowDisplayName, renderAuditReport - pkg/cli/audit_comparison.go: add ctx to findPreviousSuccessfulWorkflowRuns, buildAuditComparisonForRun - pkg/cli/audit_diff.go: add ctx to loadRunSummaryForDiff - pkg/cli/audit_diff_command.go: pass ctx to loadRunSummaryForDiff calls - pkg/cli/logs_download.go: add ctx to downloadWorkflowRunLogs, listRunArtifactNames, downloadArtifactsByName, retryCriticalArtifacts, downloadRunArtifacts; use ExecGHContext / RunGHContext - pkg/cli/logs_orchestrator.go: pass ctx to downloadRunArtifacts in pool - pkg/cli/trial_runner.go: add ctx to getCurrentGitHubUsername - All affected test files updated accordingly * fix: update_integration_test.go missing ctx for getLatestBranchCommitSHA * Update pkg/cli/update_actions.go Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * update * fix: use RunGHCombinedContext in findPreviousSuccessfulWorkflowRuns * fix: update_integration_test.go missing ctx for resolveLatestRef * fix: update_integration_test.go missing ctx for resolveLatestRef (SHA case) --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
1 parent 7934db7 commit e44f4e7

19 files changed

+180
-148
lines changed

pkg/cli/audit.go

Lines changed: 11 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -221,14 +221,14 @@ func AuditWorkflowRun(ctx context.Context, runID int64, owner, repo, hostname st
221221
// file reads (created items, aw_info, etc.) resolve correctly even if the run
222222
// directory has been moved or copied since the summary was first written.
223223
processedRun.Run.LogsPath = runOutputDir
224-
return renderAuditReport(processedRun, summary.Metrics, summary.MCPToolUsage, runOutputDir, owner, repo, hostname, verbose, parse, jsonOutput)
224+
return renderAuditReport(ctx, processedRun, summary.Metrics, summary.MCPToolUsage, runOutputDir, owner, repo, hostname, verbose, parse, jsonOutput)
225225
}
226226

227227
// Check if we have locally cached artifacts first
228228
hasLocalCache := fileutil.DirExists(runOutputDir) && !fileutil.IsDirEmpty(runOutputDir)
229229

230230
// Try to get run metadata from GitHub API
231-
run, metadataErr := fetchWorkflowRunMetadata(runID, owner, repo, hostname, verbose)
231+
run, metadataErr := fetchWorkflowRunMetadata(ctx, runID, owner, repo, hostname, verbose)
232232
var useLocalCache bool
233233

234234
if metadataErr != nil {
@@ -259,7 +259,7 @@ func AuditWorkflowRun(ctx context.Context, runID int64, owner, repo, hostname st
259259

260260
// Download artifacts for the run
261261
auditLog.Printf("Downloading artifacts for run %d", runID)
262-
err := downloadRunArtifacts(runID, runOutputDir, verbose, owner, repo, hostname, artifactFilter)
262+
err := downloadRunArtifacts(ctx, runID, runOutputDir, verbose, owner, repo, hostname, artifactFilter)
263263
if err != nil {
264264
// Gracefully handle cases where the run legitimately has no artifacts
265265
if errors.Is(err, ErrNoArtifacts) {
@@ -483,20 +483,20 @@ func AuditWorkflowRun(ctx context.Context, runID int64, owner, repo, hostname st
483483
}
484484
}
485485

486-
return renderAuditReport(processedRun, metrics, mcpToolUsage, runOutputDir, owner, repo, hostname, verbose, parse, jsonOutput)
486+
return renderAuditReport(ctx, processedRun, metrics, mcpToolUsage, runOutputDir, owner, repo, hostname, verbose, parse, jsonOutput)
487487
}
488488

489489
// renderAuditReport builds and renders the audit report from a fully-populated processedRun.
490490
// It is called both when serving from a cached run summary and after a fresh processing pass,
491491
// ensuring that the two paths produce identical output.
492-
func renderAuditReport(processedRun ProcessedRun, metrics LogMetrics, mcpToolUsage *MCPToolUsageData, runOutputDir string, owner, repo, hostname string, verbose bool, parse bool, jsonOutput bool) error {
492+
func renderAuditReport(ctx context.Context, processedRun ProcessedRun, metrics LogMetrics, mcpToolUsage *MCPToolUsageData, runOutputDir string, owner, repo, hostname string, verbose bool, parse bool, jsonOutput bool) error {
493493
runID := processedRun.Run.DatabaseID
494494

495495
currentCreatedItems := extractCreatedItemsFromManifest(runOutputDir)
496496
processedRun.Run.SafeItemsCount = len(currentCreatedItems)
497497

498498
currentSnapshot := buildAuditComparisonSnapshot(processedRun, currentCreatedItems)
499-
comparison := buildAuditComparisonForRun(processedRun, currentSnapshot, runOutputDir, owner, repo, hostname, verbose)
499+
comparison := buildAuditComparisonForRun(ctx, processedRun, currentSnapshot, runOutputDir, owner, repo, hostname, verbose)
500500

501501
// Build structured audit data
502502
auditData := buildAuditData(processedRun, metrics, mcpToolUsage)
@@ -752,7 +752,7 @@ func findFirstFailingStep(jobLog string) (int, string) {
752752
}
753753

754754
// fetchWorkflowRunMetadata fetches metadata for a single workflow run
755-
func fetchWorkflowRunMetadata(runID int64, owner, repo, hostname string, verbose bool) (WorkflowRun, error) {
755+
func fetchWorkflowRunMetadata(ctx context.Context, runID int64, owner, repo, hostname string, verbose bool) (WorkflowRun, error) {
756756
// Build the API endpoint
757757
var endpoint string
758758
if owner != "" && repo != "" {
@@ -780,7 +780,7 @@ func fetchWorkflowRunMetadata(runID int64, owner, repo, hostname string, verbose
780780
fmt.Fprintln(os.Stderr, console.FormatInfoMessage("Executing: gh "+strings.Join(args, " ")))
781781
}
782782

783-
output, err := workflow.RunGHCombined("Fetching run metadata...", args...)
783+
output, err := workflow.RunGHCombinedContext(ctx, "Fetching run metadata...", args...)
784784
if err != nil {
785785
if verbose {
786786
fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(string(output)))
@@ -807,7 +807,7 @@ func fetchWorkflowRunMetadata(runID int64, owner, repo, hostname string, verbose
807807
// that were cancelled or failed before any jobs started), resolve the actual workflow
808808
// display name so that audit output is consistent with 'gh aw logs'.
809809
if strings.HasPrefix(run.WorkflowName, ".github/") {
810-
if displayName := resolveWorkflowDisplayName(run.WorkflowPath, owner, repo, hostname); displayName != "" {
810+
if displayName := resolveWorkflowDisplayName(ctx, run.WorkflowPath, owner, repo, hostname); displayName != "" {
811811
auditLog.Printf("Resolved workflow display name: %q -> %q", run.WorkflowName, displayName)
812812
run.WorkflowName = displayName
813813
}
@@ -821,7 +821,7 @@ func fetchWorkflowRunMetadata(runID int64, owner, repo, hostname string, verbose
821821
// relative to the git repository root so that it works from any working directory inside
822822
// the repo); if that fails it falls back to a GitHub API call. An empty string is
823823
// returned on any error so that callers can gracefully keep the original value.
824-
func resolveWorkflowDisplayName(workflowPath, owner, repo, hostname string) string {
824+
func resolveWorkflowDisplayName(ctx context.Context, workflowPath, owner, repo, hostname string) string {
825825
// Try local file first. workflowPath is a repo-relative path like
826826
// ".github/workflows/foo.lock.yml", so we resolve it against the git root to
827827
// produce a correct absolute path regardless of the current working directory.
@@ -849,7 +849,7 @@ func resolveWorkflowDisplayName(workflowPath, owner, repo, hostname string) stri
849849
}
850850
args = append(args, endpoint, "--jq", ".name")
851851

852-
out, err := workflow.RunGHCombined("Fetching workflow name...", args...)
852+
out, err := workflow.RunGHCombinedContext(ctx, "Fetching workflow name...", args...)
853853
if err != nil {
854854
auditLog.Printf("Failed to fetch workflow display name for %q: %v", workflowPath, err)
855855
return ""

pkg/cli/audit_comparison.go

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cli
22

33
import (
4+
"context"
45
"encoding/json"
56
"fmt"
67
"net/url"
@@ -457,7 +458,7 @@ func collectMCPFailureServers(failures []MCPFailureReport) []string {
457458
return servers
458459
}
459460

460-
func findPreviousSuccessfulWorkflowRuns(current WorkflowRun, owner, repo, hostname string, verbose bool) ([]WorkflowRun, error) {
461+
func findPreviousSuccessfulWorkflowRuns(ctx context.Context, current WorkflowRun, owner, repo, hostname string, verbose bool) ([]WorkflowRun, error) {
461462
_ = verbose
462463
workflowID := filepath.Base(current.WorkflowPath)
463464
if workflowID == "." || workflowID == "" {
@@ -480,7 +481,7 @@ func findPreviousSuccessfulWorkflowRuns(current WorkflowRun, owner, repo, hostna
480481
}
481482
args = append(args, endpoint, "--jq", jq)
482483

483-
output, err := workflow.RunGHCombined("Fetching previous successful workflow run...", args...)
484+
output, err := workflow.RunGHCombinedContext(ctx, "Fetching previous successful workflow run...", args...)
484485
if err != nil {
485486
return nil, fmt.Errorf("failed to fetch previous successful workflow run: %w", err)
486487
}
@@ -497,7 +498,7 @@ func findPreviousSuccessfulWorkflowRuns(current WorkflowRun, owner, repo, hostna
497498

498499
for index := range runs {
499500
if strings.HasPrefix(runs[index].WorkflowName, ".github/") {
500-
if displayName := resolveWorkflowDisplayName(runs[index].WorkflowPath, owner, repo, hostname); displayName != "" {
501+
if displayName := resolveWorkflowDisplayName(ctx, runs[index].WorkflowPath, owner, repo, hostname); displayName != "" {
501502
runs[index].WorkflowName = displayName
502503
}
503504
}
@@ -506,8 +507,8 @@ func findPreviousSuccessfulWorkflowRuns(current WorkflowRun, owner, repo, hostna
506507
return runs, nil
507508
}
508509

509-
func buildAuditComparisonForRun(currentRun ProcessedRun, currentSnapshot auditComparisonSnapshot, outputDir string, owner, repo, hostname string, verbose bool) *AuditComparisonData {
510-
baselineRuns, err := findPreviousSuccessfulWorkflowRuns(currentRun.Run, owner, repo, hostname, verbose)
510+
func buildAuditComparisonForRun(ctx context.Context, currentRun ProcessedRun, currentSnapshot auditComparisonSnapshot, outputDir string, owner, repo, hostname string, verbose bool) *AuditComparisonData {
511+
baselineRuns, err := findPreviousSuccessfulWorkflowRuns(ctx, currentRun.Run, owner, repo, hostname, verbose)
511512
if err != nil {
512513
auditLog.Printf("Skipping audit comparison: failed to find baseline: %v", err)
513514
return &AuditComparisonData{BaselineFound: false}
@@ -520,7 +521,7 @@ func buildAuditComparisonForRun(currentRun ProcessedRun, currentSnapshot auditCo
520521
for _, baselineRun := range baselineRuns {
521522
baselineOutputDir := filepath.Join(outputDir, fmt.Sprintf("baseline-%d", baselineRun.DatabaseID))
522523
if _, err := os.Stat(baselineOutputDir); err != nil {
523-
if downloadErr := downloadRunArtifacts(baselineRun.DatabaseID, baselineOutputDir, verbose, owner, repo, hostname, nil); downloadErr != nil {
524+
if downloadErr := downloadRunArtifacts(ctx, baselineRun.DatabaseID, baselineOutputDir, verbose, owner, repo, hostname, nil); downloadErr != nil {
524525
auditLog.Printf("Skipping candidate baseline for run %d: failed to download baseline artifacts: %v", baselineRun.DatabaseID, downloadErr)
525526
continue
526527
}

pkg/cli/audit_diff.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cli
22

33
import (
4+
"context"
45
"errors"
56
"fmt"
67
"math"
@@ -681,7 +682,7 @@ func formatCountChange(count1, count2 int) string {
681682
// metrics); otherwise it downloads artifacts and analyzes firewall logs, returning a partial
682683
// summary with only FirewallAnalysis populated.
683684
// artifactFilter restricts which artifacts are downloaded; nil means download all.
684-
func loadRunSummaryForDiff(runID int64, outputDir string, owner, repo, hostname string, verbose bool, artifactFilter []string) (*RunSummary, error) {
685+
func loadRunSummaryForDiff(ctx context.Context, runID int64, outputDir string, owner, repo, hostname string, verbose bool, artifactFilter []string) (*RunSummary, error) {
685686
runOutputDir := filepath.Join(outputDir, fmt.Sprintf("run-%d", runID))
686687
if absDir, err := filepath.Abs(runOutputDir); err == nil {
687688
runOutputDir = absDir
@@ -694,7 +695,7 @@ func loadRunSummaryForDiff(runID int64, outputDir string, owner, repo, hostname
694695
}
695696

696697
// Download artifacts if needed
697-
if err := downloadRunArtifacts(runID, runOutputDir, verbose, owner, repo, hostname, artifactFilter); err != nil {
698+
if err := downloadRunArtifacts(ctx, runID, runOutputDir, verbose, owner, repo, hostname, artifactFilter); err != nil {
698699
if !errors.Is(err, ErrNoArtifacts) {
699700
return nil, fmt.Errorf("failed to download artifacts for run %d: %w", runID, err)
700701
}

pkg/cli/audit_diff_command.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -136,7 +136,7 @@ func RunAuditDiff(ctx context.Context, baseRunID int64, compareRunIDs []int64, o
136136

137137
// Load base run summary once (shared across all comparisons)
138138
fmt.Fprintln(os.Stderr, console.FormatProgressMessage(fmt.Sprintf("Loading data for base run %d...", baseRunID)))
139-
baseSummary, err := loadRunSummaryForDiff(baseRunID, outputDir, owner, repo, hostname, verbose, artifactFilter)
139+
baseSummary, err := loadRunSummaryForDiff(ctx, baseRunID, outputDir, owner, repo, hostname, verbose, artifactFilter)
140140
if err != nil {
141141
return fmt.Errorf("failed to load data for base run %d: %w", baseRunID, err)
142142
}
@@ -153,7 +153,7 @@ func RunAuditDiff(ctx context.Context, baseRunID int64, compareRunIDs []int64, o
153153
}
154154

155155
fmt.Fprintln(os.Stderr, console.FormatProgressMessage(fmt.Sprintf("Loading data for run %d...", compareRunID)))
156-
compareSummary, err := loadRunSummaryForDiff(compareRunID, outputDir, owner, repo, hostname, verbose, artifactFilter)
156+
compareSummary, err := loadRunSummaryForDiff(ctx, compareRunID, outputDir, owner, repo, hostname, verbose, artifactFilter)
157157
if err != nil {
158158
return fmt.Errorf("failed to load data for run %d: %w", compareRunID, err)
159159
}

pkg/cli/audit_test.go

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
package cli
44

55
import (
6+
"context"
67
"encoding/json"
78
"errors"
89
"fmt"
@@ -423,7 +424,7 @@ func TestAuditCachingBehavior(t *testing.T) {
423424
// Verify that downloadRunArtifacts skips download when valid summary exists
424425
// This is tested by checking that the function returns without error
425426
// and doesn't attempt to call `gh run download`
426-
err := downloadRunArtifacts(run.DatabaseID, runOutputDir, false, "", "", "", nil)
427+
err := downloadRunArtifacts(context.Background(), run.DatabaseID, runOutputDir, false, "", "", "", nil)
427428
if err != nil {
428429
t.Errorf("downloadRunArtifacts should skip download when valid summary exists, but got error: %v", err)
429430
}
@@ -579,7 +580,7 @@ func TestRenderAuditReportUsesProvidedMetrics(t *testing.T) {
579580
// renderAuditReport should complete without error even without GitHub API access.
580581
// No GitHub calls are made because WorkflowPath is empty, causing findPreviousSuccessfulWorkflowRuns
581582
// to return early with an error before any network requests are issued.
582-
err := renderAuditReport(processedRun, metrics, nil, runOutputDir, "", "", "", false, false, false)
583+
err := renderAuditReport(context.Background(), processedRun, metrics, nil, runOutputDir, "", "", "", false, false, false)
583584
if err != nil {
584585
t.Errorf("renderAuditReport returned unexpected error: %v", err)
585586
}

pkg/cli/download_workflow.go

Lines changed: 17 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package cli
22

33
import (
4+
"context"
45
"encoding/base64"
56
"fmt"
67
"os"
@@ -18,7 +19,7 @@ import (
1819
var downloadLog = logger.New("cli:download_workflow")
1920

2021
// downloadWorkflowContentViaGit downloads a workflow file using git archive
21-
func downloadWorkflowContentViaGit(repo, path, ref string, verbose bool) ([]byte, error) {
22+
func downloadWorkflowContentViaGit(ctx context.Context, repo, path, ref string, verbose bool) ([]byte, error) {
2223
if verbose {
2324
fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Fetching %s/%s@%s via git", repo, path, ref)))
2425
}
@@ -31,12 +32,12 @@ func downloadWorkflowContentViaGit(repo, path, ref string, verbose bool) ([]byte
3132

3233
// git archive command: git archive --remote=<repo> <ref> <path>
3334
// #nosec G204 -- repoURL, ref, and path are from workflow import configuration authored by the
34-
// developer; exec.Command with separate args (not shell execution) prevents shell injection.
35-
cmd := exec.Command("git", "archive", "--remote="+repoURL, ref, path)
35+
// developer; exec.CommandContext with separate args (not shell execution) prevents shell injection.
36+
cmd := exec.CommandContext(ctx, "git", "archive", "--remote="+repoURL, ref, path)
3637
archiveOutput, err := cmd.Output()
3738
if err != nil {
3839
// If git archive fails, try with git clone + read file as a fallback
39-
return downloadWorkflowContentViaGitClone(repo, path, ref, verbose)
40+
return downloadWorkflowContentViaGitClone(ctx, repo, path, ref, verbose)
4041
}
4142

4243
// Extract the file from the tar archive using Go's archive/tar (cross-platform)
@@ -53,7 +54,7 @@ func downloadWorkflowContentViaGit(repo, path, ref string, verbose bool) ([]byte
5354
}
5455

5556
// downloadWorkflowContentViaGitClone downloads a workflow file by shallow cloning with sparse checkout
56-
func downloadWorkflowContentViaGitClone(repo, path, ref string, verbose bool) ([]byte, error) {
57+
func downloadWorkflowContentViaGitClone(ctx context.Context, repo, path, ref string, verbose bool) ([]byte, error) {
5758
if verbose {
5859
fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Fetching %s/%s@%s via git clone", repo, path, ref)))
5960
}
@@ -71,19 +72,19 @@ func downloadWorkflowContentViaGitClone(repo, path, ref string, verbose bool) ([
7172
repoURL := fmt.Sprintf("%s/%s.git", githubHost, repo)
7273

7374
// Initialize git repository
74-
initCmd := exec.Command("git", "-C", tmpDir, "init")
75+
initCmd := exec.CommandContext(ctx, "git", "-C", tmpDir, "init")
7576
if output, err := initCmd.CombinedOutput(); err != nil {
7677
return nil, fmt.Errorf("failed to initialize git repository: %w\nOutput: %s", err, string(output))
7778
}
7879

7980
// Add remote
80-
remoteCmd := exec.Command("git", "-C", tmpDir, "remote", "add", "origin", repoURL)
81+
remoteCmd := exec.CommandContext(ctx, "git", "-C", tmpDir, "remote", "add", "origin", repoURL)
8182
if output, err := remoteCmd.CombinedOutput(); err != nil {
8283
return nil, fmt.Errorf("failed to add remote: %w\nOutput: %s", err, string(output))
8384
}
8485

8586
// Enable sparse-checkout
86-
sparseCmd := exec.Command("git", "-C", tmpDir, "config", "core.sparseCheckout", "true")
87+
sparseCmd := exec.CommandContext(ctx, "git", "-C", tmpDir, "config", "core.sparseCheckout", "true")
8788
if output, err := sparseCmd.CombinedOutput(); err != nil {
8889
return nil, fmt.Errorf("failed to enable sparse checkout: %w\nOutput: %s", err, string(output))
8990
}
@@ -109,29 +110,29 @@ func downloadWorkflowContentViaGitClone(repo, path, ref string, verbose bool) ([
109110
// Note: sparse-checkout with SHA refs may not reduce bandwidth as much as with branch refs,
110111
// because the server needs to send enough history to reach the specific commit.
111112
// However, it still limits the working directory to only the requested file.
112-
fetchCmd := exec.Command("git", "-C", tmpDir, "fetch", "--depth", "1", "origin", ref)
113+
fetchCmd := exec.CommandContext(ctx, "git", "-C", tmpDir, "fetch", "--depth", "1", "origin", ref)
113114
if _, err := fetchCmd.CombinedOutput(); err != nil {
114115
// If fetching specific SHA fails, try fetching all branches with depth 1
115-
fetchCmd = exec.Command("git", "-C", tmpDir, "fetch", "--depth", "1", "origin")
116+
fetchCmd = exec.CommandContext(ctx, "git", "-C", tmpDir, "fetch", "--depth", "1", "origin")
116117
if output, err := fetchCmd.CombinedOutput(); err != nil {
117118
return nil, fmt.Errorf("failed to fetch repository: %w\nOutput: %s", err, string(output))
118119
}
119120
}
120121

121122
// Checkout the specific commit
122-
checkoutCmd := exec.Command("git", "-C", tmpDir, "checkout", ref)
123+
checkoutCmd := exec.CommandContext(ctx, "git", "-C", tmpDir, "checkout", ref)
123124
if output, err := checkoutCmd.CombinedOutput(); err != nil {
124125
return nil, fmt.Errorf("failed to checkout commit %s: %w\nOutput: %s", ref, err, string(output))
125126
}
126127
} else {
127128
// For branch/tag refs, fetch the specific ref
128-
fetchCmd := exec.Command("git", "-C", tmpDir, "fetch", "--depth", "1", "origin", ref)
129+
fetchCmd := exec.CommandContext(ctx, "git", "-C", tmpDir, "fetch", "--depth", "1", "origin", ref)
129130
if output, err := fetchCmd.CombinedOutput(); err != nil {
130131
return nil, fmt.Errorf("failed to fetch ref %s: %w\nOutput: %s", ref, err, string(output))
131132
}
132133

133134
// Checkout FETCH_HEAD
134-
checkoutCmd := exec.Command("git", "-C", tmpDir, "checkout", "FETCH_HEAD")
135+
checkoutCmd := exec.CommandContext(ctx, "git", "-C", tmpDir, "checkout", "FETCH_HEAD")
135136
if output, err := checkoutCmd.CombinedOutput(); err != nil {
136137
return nil, fmt.Errorf("failed to checkout FETCH_HEAD: %w\nOutput: %s", err, string(output))
137138
}
@@ -155,21 +156,21 @@ func downloadWorkflowContentViaGitClone(repo, path, ref string, verbose bool) ([
155156
}
156157

157158
// downloadWorkflowContent downloads the content of a workflow file from GitHub
158-
func downloadWorkflowContent(repo, path, ref string, verbose bool) ([]byte, error) {
159+
func downloadWorkflowContent(ctx context.Context, repo, path, ref string, verbose bool) ([]byte, error) {
159160
downloadLog.Printf("Downloading workflow content: %s/%s@%s", repo, path, ref)
160161
if verbose {
161162
fmt.Fprintln(os.Stderr, console.FormatVerboseMessage(fmt.Sprintf("Fetching %s/%s@%s", repo, path, ref)))
162163
}
163164

164165
// Use gh CLI to download the file
165-
output, err := workflow.RunGHCombined("Downloading workflow...", "api", fmt.Sprintf("/repos/%s/contents/%s?ref=%s", repo, path, ref), "--jq", ".content")
166+
output, err := workflow.RunGHCombinedContext(ctx, "Downloading workflow...", "api", fmt.Sprintf("/repos/%s/contents/%s?ref=%s", repo, path, ref), "--jq", ".content")
166167
if err != nil {
167168
// Check if this is an authentication error
168169
outputStr := string(output)
169170
if gitutil.IsAuthError(outputStr) || gitutil.IsAuthError(err.Error()) {
170171
downloadLog.Printf("GitHub API authentication failed, attempting git fallback for %s/%s@%s", repo, path, ref)
171172
// Try fallback using git commands
172-
content, gitErr := downloadWorkflowContentViaGit(repo, path, ref, verbose)
173+
content, gitErr := downloadWorkflowContentViaGit(ctx, repo, path, ref, verbose)
173174
if gitErr != nil {
174175
return nil, fmt.Errorf("failed to fetch file content via GitHub API and git: API error: %w, Git error: %w", err, gitErr)
175176
}

0 commit comments

Comments
 (0)