Skip to content

Extract shared SafeOutputAllowBlockConfig across safe-output handlers#42322

Merged
pelikhan merged 5 commits into
mainfrom
copilot/deep-report-quick-win-extract-safeoutputallowblock
Jun 29, 2026
Merged

Extract shared SafeOutputAllowBlockConfig across safe-output handlers#42322
pelikhan merged 5 commits into
mainfrom
copilot/deep-report-quick-win-extract-safeoutputallowblock

Conversation

Copilot AI commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Several safe-output handler configs were duplicating the same allowed / blocked fields despite already following an embedded shared-config pattern for other common fields. This change extracts that pair into a single shared config and reuses it across the affected handlers without changing the external config shape.

  • Shared allow/block config

    • Added SafeOutputAllowBlockConfig in pkg/workflow/safe_outputs_parser.go
    • Centralizes the canonical YAML definition for:
      • allowed []string
      • blocked []string
  • Embedded in affected safe-output configs

    • Replaced per-struct field duplication with inline embedding in:
      • AddLabelsConfig
      • RemoveLabelsConfig
      • AssignToUserConfig
      • UnassignFromUserConfig
      • SetIssueTypeConfig
      • AssignMilestoneConfig
    • Reused the same shared config in ListJobConfig to avoid leaving a second local copy behind
  • Test fixture updates

    • Updated composite literals in workflow and CLI tests to initialize the new embedded config explicitly
    • Keeps coverage aligned with the refactor while preserving existing behavior and serialized output
  • Example

    type AddLabelsConfig struct {
    	BaseSafeOutputConfig       `yaml:",inline"`
    	SafeOutputTargetConfig     `yaml:",inline"`
    	SafeOutputFilterConfig     `yaml:",inline"`
    	SafeOutputAllowBlockConfig `yaml:",inline"`
    }
    AddLabels: &AddLabelsConfig{
    	SafeOutputAllowBlockConfig: SafeOutputAllowBlockConfig{
    		Allowed: []string{"bug", "enhancement"},
    		Blocked: []string{"~spam"},
    	},
    }

Generated by 👨‍🍳 PR Sous Chef · 172.6 AIC · ⌖ 10.9 AIC · ⊞ 6.7K ·

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Extract shared SafeOutputAllowBlockConfig across safe-output configs Extract shared SafeOutputAllowBlockConfig across safe-output handlers Jun 29, 2026
Copilot AI requested a review from pelikhan June 29, 2026 19:15
@pelikhan pelikhan marked this pull request as ready for review June 29, 2026 19:39
Copilot AI review requested due to automatic review settings June 29, 2026 19:39
@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Test Quality Sentinel completed test quality analysis.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

PR Code Quality Reviewer completed the code quality review.

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

🧠 Matt Pocock Skills Reviewer has completed the skills-based review. ✅

@github-actions

github-actions Bot commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Design Decision Gate 🏗️ completed the design decision gate check.

No ADR enforcement needed: PR does not have the 'implementation' label and has ≤100 new lines of code in business logic directories (99 additions, threshold is 100).

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR refactors safe-output handler configuration structs to eliminate duplication of the allowed / blocked YAML fields by introducing a shared embedded config type, then updates existing tests to construct the new embedded config explicitly.

Changes:

  • Added SafeOutputAllowBlockConfig to centralize the canonical YAML definition for allowed and blocked.
  • Replaced duplicated per-handler Allowed/Blocked fields with inline embedding across several safe-output handler config structs (and ListJobConfig).
  • Updated workflow/compiler/CLI tests to initialize the embedded config in composite literals.
Show a summary per file
File Description
pkg/workflow/safe_outputs_parser.go Introduces SafeOutputAllowBlockConfig and embeds it into ListJobConfig.
pkg/workflow/add_labels.go Embeds the shared allow/block config into AddLabelsConfig.
pkg/workflow/remove_labels.go Embeds the shared allow/block config into RemoveLabelsConfig.
pkg/workflow/assign_to_user.go Embeds the shared allow/block config into AssignToUserConfig.
pkg/workflow/unassign_from_user.go Embeds the shared allow/block config into UnassignFromUserConfig.
pkg/workflow/set_issue_type.go Embeds the shared allow/block config into SetIssueTypeConfig.
pkg/workflow/assign_milestone.go Embeds the shared allow/block config into AssignMilestoneConfig.
pkg/workflow/safe_outputs_tools_test.go Updates tool-description enhancement test fixtures to use the embedded config.
pkg/workflow/safe_outputs_config_generation_test.go Updates safe-outputs config generation fixtures to use the embedded config.
pkg/workflow/compiler_safe_outputs_job_test.go Updates compiler job-related fixtures to use the embedded config.
pkg/workflow/compiler_safe_outputs_env_test.go Updates env var compilation fixtures to use the embedded config.
pkg/workflow/compiler_safe_outputs_config_test.go Updates handler config/env var fixtures to use the embedded config.
pkg/cli/compile_workflow_processor_test.go Updates CLI compilation processor fixtures to use the embedded config.

Review details

Tip

Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

  • Files reviewed: 13/13 changed files
  • Comments generated: 3
  • Review effort level: Low

Comment thread pkg/workflow/set_issue_type.go Outdated
Comment on lines +10 to +14
type SetIssueTypeConfig struct {
BaseSafeOutputConfig `yaml:",inline"`
SafeOutputTargetConfig `yaml:",inline"`
SafeOutputFilterConfig `yaml:",inline"`
Allowed []string `yaml:"allowed,omitempty"` // Optional list of allowed issue type names. If omitted, any type is allowed (including clearing with "").
BaseSafeOutputConfig `yaml:",inline"`
SafeOutputTargetConfig `yaml:",inline"`
SafeOutputFilterConfig `yaml:",inline"`
SafeOutputAllowBlockConfig `yaml:",inline"`

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the latest commit — SetIssueTypeConfig now uses a direct Allowed []string field again instead of embedding SafeOutputAllowBlockConfig, so no blocked: key is exposed in YAML for this handler.

Comment thread pkg/workflow/assign_milestone.go Outdated
Comment on lines +10 to +15
type AssignMilestoneConfig struct {
BaseSafeOutputConfig `yaml:",inline"`
SafeOutputTargetConfig `yaml:",inline"`
SafeOutputFilterConfig `yaml:",inline"`
Allowed []string `yaml:"allowed,omitempty"` // Optional list of allowed milestone titles or IDs
AutoCreate bool `yaml:"auto_create,omitempty"` // If true, auto-create missing milestones found in the allowed list
BaseSafeOutputConfig `yaml:",inline"`
SafeOutputTargetConfig `yaml:",inline"`
SafeOutputFilterConfig `yaml:",inline"`
SafeOutputAllowBlockConfig `yaml:",inline"`
AutoCreate bool `yaml:"auto_create,omitempty"` // If true, auto-create missing milestones found in the allowed list

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the latest commit — AssignMilestoneConfig now uses a direct Allowed []string field again instead of embedding SafeOutputAllowBlockConfig, so no blocked: key is exposed in YAML for this handler.

Comment on lines 10 to 16
type AssignToUserConfig struct {
BaseSafeOutputConfig `yaml:",inline"`
SafeOutputTargetConfig `yaml:",inline"`
SafeOutputFilterConfig `yaml:",inline"`
Allowed []string `yaml:"allowed,omitempty"` // Optional list of allowed usernames. If omitted, any users are allowed.
Blocked []string `yaml:"blocked,omitempty"` // Optional list of blocked usernames or patterns (e.g., "copilot", "*[bot]")
UnassignFirst *string `yaml:"unassign-first,omitempty"` // If true, unassign all current assignees before assigning new ones
BaseSafeOutputConfig `yaml:",inline"`
SafeOutputTargetConfig `yaml:",inline"`
SafeOutputFilterConfig `yaml:",inline"`
SafeOutputAllowBlockConfig `yaml:",inline"`
UnassignFirst *string `yaml:"unassign-first,omitempty"` // If true, unassign all current assignees before assigning new ones
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in the latest commit — safe_outputs_integration_test.go now initializes the embedded struct explicitly: SafeOutputAllowBlockConfig: SafeOutputAllowBlockConfig{Allowed: []string{"user1"}}. go vet -tags=integration passes cleanly.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review: Extract shared SafeOutputAllowBlockConfig

The structural refactor is clean and the test updates are correct. However, there are two correctness bugs introduced by applying the shared config too broadly.

Blocking: Silent blocked config loss for assign_milestone and set_issue_type

Both AssignMilestoneConfig and SetIssueTypeConfig previously had only Allowed []string — neither ever supported Blocked. By embedding SafeOutputAllowBlockConfig, both structs now gain a Blocked field and will happily parse it from YAML. But the handler entries in safe_outputs_handler_registry.go were not updated: neither assign_milestone nor set_issue_type calls AddStringSlice("blocked", c.Blocked). Any blocked: list a user adds to these handlers will be silently dropped at runtime — no error, no warning.

The handlers that already supported blocked before this PR (add_labels, remove_labels, assign_to_user, unassign_from_user) are fine — their registry entries were already correct.

Fix options:

  1. Add AddStringSlice("blocked", c.Blocked) to both handler registry entries (and add tests), or
  2. Introduce a narrower SafeOutputAllowOnlyConfig struct with only Allowed, and embed that in AssignMilestoneConfig and SetIssueTypeConfig instead.

Minor: Doc comment specificity lost for SetIssueTypeConfig

The original Allowed comment carried the note: "If omitted, any type is allowed (including clearing with "")." This is now replaced by the generic comment on SafeOutputAllowBlockConfig. Consider restoring this via a struct-level doc comment on SetIssueTypeConfig.

References:

🧵 Reviewed using Impeccable skills by Impeccable Skills Reviewer · 43.5 AIC · ⌖ 7.02 AIC · ⊞ 4.9K

Comment thread pkg/workflow/assign_milestone.go Outdated
BaseSafeOutputConfig `yaml:",inline"`
SafeOutputTargetConfig `yaml:",inline"`
SafeOutputFilterConfig `yaml:",inline"`
SafeOutputAllowBlockConfig `yaml:",inline"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent config loss: Blocked field added but not emitted by handler registry.

Embedding SafeOutputAllowBlockConfig silently introduces a Blocked []string field into AssignMilestoneConfig. However, the assign_milestone entry in safe_outputs_handler_registry.go only calls AddStringSlice("allowed", c.Allowed) — it never calls AddStringSlice("blocked", c.Blocked).

assign_milestone previously had only Allowed (no Blocked), so blocked-list support was never wired up. Any blocked: list a user writes in their YAML will be parsed and accepted by the struct, then silently discarded at runtime — no error, no warning.

Fix by either:

  1. Adding AddStringSlice("blocked", c.Blocked) to the assign_milestone handler entry in the registry, or
  2. Introducing a narrower shared config (e.g., SafeOutputAllowOnlyConfig) that contains only Allowed, and embed that here instead.

@copilot please address this.

Comment thread pkg/workflow/set_issue_type.go Outdated
BaseSafeOutputConfig `yaml:",inline"`
SafeOutputTargetConfig `yaml:",inline"`
SafeOutputFilterConfig `yaml:",inline"`
SafeOutputAllowBlockConfig `yaml:",inline"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Silent config loss: Blocked field added but not emitted by handler registry.

Same issue as AssignMilestoneConfig. Embedding SafeOutputAllowBlockConfig introduces a Blocked []string field, but the set_issue_type handler entry in safe_outputs_handler_registry.go only emits allowed, never blocked. A user who writes:

set-issue-type:
  blocked: ["Epic", "Task"]

will have the blocked list silently ignored at runtime.

Additionally, the original doc comment on Allowed read: "If omitted, any type is allowed (including clearing with "")." The note about clearing with empty string is important user-facing documentation and should be preserved (e.g., add a Blocked-specific doc comment to SafeOutputAllowBlockConfig that carries this context, or restore it via a struct-level comment here).

@copilot please address this.

@github-actions

Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 84/100 — Excellent

Analyzed 17 modified test(s) across 6 files: 17 design, 0 implementation, 0 guideline violation(s). All changes are mechanical fixture updates adapting composite literals to the new SafeOutputAllowBlockConfig embedded struct — no test logic was altered.

📊 Metrics & Test Classification (17 tests analyzed)
Metric Value
New/modified tests analyzed 17
✅ Design tests (behavioral contracts) 17 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 8 (47%)
Duplicate test clusters 0
Test inflation detected No (test ÷ prod additions ≈ 1.83:1)
🚨 Coding-guideline violations 0
Test File Classification Issues Detected
TestAddHandlerManagerConfigEnvVar pkg/workflow/compiler_safe_outputs_config_test.go:16 ✅ Design — (table-driven, edge-case rows)
TestHandlerConfigAssignToUser pkg/workflow/compiler_safe_outputs_config_test.go:2084 ✅ Design
TestHandlerConfigUnassignFromUser pkg/workflow/compiler_safe_outputs_config_test.go:2215 ✅ Design
TestHandlerConfigAssignToUserWithBlocked pkg/workflow/compiler_safe_outputs_config_test.go:2292 ✅ Design — (blocked pattern edge case)
TestHandlerConfigUnassignFromUserWithBlocked pkg/workflow/compiler_safe_outputs_config_test.go:2344 ✅ Design — (blocked pattern edge case)
TestAddAllSafeOutputConfigEnvVars pkg/workflow/compiler_safe_outputs_env_test.go:15 ✅ Design
TestStagedFlagOnlyAddedOnce pkg/workflow/compiler_safe_outputs_env_test.go:194 ✅ Design — (staged dedup edge case)
TestEnvVarsWithMultipleSafeOutputTypes pkg/workflow/compiler_safe_outputs_env_test.go:317 ✅ Design
TestAddLabelsTargetRepoStagedBehavior pkg/workflow/compiler_safe_outputs_env_test.go:517 ✅ Design — (target-repo + staged edge case)
TestBuildConsolidatedSafeOutputsJob pkg/workflow/compiler_safe_outputs_job_test.go:20 ✅ Design
TestGitHubAppTokenStepWithOTLPHeaders pkg/workflow/compiler_safe_outputs_job_test.go:1002 ✅ Design
TestGitHubAppTokenStepWithOTLPAttributes pkg/workflow/compiler_safe_outputs_job_test.go:1049 ✅ Design
TestGitHubAppTokenStepWithOTLPHeadersAndAttributes pkg/workflow/compiler_safe_outputs_job_test.go:1095 ✅ Design — (combined OTLP edge case)
TestGenerateSafeOutputsConfigActionsCollisionReturnsError pkg/workflow/safe_outputs_config_generation_test.go:101 ✅ Design — (error path)
TestGenerateSafeOutputsConfigAddLabelsBlocked pkg/workflow/safe_outputs_config_generation_test.go:430 ✅ Design — (blocked label edge case)
TestEnhanceToolDescription pkg/workflow/safe_outputs_tools_test.go:16 ✅ Design
TestExtractSafeOutputLabels_IncludesLabelCommand pkg/cli/compile_workflow_processor_test.go:11 ✅ Design

Go: 17 (*_test.go); JavaScript: 0. No other languages detected.

Score breakdown: behavioral 40/40 · edge-cases 14/30 · duplication 20/20 · inflation 10/10 = 84

Verdict

Check passed. 0% implementation tests (threshold: 30%). All 17 modified tests verify observable compilation output (JSON env-var payloads) rather than internal implementation details. No mock libraries, no missing build tags, no bare assertion-message violations found in modified functions.

References: §28397842942

🧪 Test quality analysis by Test Quality Sentinel · 81.2 AIC · ⌖ 20.8 AIC · ⊞ 8.1K ·
Comment /review to run again

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

✅ Test Quality Sentinel: 84/100. Test quality is acceptable — 0% of new tests are implementation tests (threshold: 30%).

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Skills-Based Review 🧠

Applied /zoom-out and /improve-codebase-architecture — requesting changes on two correctness gaps.

📋 Key Themes & Highlights

Key Themes

  • Silent feature expansion: AssignMilestoneConfig and SetIssueTypeConfig originally had no Blocked field. By embedding SafeOutputAllowBlockConfig, this PR adds a blocked: YAML key that the parser will happily accept — but the handler registry for both assign_milestone and set_issue_type does not forward c.Blocked when building the runtime config map. The net effect is a silently ignored config field, which is worse than a parse error.

  • Doc comment regression: The per-field comments in original structs carried handler-specific context (e.g. *including

🧠 Reviewed using Matt Pocock's skills by Matt Pocock Skills Reviewer · 83.6 AIC · ⌖ 7.58 AIC · ⊞ 6.6K
Comment /matt to run again

Comments that could not be inline-anchored

pkg/workflow/assign_milestone.go:14

[/zoom-out] AssignMilestoneConfig never had a Blocked field before this PR — and the handler registry (safe_outputs_handler_registry.go lines 219-233) still does not forward c.Blocked. Embedding SafeOutputAllowBlockConfig silently adds a blocked: YAML key that will be accepted by the parser but dropped at runtime, giving users false confidence that blocking works.

<details>
<summary>💡 Options to resolve</summary>

Option A — Wire it up in the handler registry (add one line…

pkg/workflow/set_issue_type.go:14

[/zoom-out] Same issue as AssignMilestoneConfig: SetIssueTypeConfig previously had only Allowed, but now gains Blocked through the embed. The handler registry function for set_issue_type (lines 928-948 of safe_outputs_handler_registry.go) does not call AddStringSlice(&quot;blocked&quot;, c.Blocked), so a user who writes blocked: in their set-issue-type config will have it silently ignored.

This is in tension with the PR's stated goal of "no functional behavior changes" — it does ch…

pkg/workflow/safe_outputs_parser.go:32

[/improve-codebase-architecture] The shared doc comment // Optional list of allowed values loses context that each handler's original field comment carried. The previous comments were meaningfully specific — e.g., SetIssueTypeConfig.Allowed had // If omitted, any type is allowed (including clearing with &quot;&quot;) and AssignMilestoneConfig.Allowed had // Optional list of allowed milestone titles or IDs.

<details>
<summary>💡 Suggestion</summary>

Consider keeping the handler-level conte…

pkg/workflow/assign_milestone.go:27

[/improve-codebase-architecture] The log message tracks allowed_count but not blocked_count — inconsistent with add_labels.go and remove_labels.go which log both after embedding the same struct. If Blocked is going to be part of this config, the log should reflect it.

assignMilestoneLog.Printf(&quot;Parsed milestone config: target=%s, allowed_count=%d, blocked_count=%d&quot;,
    config.Target, len(config.Allowed), len(config.Blocked))

(Same applies to set_issue_type.go.)

@co

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REQUEST_CHANGES — two high-severity silent-bypass bugs must be fixed before merge.

### Blocking issues (2)

1. assign_milestone and set_issue_type silently gained a blocked field that is never forwarded to the runtime.

Both AssignMilestoneConfig and SetIssueTypeConfig previously had no Blocked field. Embedding SafeOutputAllowBlockConfig introduces one that parses valid YAML without error, but the handler registry entries for assign_milestone (line 226) and set_issue_type (line 939) only call AddStringSlice("allowed", c.Allowed) — there is no corresponding AddStringSlice("blocked", c.Blocked) call for either. Any blocked: constraint an operator configures is silently dropped at compile time, giving a false sense of security.

Fix: either add the missing AddStringSlice("blocked", c.Blocked) calls in the registry (if blocking is intentionally supported for those handlers), or avoid embedding the shared struct on configs where only Allowed was previously supported.

### Non-blocking observations (2)
  • Docstring generalization erases per-handler semanticsadd_labels's auto-label-creation side-effect and set_issue_type's empty-string clearing behavior are no longer documented at the struct site.
  • Generic Allowed comment — "Optional list of allowed values" conveys nothing about glob-pattern support, permissive-by-default posture, or dual title/ID matching.

These are maintainability debts introduced by this PR and should be addressed alongside the blocking fixes.

🔎 Code quality review by PR Code Quality Reviewer · 124.7 AIC · ⌖ 8.72 AIC · ⊞ 5.2K
Comment /review to run again

Comment thread pkg/workflow/assign_milestone.go Outdated
BaseSafeOutputConfig `yaml:",inline"`
SafeOutputTargetConfig `yaml:",inline"`
SafeOutputFilterConfig `yaml:",inline"`
SafeOutputAllowBlockConfig `yaml:",inline"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocked field silently added and silently dropped at runtime: AssignMilestoneConfig never had a Blocked field before; this embedding introduces one, but the handler registry never forwards it — so any blocked: the user writes in YAML will parse successfully and then be completely ignored.

💡 Details and fix

safe_outputs_handler_registry.go (around line 226) builds the assign_milestone config map with:

return newHandlerConfigBuilder().
    AddTemplatableInt("max", c.Max).
    AddStringSlice("allowed", c.Allowed).
    // ... but no AddStringSlice("blocked", c.Blocked)

Before this PR AssignMilestoneConfig had no Blocked field, so this was never a problem. Now that SafeOutputAllowBlockConfig is embedded, the field exists in the struct and is YAML-parseable — a user writing:

assign-milestone:
  blocked: ["v99"]

...will see no error but the blocked constraint will never reach the runtime handler. This is a silent security policy bypass.

Fix options:

  1. Add AddStringSlice("blocked", c.Blocked) to the assign_milestone registry entry if blocking milestones is a supported feature.
  2. Or keep a standalone Allowed []string on AssignMilestoneConfig instead of embedding the shared struct, until blocked support is verified end-to-end.

Comment thread pkg/workflow/set_issue_type.go Outdated
BaseSafeOutputConfig `yaml:",inline"`
SafeOutputTargetConfig `yaml:",inline"`
SafeOutputFilterConfig `yaml:",inline"`
SafeOutputAllowBlockConfig `yaml:",inline"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Blocked field silently added and silently dropped at runtime: SetIssueTypeConfig never had a Blocked field before; embedding SafeOutputAllowBlockConfig adds one, but the set_issue_type handler registry entry only emits allowedblocked is parsed and then discarded.

💡 Details and fix

In safe_outputs_handler_registry.go the set_issue_type entry (around line 939) is:

config := newHandlerConfigBuilder().
    AddTemplatableInt("max", c.Max).
    AddStringSlice("allowed", c.Allowed).
    // no AddStringSlice("blocked", c.Blocked)

A user writing:

set-issue-type:
  blocked: ["spam"]

...will get no YAML parse error, but the blocked constraint is never forwarded to the runtime. The user believes they are blocking certain issue types; they are not.

Fix options:

  1. Add AddStringSlice("blocked", c.Blocked) to the set_issue_type registry entry if blocking is intended.
  2. Or use a standalone Allowed []string field instead of the shared struct to keep the struct's public surface exactly as it was before.

// SafeOutputAllowBlockConfig contains common allow/block lists for safe output configurations.
// Embed this in safe output config structs that support optional allowed/blocked value filters.
type SafeOutputAllowBlockConfig struct {
Allowed []string `yaml:"allowed,omitempty"` // Optional list of allowed values

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Semantic docstrings lost in generalization: the shared Allowed comment "Optional list of allowed values" replaces precise per-handler documentation that carried important semantic distinctions.

💡 What was lost
Handler Original comment
AddLabelsConfig "Labels will be created if they don't already exist in the repository. If omitted, any labels are allowed (including creating new ones)."
RemoveLabelsConfig "Optional list of allowed label patterns to remove ... If omitted, any labels can be removed."
SetIssueTypeConfig "Optional list of allowed issue type names. If omitted, any type is allowed (including clearing with "")."
AssignToUserConfig "If omitted, any users are allowed."

The generic comment doesn't convey: when the list is omitted vs. present, what the wildcard behavior is, whether labels are auto-created, or that the empty-string sentinel for clearing a type is allowed. This context should be preserved either by adding handler-specific doc on the embedding site or by enriching the shared struct comments.

BaseSafeOutputConfig `yaml:",inline"`
SafeOutputTargetConfig `yaml:",inline"`
SafeOutputFilterConfig `yaml:",inline"`
SafeOutputAllowBlockConfig `yaml:",inline"`

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Operator-critical side-effect stripped from documentation: the original Allowed comment noted that "Labels will be created if they don't already exist in the repository." — this is a write side-effect, not just a filter. The generic replacement says only "Optional list of allowed values" and conceals this completely.

💡 Why this matters

For add_labels, Allowed serves a dual role:

  1. Filter: only labels on this list may be applied.
  2. Creation whitelist: the handler will create labels in the repository if they don't already exist, trusting that any entry in Allowed was pre-approved.

A user adding a label pattern like team-backend to Allowed is implicitly authorizing the agent to create that label in the target repository. Without documentation at the struct definition site, this side-effect is invisible to anyone reading just the config struct. At minimum, the embedding struct or a comment at the field declaration site should note this behavior.

@gh-aw-bot

Copy link
Copy Markdown
Collaborator

@copilot please run the pr-finisher skill to drive this PR to a mergeable state.

Unresolved review threads (newest first):

Branch refresh: This branch is 4 commits behind main — branch update attempted above; please also rebase if needed.

Generated by 👨‍🍳 PR Sous Chef · 172.6 AIC · ⌖ 10.9 AIC · ⊞ 6.7K ·

- SetIssueTypeConfig: revert to direct Allowed field; handler registry
  only serializes allowed, so embedding SafeOutputAllowBlockConfig would
  silently accept (and ignore) a blocked: YAML key
- AssignMilestoneConfig: same fix — keep direct Allowed field
- safe_outputs_integration_test.go: update AssignToUserConfig composite
  literal to use SafeOutputAllowBlockConfig{} embedded struct syntax,
  fixing the go vet / integration build error

Co-authored-by: gh-aw-bot <259018956+gh-aw-bot@users.noreply.github.com>
@pelikhan pelikhan merged commit 1d1cc76 into main Jun 29, 2026
29 checks passed
@pelikhan pelikhan deleted the copilot/deep-report-quick-win-extract-safeoutputallowblock branch June 29, 2026 20:53
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[deep-report] [quick-win] Extract shared SafeOutputAllowBlockConfig (Allowed/Blocked) across safe-output configs

4 participants