From 0a7e31dcafd09605279fe929e4d09715632a64d9 Mon Sep 17 00:00:00 2001 From: fullsend-code <278716306+fullsend-ai-coder[bot]@users.noreply.github.com> Date: Mon, 31 Aug 2026 20:35:54 +0000 Subject: [PATCH] fix(#6824): fall back to main when agents repo version tag is missing When a release build resolves agents from fullsend-ai/agents using a version-tagged ref (e.g. tags/v0.38.0) and that tag does not exist, fetchPinnedAgentsRepoFile now retries with heads/main instead of failing immediately. This prevents auto-triggered dispatches from failing with a 404 when the agents repo has not yet been tagged for the running CLI version. The fallback logs a warning so the missing tag is visible in run output. Dev builds (which already use heads/main) are unaffected. Note: pre-commit could not run in this sandbox (network restrictions blocked hook repo fetching). gofmt and go vet were run directly and passed. Closes #6824 --- internal/cli/run.go | 18 +++++++- internal/cli/run_test.go | 94 ++++++++++++++++++++++++++++++++++++++++ 2 files changed, 110 insertions(+), 2 deletions(-) diff --git a/internal/cli/run.go b/internal/cli/run.go index 975eb8442..7cca6d8c6 100644 --- a/internal/cli/run.go +++ b/internal/cli/run.go @@ -4803,8 +4803,22 @@ func fetchPinnedAgentsRepoFile(ctx context.Context, relPath string, forgeClient displayRef, gitRef := resolveAgentsRef() resolvedSHA, err := forgeClient.GetRef(ctx, defaultAgentsRepoOwner, defaultAgentsRepoName, gitRef) if err != nil { - printer.StepWarn(fmt.Sprintf("Could not resolve %s/%s@%s: %v", defaultAgentsRepoOwner, defaultAgentsRepoName, displayRef, err)) - return "", none, false + // When a version-tagged ref (e.g. tags/v0.38.0) is missing, + // fall back to heads/main so release builds whose tag has not + // been published in the agents repo can still resolve agents. + if strings.HasPrefix(gitRef, "tags/") { + printer.StepWarn(fmt.Sprintf("Could not resolve %s/%s@%s: %v — falling back to main", defaultAgentsRepoOwner, defaultAgentsRepoName, displayRef, err)) + fallbackSHA, fbErr := forgeClient.GetRef(ctx, defaultAgentsRepoOwner, defaultAgentsRepoName, "heads/main") + if fbErr != nil { + printer.StepWarn(fmt.Sprintf("Fallback to main also failed: %v", fbErr)) + return "", none, false + } + resolvedSHA = fallbackSHA + displayRef = "main (fallback from " + displayRef + ")" + } else { + printer.StepWarn(fmt.Sprintf("Could not resolve %s/%s@%s: %v", defaultAgentsRepoOwner, defaultAgentsRepoName, displayRef, err)) + return "", none, false + } } if !commitSHAPattern.MatchString(resolvedSHA) { printer.StepWarn(fmt.Sprintf("Invalid SHA from %s/%s@%s: %q", defaultAgentsRepoOwner, defaultAgentsRepoName, displayRef, resolvedSHA)) diff --git a/internal/cli/run_test.go b/internal/cli/run_test.go index d54035534..30800577f 100644 --- a/internal/cli/run_test.go +++ b/internal/cli/run_test.go @@ -1441,6 +1441,100 @@ func TestTryAgentsRepoFallback_SuccessPath_ReleaseBuild(t *testing.T) { assert.Contains(t, deps[0].URL, fakeSHA) } +func TestTryAgentsRepoFallback_ReleaseBuild_MissingTagFallsBackToMain(t *testing.T) { + origVersion := version + origSHA := commitSHA + version = "0.38.0" + commitSHA = "abc123def456" + t.Cleanup(func() { version = origVersion; commitSHA = origSHA }) + + harnessContent := []byte("agent: agents/fix.md\nrole: test\n") + mainSHA := "1234567890abcdef1234567890abcdef12345678" + + srv := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + expectedPath := "/" + mainSHA + "/harness/fix.yaml" + if r.URL.Path == expectedPath { + w.WriteHeader(http.StatusOK) + _, _ = w.Write(harnessContent) + } else { + w.WriteHeader(http.StatusNotFound) + } + })) + t.Cleanup(srv.Close) + + hostPort := strings.TrimPrefix(srv.URL, "https://") + hostname, port, _ := net.SplitHostPort(hostPort) + + tlsCfg := srv.TLS.Clone() + tlsCfg.InsecureSkipVerify = true + policy := fetch.NewTestPolicy(tlsCfg, []string{hostname}, []string{port}) + + orig := defaultAgentsRepoURLPrefix + defaultAgentsRepoURLPrefix = srv.URL + "/" + t.Cleanup(func() { defaultAgentsRepoURLPrefix = orig }) + + workDir := t.TempDir() + + fakeClient := forge.NewFakeClient() + // No tags/v0.38.0 ref — only heads/main exists + fakeClient.Refs["fullsend-ai/agents/heads/main"] = mainSHA + + var buf bytes.Buffer + printer := ui.New(&buf) + opts := harness.ComposeOpts{ + WorkspaceRoot: workDir, + FetchPolicy: policy, + OrgAllowlist: []string{srv.URL + "/"}, + } + + path, deps, ok := tryAgentsRepoFallback(context.Background(), "fix", fakeClient, opts, printer) + require.True(t, ok, "expected tag-missing fallback to main to succeed") + assert.NotEmpty(t, path) + assert.Len(t, deps, 1) + assert.Contains(t, deps[0].URL, mainSHA) + assert.Contains(t, buf.String(), "falling back to main") +} + +func TestTryAgentsRepoFallback_ReleaseBuild_MissingTagAndMainFails(t *testing.T) { + origVersion := version + origSHA := commitSHA + version = "0.38.0" + commitSHA = "abc123def456" + t.Cleanup(func() { version = origVersion; commitSHA = origSHA }) + + // No refs at all — both tag and main will fail + fakeClient := forge.NewFakeClient() + + var buf bytes.Buffer + printer := ui.New(&buf) + + _, _, ok := tryAgentsRepoFallback(context.Background(), "fix", fakeClient, harness.ComposeOpts{}, printer) + assert.False(t, ok) + assert.Contains(t, buf.String(), "falling back to main") + assert.Contains(t, buf.String(), "Fallback to main also failed") +} + +func TestTryAgentsRepoFallback_DevBuild_NoFallback(t *testing.T) { + origVersion := version + origSHA := commitSHA + version = "dev" + commitSHA = "dev" + t.Cleanup(func() { version = origVersion; commitSHA = origSHA }) + + // Dev builds use heads/main directly; no tag-fallback path should + // be exercised. A missing heads/main ref should fail without retrying. + fakeClient := forge.NewFakeClient() + + var buf bytes.Buffer + printer := ui.New(&buf) + + _, _, ok := tryAgentsRepoFallback(context.Background(), "triage", fakeClient, harness.ComposeOpts{}, printer) + assert.False(t, ok) + // The warning should NOT mention "falling back to main" because + // the ref was already heads/main. + assert.NotContains(t, buf.String(), "falling back to main") +} + func TestTryAgentsRepoMeasurementManifest_Success(t *testing.T) { manifest := []byte("agent: triage\nmeasurements:\n - id: em-001\n scorer: trace_fitness\n version: 1\n") fakeSHA := "abcdef1234567890abcdef1234567890abcdef12"