fix: safe-outputs types from imports dropped when main workflow defines any safe-outputs#25402
fix: safe-outputs types from imports dropped when main workflow defines any safe-outputs#25402
Conversation
…ent types The bug: extractSafeOutputsConfig auto-defaults threat-detection, noop, missing-tool, missing-data, and report-incomplete even when not explicitly in frontmatter. These auto-defaults polluted topDefinedTypes in MergeSafeOutputs, causing imported configurations for those types to be dropped. Fix 1 (MergeSafeOutputs): Accept topRawSafeOutputs map[string]any. When provided, only keys explicitly present in the raw frontmatter map count as "defined" — preventing auto-defaults from blocking import merges. Fix 2 (mergeSafeOutputConfig): For the 5 auto-defaultable types, use the raw import config map key presence as the merge signal instead of (or in addition to) result.X == nil checks. This prevents an auto-default in result from blocking an explicitly-configured value from an import. Add integration test TestSafeOutputsDifferentTypesFromImportsMerged that reproduces the exact scenario from the bug report. Agent-Logs-Url: https://github.com/github/gh-aw/sessions/0924a615-0379-4777-8e36-0b1a83b701dc Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
@copilot Add more test scenarios |
There was a problem hiding this comment.
Pull request overview
Fixes a safe-outputs merge regression where auto-defaulted safe-output types in the main workflow were incorrectly treated as user-defined, causing imported safe-output configurations (notably threat-detection) to be dropped during merge.
Changes:
- Updates
MergeSafeOutputsto accept the rawsafe-outputsfrontmatter map and use it to determine which types were explicitly defined in the main workflow. - Adjusts
mergeSafeOutputConfigso imported configs for auto-defaultable types can override auto-default structs in the merge result. - Adds an integration test reproducing the “main defines
noop, import definesthreat-detection” scenario.
Show a summary per file
| File | Description |
|---|---|
| pkg/workflow/imports.go | Implements raw-frontmatter-aware conflict detection and refines merge behavior for auto-defaultable safe-output types. |
| pkg/workflow/compiler_orchestrator_workflow.go | Passes raw frontmatter["safe-outputs"] into MergeSafeOutputs to avoid treating auto-defaults as user-defined. |
| pkg/workflow/safe_outputs_import_test.go | Updates call sites for the new MergeSafeOutputs signature and adds an integration regression test for the reported merge bug. |
| pkg/workflow/safe_outputs_fix_test.go | Updates unit test call site for the new MergeSafeOutputs signature. |
Copilot's findings
Tip
Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
Comments suppressed due to low confidence (1)
pkg/workflow/imports.go:213
- topDefinedTypes is only populated when topSafeOutputs != nil. If a caller passes topRawSafeOutputs (raw frontmatter) but topSafeOutputs is nil, explicit main-workflow keys in topRawSafeOutputs won’t be treated as defined and imports can incorrectly override them. Consider populating topDefinedTypes from topRawSafeOutputs regardless of whether topSafeOutputs is nil (and only fall back to hasSafeOutputType when topRawSafeOutputs is nil).
// Collect all safe output types defined in the top-level config.
// When topRawSafeOutputs is provided (from raw frontmatter), use only keys that are
// explicitly present in the raw map to avoid counting auto-defaults as user-defined types.
// When nil, fall back to inspecting the processed config struct (legacy/test behaviour).
topDefinedTypes := make(map[string]bool)
if topSafeOutputs != nil {
for _, key := range typeKeys {
if topRawSafeOutputs != nil {
if _, exists := topRawSafeOutputs[key]; exists {
topDefinedTypes[key] = true
}
} else if hasSafeOutputType(topSafeOutputs, key) {
topDefinedTypes[key] = true
}
}
}
importsLog.Printf("Top-level safe-outputs defines %d types", len(topDefinedTypes))
- Files reviewed: 4/4 changed files
- Comments generated: 2
| // Returns an error if a conflict is detected (same safe-output type defined in both main and imported) | ||
| func (c *Compiler) MergeSafeOutputs(topSafeOutputs *SafeOutputsConfig, importedSafeOutputsJSON []string) (*SafeOutputsConfig, error) { | ||
| // MergeSafeOutputs merges safe-outputs configurations from imports into the top-level safe-outputs. | ||
| // Returns an error if a conflict is detected (same safe-output type defined in both main and imported). |
There was a problem hiding this comment.
The doc comment says MergeSafeOutputs returns an error when a type is defined in both main and imported configs, but the implementation actually treats main workflow definitions as overriding imports (it deletes the imported key) and only errors when the same type is defined in multiple imports. Update the comment to match the real conflict/override behavior so callers aren’t misled.
| // Returns an error if a conflict is detected (same safe-output type defined in both main and imported). | |
| // Explicit safe-output types defined in the main workflow take precedence over imported definitions. | |
| // Returns an error only if the same safe-output type is defined by multiple imports. |
| // TestSafeOutputsDifferentTypesFromImportsMerged reproduces the bug reported in | ||
| // https://github.com/github/gh-aw/issues/<issue>: | ||
| // When the main workflow defines one safe-outputs type (e.g. noop) and an imported | ||
| // workflow defines a different type (e.g. threat-detection), the imported type should | ||
| // be merged into the compiled output. Previously the auto-default applied by | ||
| // extractSafeOutputsConfig (which enabled threat-detection by default whenever any | ||
| // safe-outputs were present) caused threat-detection to appear as "already defined" in | ||
| // topDefinedTypes, so the import's explicit threat-detection configuration was dropped. |
There was a problem hiding this comment.
The test comment links to an issue URL with a <issue> placeholder. Replace it with the actual issue number/URL or remove the link to avoid leaving a misleading reference in the codebase.
| // TestSafeOutputsDifferentTypesFromImportsMerged reproduces the bug reported in | |
| // https://github.com/github/gh-aw/issues/<issue>: | |
| // When the main workflow defines one safe-outputs type (e.g. noop) and an imported | |
| // workflow defines a different type (e.g. threat-detection), the imported type should | |
| // be merged into the compiled output. Previously the auto-default applied by | |
| // extractSafeOutputsConfig (which enabled threat-detection by default whenever any | |
| // safe-outputs were present) caused threat-detection to appear as "already defined" in | |
| // topDefinedTypes, so the import's explicit threat-detection configuration was dropped. | |
| // TestSafeOutputsDifferentTypesFromImportsMerged reproduces a bug where, when the main | |
| // workflow defines one safe-outputs type (e.g. noop) and an imported workflow defines a | |
| // different type (e.g. threat-detection), the imported type should be merged into the | |
| // compiled output. Previously the auto-default applied by extractSafeOutputsConfig | |
| // (which enabled threat-detection by default whenever any safe-outputs were present) | |
| // caused threat-detection to appear as "already defined" in topDefinedTypes, so the | |
| // import's explicit threat-detection configuration was dropped. |
Add three new integration tests covering the auto-defaultable types fix: 1. TestSafeOutputsAutoDefaultableTypesImportedWhenMainHasNone - all five auto-defaultable types (noop, missing-tool, missing-data, report-incomplete, threat-detection) imported with custom config when main has no safe-outputs 2. TestSafeOutputsMainExplicitAutoDefaultableTypeOverridesImport - main explicitly configures noop, import also defines noop → main wins; import's other types (missing-tool) still merge in 3. TestSafeOutputsMultipleImportsEachContributeAutoDefaultableType - three imports each contribute a different auto-defaultable type; no conflict, all merge alongside main's create-issue Agent-Logs-Url: https://github.com/github/gh-aw/sessions/e03dc446-5102-4a4b-823f-38784e0c1d8f Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Added three new integration tests in commit a62daa7:
|
|
Hey Checklist summary:
Verdict: 🟢 Aligned — no blocking issues found.
|
When a main workflow defines any
safe-outputstype (e.g.noop),extractSafeOutputsConfigauto-defaultsthreat-detection,noop,missing-tool,missing-data, andreport-incompleteeven for types not present in the raw frontmatter. This causedtopDefinedTypesinMergeSafeOutputsto treat those auto-defaults as user-defined, silently deleting conflicting keys from imports before merging — sothreat-detectionsteps from an import would disappear entirely.Root cause (two parts)
1.
MergeSafeOutputs—topDefinedTypespollutionhasSafeOutputType(topSafeOutputs, key)can't distinguish auto-defaults from explicit user config, so imported types are incorrectly deleted.2.
mergeSafeOutputConfig—result.X == nilguardEven when an import's key survives deletion, the
result.ThreatDetection != nil(auto-default empty struct) condition blocks the import's explicit config from being applied.Changes
MergeSafeOutputs— addstopRawSafeOutputs map[string]anyparameter. When provided, only keys explicitly present in the raw frontmatter map count as "defined" for conflict detection. All existing call sites passnilto preserve current unit-test behavior.mergeSafeOutputConfig— for the 5 auto-defaultable types (threat-detection,noop,missing-tool,missing-data,report-incomplete), uses raw import config map key presence as the authoritative merge signal instead ofresult.X == nil:compiler_orchestrator_workflow.go— passesfrontmatter["safe-outputs"]as the raw map toMergeSafeOutputs.TestSafeOutputsDifferentTypesFromImportsMerged— integration test reproducing the exact issue scenario: main definesnoop, import definesthreat-detectionwith custom steps; verifies both survive the merge.TestSafeOutputsAutoDefaultableTypesImportedWhenMainHasNone— verifies all five auto-defaultable types are imported with custom configuration when the main workflow has nosafe-outputssection.TestSafeOutputsMainExplicitAutoDefaultableTypeOverridesImport— verifies that when the main workflow explicitly configures an auto-defaultable type (e.g.noop), the import's value for the same type is overridden by main, while other types from the import still merge correctly.TestSafeOutputsMultipleImportsEachContributeAutoDefaultableType— verifies that multiple imports can each contribute a different auto-defaultable type without triggering false conflict errors, all merging alongside main's explicitly defined types.