From 4bdf154114db29d9794acc022e0130c36ddc76ea Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Jul 2026 20:50:11 +0000 Subject: [PATCH 1/3] fix: use stable dev-mode seed for auto-upgrade workflow schedule The FUZZY:WEEKLY schedule for agentic-auto-upgrade.yml was computed from `repoSlug + "/agentic-auto-upgrade"`. When `make recompile` passes `--schedule-seed github/gh-aw`, the slug is `github/gh-aw`. But when an agent or CI runs `gh aw compile` without `--schedule-seed`, the git remote URL is a localhost proxy that cannot be parsed as a GitHub slug, so the slug is empty. These two different seeds produce different cron schedules, causing the file to change on every build. Fix: mirror the pattern from schedule_preprocessing.go - in dev mode, use a fixed `dev/agentic-auto-upgrade` prefix that is independent of git remote detection. In release mode, keep the per-repo scattering using the repo slug. The new stable schedule in agentic-auto-upgrade.yml is `41 23 * * 1` (Monday at 23:41 UTC), computed from `dev/agentic-auto-upgrade`. Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic-auto-upgrade.yml | 2 +- pkg/workflow/auto_update_workflow.go | 34 ++++++++++----- pkg/workflow/auto_update_workflow_test.go | 49 ++++++++++++++++++++-- 3 files changed, 70 insertions(+), 15 deletions(-) diff --git a/.github/workflows/agentic-auto-upgrade.yml b/.github/workflows/agentic-auto-upgrade.yml index e169cea32b6..6aff9d33647 100644 --- a/.github/workflows/agentic-auto-upgrade.yml +++ b/.github/workflows/agentic-auto-upgrade.yml @@ -34,7 +34,7 @@ name: Agentic Auto-Upgrade on: schedule: - - cron: "11 4 * * 6" # Weekly (auto-upgrade) + - cron: "41 23 * * 1" # Weekly (auto-upgrade) workflow_dispatch: permissions: diff --git a/pkg/workflow/auto_update_workflow.go b/pkg/workflow/auto_update_workflow.go index 18e0949f925..debeb93362b 100644 --- a/pkg/workflow/auto_update_workflow.go +++ b/pkg/workflow/auto_update_workflow.go @@ -78,7 +78,12 @@ func GenerateAutoUpdateWorkflow(opts GenerateAutoUpdateWorkflowOptions) error { return nil } - seed := buildAutoUpdateSeed(opts.RepoSlug) + actionMode := opts.ActionMode + if actionMode == "" { + actionMode = ActionModeDev + } + + seed := buildAutoUpdateSeed(opts.RepoSlug, actionMode) cronSchedule, err := parser.ScatterSchedule("FUZZY:WEEKLY", seed) if err != nil { return fmt.Errorf("failed to scatter FUZZY:WEEKLY schedule for auto-update workflow: %w", err) @@ -94,10 +99,6 @@ func GenerateAutoUpdateWorkflow(opts GenerateAutoUpdateWorkflowOptions) error { githubScriptPin = getActionPin("actions/github-script") } - actionMode := opts.ActionMode - if actionMode == "" { - actionMode = ActionModeDev - } ctx := opts.Context if ctx == nil { ctx = context.Background() @@ -123,13 +124,24 @@ func GenerateAutoUpdateWorkflow(opts GenerateAutoUpdateWorkflowOptions) error { } // buildAutoUpdateSeed returns the deterministic seed string used to scatter the -// FUZZY:WEEKLY cron schedule. It combines the repo slug with the fixed workflow -// identifier so that repositories scatter to distinct time slots. -func buildAutoUpdateSeed(repoSlug string) string { - if repoSlug != "" { - return repoSlug + "/" + autoUpdateWorkflowIdentifier +// FUZZY:WEEKLY cron schedule. +// +// In release mode the repo slug is incorporated so that different repositories +// scatter to distinct time slots. In dev mode a stable "dev/" prefix is used +// instead of the slug, which may not be available (e.g. in sandbox environments +// where the git remote URL is a localhost proxy). This mirrors the behaviour of +// normalizeScheduleString in schedule_preprocessing.go and prevents the +// generated schedule from changing between dev builds when --schedule-seed is +// sometimes provided and sometimes not. +func buildAutoUpdateSeed(repoSlug string, actionMode ActionMode) string { + if actionMode.IsRelease() { + if repoSlug != "" { + return repoSlug + "/" + autoUpdateWorkflowIdentifier + } + return autoUpdateWorkflowIdentifier } - return autoUpdateWorkflowIdentifier + // Dev mode: use a fixed prefix that does not depend on git remote detection. + return "dev/" + autoUpdateWorkflowIdentifier } // buildAutoUpdateWorkflowYAML generates the YAML content for agentic-auto-upgrade.yml. diff --git a/pkg/workflow/auto_update_workflow_test.go b/pkg/workflow/auto_update_workflow_test.go index 60447fe1e8c..cf1fe049325 100644 --- a/pkg/workflow/auto_update_workflow_test.go +++ b/pkg/workflow/auto_update_workflow_test.go @@ -102,15 +102,18 @@ func TestGenerateAutoUpdateWorkflow_DifferentReposDifferentCron(t *testing.T) { dir1 := t.TempDir() dir2 := t.TempDir() + // Per-repo schedule scattering only applies in release mode. require.NoError(t, GenerateAutoUpdateWorkflow(GenerateAutoUpdateWorkflowOptions{ WorkflowDir: dir1, Enabled: true, RepoSlug: "org1/repo-alpha", + ActionMode: ActionModeRelease, })) require.NoError(t, GenerateAutoUpdateWorkflow(GenerateAutoUpdateWorkflowOptions{ WorkflowDir: dir2, Enabled: true, RepoSlug: "org2/repo-beta", + ActionMode: ActionModeRelease, })) data1, err := os.ReadFile(filepath.Join(dir1, AutoUpdateWorkflowFileName)) @@ -125,7 +128,7 @@ func TestGenerateAutoUpdateWorkflow_DifferentReposDifferentCron(t *testing.T) { assert.NotEmpty(t, cron2, "cron should be non-empty for org2/repo-beta") // Schedules are scattered by hash — different repos should typically differ. // This is a best-effort check; hash collisions are possible but unlikely for these slugs. - assert.NotEqual(t, cron1, cron2, "different repo slugs should produce different cron schedules") + assert.NotEqual(t, cron1, cron2, "different repo slugs should produce different cron schedules in release mode") } func TestGenerateAutoUpdateWorkflow_NoRepoSlug(t *testing.T) { @@ -180,9 +183,49 @@ func TestGenerateAutoUpdateWorkflow_ReleaseModeUsesGhAwPrefix(t *testing.T) { assert.NotContains(t, string(content), "Build gh-aw", "should not build gh-aw from source in release mode") } +func TestGenerateAutoUpdateWorkflow_DevModeScheduleStable(t *testing.T) { + // Dev mode must produce the same schedule regardless of whether --schedule-seed / + // a repo slug is provided. This is the key stability property: agents and CI that + // call `gh aw compile` without --schedule-seed should not cause agentic-auto-upgrade.yml + // to differ from a build that passes --schedule-seed. + dir1 := t.TempDir() + dir2 := t.TempDir() + + require.NoError(t, GenerateAutoUpdateWorkflow(GenerateAutoUpdateWorkflowOptions{ + WorkflowDir: dir1, + Enabled: true, + RepoSlug: "github/gh-aw", // simulates --schedule-seed github/gh-aw + ActionMode: ActionModeDev, + })) + require.NoError(t, GenerateAutoUpdateWorkflow(GenerateAutoUpdateWorkflowOptions{ + WorkflowDir: dir2, + Enabled: true, + RepoSlug: "", // simulates sandbox/CI where git remote detection yields empty slug + ActionMode: ActionModeDev, + })) + + data1, err := os.ReadFile(filepath.Join(dir1, AutoUpdateWorkflowFileName)) + require.NoError(t, err) + data2, err := os.ReadFile(filepath.Join(dir2, AutoUpdateWorkflowFileName)) + require.NoError(t, err) + + cron1 := extractCronLine(string(data1)) + cron2 := extractCronLine(string(data2)) + assert.NotEmpty(t, cron1, "cron should be present with slug in dev mode") + assert.NotEmpty(t, cron2, "cron should be present without slug in dev mode") + assert.Equal(t, cron1, cron2, "dev mode schedule must be identical regardless of repo slug") +} + func TestBuildAutoUpdateSeed(t *testing.T) { - assert.Equal(t, "owner/repo/agentic-auto-upgrade", buildAutoUpdateSeed("owner/repo")) - assert.Equal(t, "agentic-auto-upgrade", buildAutoUpdateSeed("")) + // Release mode: incorporates repo slug for per-repo scattering. + assert.Equal(t, "owner/repo/agentic-auto-upgrade", buildAutoUpdateSeed("owner/repo", ActionModeRelease)) + assert.Equal(t, "agentic-auto-upgrade", buildAutoUpdateSeed("", ActionModeRelease)) + + // Dev mode: always uses the stable "dev/" prefix regardless of repo slug, + // so that the schedule does not change depending on whether --schedule-seed + // is passed or whether git remote detection succeeds. + assert.Equal(t, "dev/agentic-auto-upgrade", buildAutoUpdateSeed("owner/repo", ActionModeDev)) + assert.Equal(t, "dev/agentic-auto-upgrade", buildAutoUpdateSeed("", ActionModeDev)) } // extractCronLine returns the cron expression from the first `- cron:` line in the YAML. From 818d2e5e7c4d402ddb2ec7d0cabae4ba0dd81346 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Jul 2026 22:41:00 +0000 Subject: [PATCH 2/3] fix: use IsDev() in buildAutoUpdateSeed so ActionModeAction gets per-repo scattering MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Released binaries auto-detect ActionModeAction, not ActionModeRelease. The previous IsRelease() check caused ActionModeAction builds to silently fall through to the dev branch and use the same "dev/agentic-auto-upgrade" seed in every repository, losing intended per-repository schedule scattering. Fix: only ActionModeDev uses the stable "dev/" prefix; all other modes (release, action, script) incorporate the repo slug for per-repo scattering. Also resolves the merge conflict in agentic-auto-upgrade.yml by regenerating via make recompile (stable dev-mode seed "dev/agentic-auto-upgrade" → 41 23 * * 1). Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --- .github/workflows/agentic-auto-upgrade.yml | 4 --- pkg/workflow/auto_update_workflow.go | 32 ++++++++++++---------- pkg/workflow/auto_update_workflow_test.go | 17 +++++++++--- 3 files changed, 31 insertions(+), 22 deletions(-) diff --git a/.github/workflows/agentic-auto-upgrade.yml b/.github/workflows/agentic-auto-upgrade.yml index c2cae7cfac9..6aff9d33647 100644 --- a/.github/workflows/agentic-auto-upgrade.yml +++ b/.github/workflows/agentic-auto-upgrade.yml @@ -34,11 +34,7 @@ name: Agentic Auto-Upgrade on: schedule: -<<<<<<< HEAD - cron: "41 23 * * 1" # Weekly (auto-upgrade) -======= - - cron: "21 3 * * 5" # Weekly (auto-upgrade) ->>>>>>> origin/main workflow_dispatch: permissions: diff --git a/pkg/workflow/auto_update_workflow.go b/pkg/workflow/auto_update_workflow.go index debeb93362b..ceca0c5606a 100644 --- a/pkg/workflow/auto_update_workflow.go +++ b/pkg/workflow/auto_update_workflow.go @@ -126,22 +126,26 @@ func GenerateAutoUpdateWorkflow(opts GenerateAutoUpdateWorkflowOptions) error { // buildAutoUpdateSeed returns the deterministic seed string used to scatter the // FUZZY:WEEKLY cron schedule. // -// In release mode the repo slug is incorporated so that different repositories -// scatter to distinct time slots. In dev mode a stable "dev/" prefix is used -// instead of the slug, which may not be available (e.g. in sandbox environments -// where the git remote URL is a localhost proxy). This mirrors the behaviour of -// normalizeScheduleString in schedule_preprocessing.go and prevents the -// generated schedule from changing between dev builds when --schedule-seed is -// sometimes provided and sometimes not. +// In dev mode a stable "dev/" prefix is used instead of the slug, which may not +// be available (e.g. in sandbox environments where the git remote URL is a +// localhost proxy). This mirrors the behaviour of normalizeScheduleString in +// schedule_preprocessing.go and prevents the generated schedule from changing +// between dev builds when --schedule-seed is sometimes provided and sometimes not. +// +// In all other modes (release, action, script) the repo slug is incorporated so +// that different repositories scatter to distinct time slots. Note: released +// binaries auto-detect ActionModeAction, not ActionModeRelease, so checking only +// IsRelease() would cause ActionModeAction builds to silently use the dev seed. func buildAutoUpdateSeed(repoSlug string, actionMode ActionMode) string { - if actionMode.IsRelease() { - if repoSlug != "" { - return repoSlug + "/" + autoUpdateWorkflowIdentifier - } - return autoUpdateWorkflowIdentifier + if actionMode.IsDev() { + // Dev mode: use a fixed prefix that does not depend on git remote detection. + return "dev/" + autoUpdateWorkflowIdentifier + } + // Release/action/script mode: incorporate repo slug for per-repo scattering. + if repoSlug != "" { + return repoSlug + "/" + autoUpdateWorkflowIdentifier } - // Dev mode: use a fixed prefix that does not depend on git remote detection. - return "dev/" + autoUpdateWorkflowIdentifier + return autoUpdateWorkflowIdentifier } // buildAutoUpdateWorkflowYAML generates the YAML content for agentic-auto-upgrade.yml. diff --git a/pkg/workflow/auto_update_workflow_test.go b/pkg/workflow/auto_update_workflow_test.go index cf1fe049325..2773ca75fb1 100644 --- a/pkg/workflow/auto_update_workflow_test.go +++ b/pkg/workflow/auto_update_workflow_test.go @@ -102,18 +102,19 @@ func TestGenerateAutoUpdateWorkflow_DifferentReposDifferentCron(t *testing.T) { dir1 := t.TempDir() dir2 := t.TempDir() - // Per-repo schedule scattering only applies in release mode. + // Per-repo schedule scattering applies in all non-dev modes. + // Use ActionModeAction since that is what released binaries actually detect. require.NoError(t, GenerateAutoUpdateWorkflow(GenerateAutoUpdateWorkflowOptions{ WorkflowDir: dir1, Enabled: true, RepoSlug: "org1/repo-alpha", - ActionMode: ActionModeRelease, + ActionMode: ActionModeAction, })) require.NoError(t, GenerateAutoUpdateWorkflow(GenerateAutoUpdateWorkflowOptions{ WorkflowDir: dir2, Enabled: true, RepoSlug: "org2/repo-beta", - ActionMode: ActionModeRelease, + ActionMode: ActionModeAction, })) data1, err := os.ReadFile(filepath.Join(dir1, AutoUpdateWorkflowFileName)) @@ -128,7 +129,7 @@ func TestGenerateAutoUpdateWorkflow_DifferentReposDifferentCron(t *testing.T) { assert.NotEmpty(t, cron2, "cron should be non-empty for org2/repo-beta") // Schedules are scattered by hash — different repos should typically differ. // This is a best-effort check; hash collisions are possible but unlikely for these slugs. - assert.NotEqual(t, cron1, cron2, "different repo slugs should produce different cron schedules in release mode") + assert.NotEqual(t, cron1, cron2, "different repo slugs should produce different cron schedules in action mode") } func TestGenerateAutoUpdateWorkflow_NoRepoSlug(t *testing.T) { @@ -221,6 +222,14 @@ func TestBuildAutoUpdateSeed(t *testing.T) { assert.Equal(t, "owner/repo/agentic-auto-upgrade", buildAutoUpdateSeed("owner/repo", ActionModeRelease)) assert.Equal(t, "agentic-auto-upgrade", buildAutoUpdateSeed("", ActionModeRelease)) + // Action mode (used by released binaries): also incorporates repo slug for per-repo scattering. + assert.Equal(t, "owner/repo/agentic-auto-upgrade", buildAutoUpdateSeed("owner/repo", ActionModeAction)) + assert.Equal(t, "agentic-auto-upgrade", buildAutoUpdateSeed("", ActionModeAction)) + + // Script mode: also incorporates repo slug for per-repo scattering. + assert.Equal(t, "owner/repo/agentic-auto-upgrade", buildAutoUpdateSeed("owner/repo", ActionModeScript)) + assert.Equal(t, "agentic-auto-upgrade", buildAutoUpdateSeed("", ActionModeScript)) + // Dev mode: always uses the stable "dev/" prefix regardless of repo slug, // so that the schedule does not change depending on whether --schedule-seed // is passed or whether git remote detection succeeds. From 46ce83318bb44791b6dffff4790a2759f1825fbd Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 18 Jul 2026 22:56:39 +0000 Subject: [PATCH 3/3] fix: use DetectActionMode fallback instead of hardcoded ActionModeDev When ActionMode is not explicitly set, use DetectActionMode(opts.Version) so the auto-detection logic (build flags, GitHub Actions context) is used, matching the codebase pattern throughout compiler_types.go and cli/. The old hardcoded ActionModeDev fallback silently produced the wrong dev-mode seed for callers that hadn't set ActionMode. Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com> --- pkg/workflow/auto_update_workflow.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/workflow/auto_update_workflow.go b/pkg/workflow/auto_update_workflow.go index ceca0c5606a..77c340137c5 100644 --- a/pkg/workflow/auto_update_workflow.go +++ b/pkg/workflow/auto_update_workflow.go @@ -80,7 +80,7 @@ func GenerateAutoUpdateWorkflow(opts GenerateAutoUpdateWorkflowOptions) error { actionMode := opts.ActionMode if actionMode == "" { - actionMode = ActionModeDev + actionMode = DetectActionMode(opts.Version) } seed := buildAutoUpdateSeed(opts.RepoSlug, actionMode)