From 76edcad488ddbb134284ad6c58aa7c5ad62dc921 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 15:26:29 +0000 Subject: [PATCH 1/2] Initial plan From 9d88c4fec21ed70b20d24098c5f9bfed14f03649 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 23 Feb 2026 15:45:59 +0000 Subject: [PATCH 2/2] fix: stabilize compilation output ordering for import processing and job dependencies - Add TestImportTopologicalSortStableAcrossRuns regression test from PR #17925 - Sort getCustomJobsDependingOnPreActivation result for deterministic ordering - Sort getReferencedCustomJobs result for deterministic ordering - Fix buildMainJob to iterate data.Jobs in sorted key order when building depends Fixes #17923 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- pkg/parser/import_topological_test.go | 45 ++++++++++++++++++++++++ pkg/workflow/compiler_activation_jobs.go | 6 ++++ pkg/workflow/compiler_jobs.go | 3 ++ 3 files changed, 54 insertions(+) diff --git a/pkg/parser/import_topological_test.go b/pkg/parser/import_topological_test.go index fbee2ef43b..23e8724acc 100644 --- a/pkg/parser/import_topological_test.go +++ b/pkg/parser/import_topological_test.go @@ -454,3 +454,48 @@ tools: assert.Equal(t, "a-dependency.md", result.ImportedFiles[0]) assert.Equal(t, "z-parent.md", result.ImportedFiles[1]) } + +func TestImportTopologicalSortStableAcrossRuns(t *testing.T) { + tempDir := testutil.TempDir(t, "import-topo-stable-*") + + files := map[string]string{ + "root.md": `--- +imports: + - b.md + - a.md +---`, + "a.md": `--- +imports: + - c.md +tools: + a-tool: {} +---`, + "b.md": `--- +tools: + b-tool: {} +---`, + "c.md": `--- +tools: + c-tool: {} +---`, + } + + for filename, content := range files { + filePath := filepath.Join(tempDir, filename) + err := os.WriteFile(filePath, []byte(content), 0644) + require.NoError(t, err) + } + + frontmatter := map[string]any{"imports": []string{"root.md"}} + + var baseline []string + for i := range 5 { + result, err := parser.ProcessImportsFromFrontmatterWithManifest(frontmatter, tempDir, nil) + require.NoError(t, err) + if i == 0 { + baseline = append([]string(nil), result.ImportedFiles...) + continue + } + assert.Equal(t, baseline, result.ImportedFiles) + } +} diff --git a/pkg/workflow/compiler_activation_jobs.go b/pkg/workflow/compiler_activation_jobs.go index 0c242550a2..e2b3f7502a 100644 --- a/pkg/workflow/compiler_activation_jobs.go +++ b/pkg/workflow/compiler_activation_jobs.go @@ -6,6 +6,7 @@ import ( "fmt" "maps" "slices" + "sort" "strconv" "strings" @@ -801,7 +802,12 @@ func (c *Compiler) buildMainJob(data *WorkflowData, activationJobCreated bool) ( // so the agent job gets them transitively through activation // Custom jobs that depend on agent should run AFTER the agent job, not before it if data.Jobs != nil { + jobNames := make([]string, 0, len(data.Jobs)) for jobName := range data.Jobs { + jobNames = append(jobNames, jobName) + } + sort.Strings(jobNames) + for _, jobName := range jobNames { // Skip jobs.pre-activation (or pre_activation) as it's handled specially if jobName == string(constants.PreActivationJobName) || jobName == "pre-activation" { continue diff --git a/pkg/workflow/compiler_jobs.go b/pkg/workflow/compiler_jobs.go index a89bafea18..7524b4434e 100644 --- a/pkg/workflow/compiler_jobs.go +++ b/pkg/workflow/compiler_jobs.go @@ -4,6 +4,7 @@ import ( "fmt" "os" "path/filepath" + "sort" "strings" "github.com/github/gh-aw/pkg/stringutil" @@ -115,6 +116,7 @@ func (c *Compiler) getCustomJobsDependingOnPreActivation(customJobs map[string]a } return false }) + sort.Strings(deps) compilerJobsLog.Printf("Found %d custom jobs depending on pre_activation: %v", len(deps), deps) return deps } @@ -133,6 +135,7 @@ func (c *Compiler) getReferencedCustomJobs(content string, customJobs map[string refs := sliceutil.FilterMapKeys(customJobs, func(jobName string, _ any) bool { return strings.Contains(content, fmt.Sprintf("needs.%s.", jobName)) }) + sort.Strings(refs) if len(refs) > 0 { compilerJobsLog.Printf("Found %d custom job references: %v", len(refs), refs) }