Skip to content

Safe outputs audit (consistency, overlap, gaps) #17317

@eaftan

Description

@eaftan

Safe Outputs Audit (Consistency, Overlap, Gaps)

Date: 2026-02-20

Scope and sources

This audit reviews built-in safe output options across:

  • User-facing reference docs: docs/src/content/docs/reference/safe-outputs.md
  • Type declaration surface: actions/setup/js/types/safe-outputs-config.d.ts
  • Runtime parsing/config behavior in workflow compiler:
    • pkg/workflow/create_issue.go
    • pkg/workflow/create_pull_request.go
    • pkg/workflow/assign_to_agent.go
    • pkg/workflow/close_entity_helpers.go
    • pkg/workflow/update_entity_helpers.go
    • pkg/workflow/safe_output_validation_config.go

1) Consistency review

1.1 Option naming is mostly consistent, but there are important outliers

A) assign-to-agent naming mismatch across surfaces

  • Docs show:
    • name, model, custom-agent, custom-instructions
    • Reference: docs/src/content/docs/reference/safe-outputs.md:1097-1103
  • Go parser expects:
    • name, model, custom-agent, custom-instructions
    • Reference: pkg/workflow/assign_to_agent.go:13-16
  • Type declaration (.d.ts) shows:
    • default-agent (not name)
    • Reference: actions/setup/js/types/safe-outputs-config.d.ts:220-223

Impact: users and tooling that rely on .d.ts may configure the wrong key.

B) max behavior is inconsistent by output type

  • Most outputs support configurable max via common base config.
  • create-pull-request explicitly forces max = 1 regardless of user input.
    • Reference: pkg/workflow/create_pull_request.go:273-276
    • Docs imply singular behavior, but this hard override differs from the broader “configurable max” pattern used elsewhere.

Impact: user expectations around max are not uniform across similarly “create-*” outputs.

C) Footer option shape differs across related PR-review outputs

  • submit-pull-request-review supports richer footer modes (always|none|if-body) in type definitions.
    • Reference: actions/setup/js/types/safe-outputs-config.d.ts:98-107
  • Other outputs frequently expose simple boolean footer (true/false) in docs/config examples.
    • References:
      • docs/src/content/docs/reference/safe-outputs.md:742-752
      • pkg/workflow/create_issue.go:23

Impact: same conceptual option (“footer control”) has different value models depending on output.

D) Targeting model is consistent in pattern, but uneven in capability

  • “Update/close/comment/assignment” outputs commonly use target (triggering|*|number) + optional target-repo.
    • Examples in docs:
      • close-issue: docs/src/content/docs/reference/safe-outputs.md:210-218
      • add-comment: docs/src/content/docs/reference/safe-outputs.md:229-236
  • “Create” outputs naturally skip target and emphasize target-repo (creating new entities).
    • Example: create-issue docs :85-96, struct pkg/workflow/create_issue.go:18

Assessment: this is mostly logical, but it creates a mental-model split users must learn.

1.2 Internal implementation already recognizes commonality

  • Close operations share one generic config type and helper pipeline.
    • Reference: pkg/workflow/close_entity_helpers.go:66-73, :88-121
  • Update operations also share a generic helper pipeline.
    • Reference: pkg/workflow/update_entity_helpers.go:86-91, :119-153

Assessment: implementation is DRY; external configuration UX is where most inconsistency remains.


2) Overlap and redundancy

2.1 Clear overlap: close-* family

close-issue, close-pull-request, close-discussion all implement the same core pattern:

  • target selection
  • max limit
  • repo scoping
  • optional filters (required-labels, required-title-prefix, and discussion-specific category)

Reference: shared model in pkg/workflow/close_entity_helpers.go:66-73.

Interpretation: these are intentionally separate tools, but semantically one “close entity” capability.

2.2 Clear overlap: update-* family

update-issue, update-pull-request, update-discussion share:

  • target selection
  • max limit
  • selective field enablement
  • similar update semantics

Reference: pkg/workflow/update_entity_helpers.go:119-153, :196-257.

Interpretation: same base behavior split by entity-specific fields.

2.3 Partial overlap: comment minimization

  • add-comment supports hide-older-comments + allowed-reasons.
    • Reference: docs/src/content/docs/reference/safe-outputs.md:235-242
  • hide-comment separately supports explicit hide operations.
    • Reference: docs/src/content/docs/reference/safe-outputs.md:257-266

Interpretation: two ways to accomplish “reduce comment noise” (automatic side-effect vs explicit operation).

2.4 Partial overlap: agent assignment paths

  • assign-to-agent for existing issue/PR automation.
  • create-issue can assign copilot directly via assignees.
    • Reference note in docs: docs/src/content/docs/reference/safe-outputs.md:1135

Interpretation: valid but overlapping pathways for similar outcome.


3) Missing safe outputs (likely user demand)

The current catalog is broad, but some common workflows still require custom jobs.

3.1 Explicit non-goal: merge pull request

No built-in merge safe output was found in docs/types/parser surfaces, and that should remain intentional for now to keep a human in the loop for merges.

  • No merge-pull-request match in:
    • docs/src/content/docs/reference/safe-outputs.md
    • pkg/workflow/*.go
    • actions/setup/js/types/safe-outputs-config.d.ts

Assessment: keep this as a deliberate boundary, not a gap.

3.2 Missing: reopen operations

No reopen-issue, reopen-pull-request, or reopen-discussion safe outputs found (search returned no matches).

Why it matters: close operations exist; lifecycle symmetry is incomplete without reopen.

3.3 Missing: remove reviewer

  • add-reviewer exists.
  • No remove-reviewer found.

Why it matters: reviewer lifecycle management is one-directional today.

3.4 Missing: reaction operations

No first-class add/remove reaction safe output found.

Why it matters: reactions are a low-risk/high-signal action often used for lightweight triage and acknowledgments.


4) Recommended actions (with backward-compatibility assessment)

Each action below is scoped so another engineer can pick it up directly.

Action 1: Resolve assign-to-agent key mismatch (name vs default-agent)

Problem: docs/parser use name; .d.ts advertises default-agent.

Execution plan:

  1. Update actions/setup/js/types/safe-outputs-config.d.ts to use name.
  2. If default-agent is currently accepted anywhere, keep it as an alias for one deprecation cycle and log a warning.
  3. Add/adjust tests covering config parsing and type surface consistency.
  4. Update docs examples to show canonical name only.

Files to touch:

  • actions/setup/js/types/safe-outputs-config.d.ts
  • pkg/workflow/assign_to_agent.go (only if alias support needed)
  • docs/src/content/docs/reference/safe-outputs.md
  • related tests in pkg/workflow/*assign*test* and JS type/config tests

Acceptance criteria:

  • name is canonical in docs/types/parser.
  • Existing workflows using prior key continue to work (or fail with explicit migration error, per chosen strategy).

Backward-compatibility assessment:
Low risk if alias support is added; medium risk if changed as a hard break. Recommended: support both for one release, then remove alias.

Action 2: Publish an option taxonomy and conformance matrix

Problem: options are conceptually similar but not uniformly documented.

Execution plan:

  1. Add a matrix section to safe-outputs.md with columns:
    • targeting (target),
    • repo scope (target-repo, allowed-repos),
    • filters (required-*, allowed-*, blocked),
    • limits (max, fixed max),
    • fallback behavior.
  2. Add a small CI/conformance check (or extend existing script/tests) to ensure docs and config surfaces stay aligned.

Files to touch:

  • docs/src/content/docs/reference/safe-outputs.md
  • scripts/check-safe-outputs-conformance.sh (or equivalent conformance tests)

Acceptance criteria:

  • A single table lets users compare option support across all safe outputs.
  • CI fails when key naming drifts across docs/types/implementation.

Backward-compatibility assessment:
No runtime risk; documentation and validation-only improvement.

Action 3: Normalize footer semantics

Problem: footer supports richer enum modes in some paths and boolean-only in others.

Execution plan:

  1. Choose one model:
    • Preferred: support enum (always|none|if-body) + boolean aliases globally.
  2. Update config types and parsers to accept chosen model consistently.
  3. Normalize runtime behavior and environment-variable mapping.
  4. Add cross-output tests for true/false/always/none/if-body.

Files to touch:

  • actions/setup/js/types/safe-outputs-config.d.ts
  • relevant parser files in pkg/workflow/*
  • footer behavior docs in docs/src/content/docs/reference/safe-outputs.md

Acceptance criteria:

  • Same footer values produce equivalent behavior across supported outputs.
  • Migration guidance exists for any changed values.

Backward-compatibility assessment:
Medium risk unless boolean aliases are preserved. Recommended: keep booleans as aliases indefinitely.

Action 4: Clarify and enforce max contract per type

Problem: users may assume max always applies, but some types force fixed limits (e.g., create-pull-request).

Execution plan:

  1. Add “Max behavior” row per type in docs (configurable, fixed, unbounded).
  2. In parser/validator, reject invalid max settings for fixed-limit types with explicit errors, or ignore with warning (choose one policy).
  3. Add tests to assert behavior.

Files to touch:

  • docs/src/content/docs/reference/safe-outputs.md
  • pkg/workflow/create_pull_request.go
  • pkg/workflow/safe_output_validation_config.go
  • tests around max validation/parse behavior

Acceptance criteria:

  • Users can tell at a glance whether max is configurable.
  • Invalid max usage yields deterministic behavior and clear messaging.

Backward-compatibility assessment:
Low risk for docs-only clarification; medium risk for parser enforcement if workflows currently rely on silent ignore. Recommended: warn first, enforce in later release.

Action 5: Add lifecycle-completeness outputs (excluding merge)

Problem: lifecycle coverage is asymmetric.

Candidate outputs:

  • reopen-issue
  • reopen-pull-request
  • reopen-discussion
  • remove-reviewer
  • optional: add-reaction / remove-reaction

Execution plan:

  1. Define schema/types + frontmatter options for each new output.
  2. Add parser wiring in safe_outputs_config.go and config structs.
  3. Implement handlers/jobs with minimal required permissions.
  4. Document examples and limits in safe-outputs.md.
  5. Add tests: parse, validation, permission, end-to-end compile snapshots.

Files to touch:

  • actions/setup/js/types/safe-outputs-config.d.ts
  • pkg/workflow/safe_outputs_config.go
  • new/updated files under pkg/workflow/*
  • docs/src/content/docs/reference/safe-outputs.md
  • corresponding test files

Acceptance criteria:

  • New outputs compile, validate, and execute with least-privilege permissions.
  • No merge-safe-output is introduced.

Backward-compatibility assessment:
Low risk (additive features). Ensure no collisions with existing tool names and defaults remain unchanged.


5) Quick examples

Example A: Key mismatch risk (assign-to-agent)

safe-outputs:
  assign-to-agent:
    name: "copilot"   # works in docs/parser

But .d.ts currently advertises default-agent, which can mislead typed config generators.

Example B: Surprising max behavior

safe-outputs:
  create-pull-request:
    max: 5   # ignored; runtime forces max=1

Reference: pkg/workflow/create_pull_request.go:273-276.

Sub-issues

Metadata

Metadata

Assignees

Labels

bugSomething isn't workingdocumentationImprovements or additions to documentationenhancementNew feature or requestworkflows

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions