diff --git a/pkg/workflow/samples_replay.go b/pkg/workflow/samples_replay.go index 76bf781de66..fd7a3b62131 100644 --- a/pkg/workflow/samples_replay.go +++ b/pkg/workflow/samples_replay.go @@ -4,8 +4,6 @@ import ( "encoding/json" "fmt" "strings" - - "github.com/github/gh-aw/pkg/sliceutil" ) // SampleEntry is the per-call payload consumed by apply_samples.cjs. @@ -30,7 +28,7 @@ func collectSampleEntries(config *SafeOutputsConfig) []SampleEntry { return nil } - fieldNames := sliceutil.SortedKeys(safeOutputFieldMapping) + fieldNames := getSortedSafeOutputFieldNames() var entries []SampleEntry for _, fieldName := range fieldNames { diff --git a/pkg/workflow/samples_validation.go b/pkg/workflow/samples_validation.go index a6ebb0008a2..9462b6c4a1d 100644 --- a/pkg/workflow/samples_validation.go +++ b/pkg/workflow/samples_validation.go @@ -61,6 +61,9 @@ var ( compiledToolSchemasOnce sync.Once compiledToolSchemas map[string]toolSchemaEntry compiledToolSchemasErr error + + sortedSafeOutputFieldNamesOnce sync.Once + sortedSafeOutputFieldNames []string ) func getCompiledToolSchemas() (map[string]toolSchemaEntry, error) { @@ -97,6 +100,13 @@ func getCompiledToolSchemas() (map[string]toolSchemaEntry, error) { return compiledToolSchemas, compiledToolSchemasErr } +func getSortedSafeOutputFieldNames() []string { + sortedSafeOutputFieldNamesOnce.Do(func() { + sortedSafeOutputFieldNames = sliceutil.SortedKeys(safeOutputFieldMapping) + }) + return sortedSafeOutputFieldNames +} + // validateSafeOutputsSamples validates every `samples` entry on every // enabled safe-output handler against the corresponding MCP tool's inputSchema. // Sample sidecar fields (e.g. `patch`) are stripped before validation. Returns @@ -107,7 +117,7 @@ func validateSafeOutputsSamples(config *SafeOutputsConfig) error { return nil } - fieldNames := sliceutil.SortedKeys(safeOutputFieldMapping) + fieldNames := getSortedSafeOutputFieldNames() samplesValidationLog.Printf("Validating safe-outputs samples across %d candidate fields", len(fieldNames)) for _, fieldName := range fieldNames { diff --git a/pkg/workflow/samples_validation_test.go b/pkg/workflow/samples_validation_test.go index a69ac2a6d28..546e09f9d8f 100644 --- a/pkg/workflow/samples_validation_test.go +++ b/pkg/workflow/samples_validation_test.go @@ -4,10 +4,25 @@ import ( "strings" "testing" + "github.com/github/gh-aw/pkg/sliceutil" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" ) +func TestGetSortedSafeOutputFieldNames_MatchesSortedKeys(t *testing.T) { + t.Parallel() + + expected := sliceutil.SortedKeys(safeOutputFieldMapping) + first := getSortedSafeOutputFieldNames() + second := getSortedSafeOutputFieldNames() + + assert.Equal(t, expected, first) + assert.Equal(t, expected, second) + if assert.NotEmpty(t, first) && assert.NotEmpty(t, second) { + assert.Equal(t, &first[0], &second[0], "expected cached field names to reuse the same backing slice") + } +} + // TestValidateSafeOutputsSamples_Valid covers the happy path for the // strict schema validation of samples entries. We use create_issue (no // sidecars, just title/body) and create_pull_request (with the `patch` sidecar