Skip to content

Commit fc96758

Browse files
authored
Mark plugins as experimental with schema and compilation warnings (#14551)
1 parent 9cfb43f commit fc96758

5 files changed

Lines changed: 198 additions & 17 deletions

File tree

docs/src/content/docs/reference/frontmatter-full.md

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1095,9 +1095,10 @@ sandbox:
10951095
# (optional)
10961096
domain: "localhost"
10971097

1098-
# Plugin configuration for installing plugins before workflow execution. Supports
1099-
# array format (list of repos/plugin configs) and object format (repos + custom
1100-
# token).
1098+
# ⚠️ EXPERIMENTAL: Plugin configuration for installing plugins before workflow
1099+
# execution. Supports array format (list of repos/plugin configs) and object
1100+
# format (repos + custom token). Note: Plugin support is experimental and may
1101+
# change in future releases.
11011102
# (optional)
11021103
# This field supports multiple formats (oneOf):
11031104

@@ -1152,14 +1153,14 @@ post-steps: []
11521153
# (optional)
11531154
# This field supports multiple formats (oneOf):
11541155

1155-
# Option 1: Simple engine name: 'claude' (default, Claude by Anthropic), 'copilot' (GitHub
1156+
# Option 1: Simple engine name: 'claude' (default, Claude Code), 'copilot' (GitHub
11561157
# Copilot CLI), 'codex' (OpenAI Codex CLI), or 'custom' (user-defined steps)
11571158
engine: "claude"
11581159

11591160
# Option 2: Extended engine configuration object with advanced options for model
11601161
# selection, turn limiting, environment variables, and custom steps
11611162
engine:
1162-
# AI engine identifier: 'claude' (Claude by Anthropic), 'codex' (OpenAI Codex CLI),
1163+
# AI engine identifier: 'claude' (Claude Code), 'codex' (OpenAI Codex CLI),
11631164
# 'copilot' (GitHub Copilot CLI), or 'custom' (user-defined GitHub Actions steps)
11641165
id: "claude"
11651166

docs/src/content/docs/reference/frontmatter.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,10 @@ See [GitHub Tokens](/gh-aw/reference/tokens/) for complete documentation.
100100

101101
### Plugins (`plugins:`)
102102

103+
:::caution[Experimental Feature]
104+
Plugin support is experimental and may change in future releases. Using plugins will emit a compilation warning.
105+
:::
106+
103107
Specifies plugins to install before workflow execution. Plugins are installed using engine-specific CLI commands (`copilot plugin install`, `claude plugin install`, `codex plugin install`).
104108

105109
**Array format** (simple):

pkg/parser/schemas/main_workflow_schema.json

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2483,7 +2483,7 @@
24832483
]
24842484
},
24852485
"plugins": {
2486-
"description": "Plugin configuration for installing plugins before workflow execution. Supports array format (list of repos/plugin configs) and object format (repos + custom token).",
2486+
"description": "⚠️ EXPERIMENTAL: Plugin configuration for installing plugins before workflow execution. Supports array format (list of repos/plugin configs) and object format (repos + custom token). Note: Plugin support is experimental and may change in future releases.",
24872487
"examples": [
24882488
["github/copilot-plugin", "acme/custom-tools"],
24892489
[
@@ -2680,7 +2680,7 @@
26802680
[
26812681
{
26822682
"name": "Verify Post-Steps Execution",
2683-
"run": "echo \"\u2705 Post-steps are executing correctly\"\necho \"This step runs after the AI agent completes\"\n"
2683+
"run": "echo \" Post-steps are executing correctly\"\necho \"This step runs after the AI agent completes\"\n"
26842684
},
26852685
{
26862686
"name": "Upload Test Results",
@@ -5940,8 +5940,8 @@
59405940
},
59415941
"staged-title": {
59425942
"type": "string",
5943-
"description": "Custom title template for staged mode preview. Available placeholders: {operation}. Example: '\ud83c\udfad Preview: {operation}'",
5944-
"examples": ["\ud83c\udfad Preview: {operation}", "## Staged Mode: {operation}"]
5943+
"description": "Custom title template for staged mode preview. Available placeholders: {operation}. Example: '🎭 Preview: {operation}'",
5944+
"examples": ["🎭 Preview: {operation}", "## Staged Mode: {operation}"]
59455945
},
59465946
"staged-description": {
59475947
"type": "string",
@@ -5955,18 +5955,18 @@
59555955
},
59565956
"run-success": {
59575957
"type": "string",
5958-
"description": "Custom message template for successful workflow completion. Available placeholders: {workflow_name}, {run_url}. Default: '\u2705 Agentic [{workflow_name}]({run_url}) completed successfully.'",
5959-
"examples": ["\u2705 Agentic [{workflow_name}]({run_url}) completed successfully.", "\u2705 [{workflow_name}]({run_url}) finished."]
5958+
"description": "Custom message template for successful workflow completion. Available placeholders: {workflow_name}, {run_url}. Default: ' Agentic [{workflow_name}]({run_url}) completed successfully.'",
5959+
"examples": [" Agentic [{workflow_name}]({run_url}) completed successfully.", " [{workflow_name}]({run_url}) finished."]
59605960
},
59615961
"run-failure": {
59625962
"type": "string",
5963-
"description": "Custom message template for failed workflow. Available placeholders: {workflow_name}, {run_url}, {status}. Default: '\u274c Agentic [{workflow_name}]({run_url}) {status} and wasn't able to produce a result.'",
5964-
"examples": ["\u274c Agentic [{workflow_name}]({run_url}) {status} and wasn't able to produce a result.", "\u274c [{workflow_name}]({run_url}) {status}."]
5963+
"description": "Custom message template for failed workflow. Available placeholders: {workflow_name}, {run_url}, {status}. Default: ' Agentic [{workflow_name}]({run_url}) {status} and wasn't able to produce a result.'",
5964+
"examples": [" Agentic [{workflow_name}]({run_url}) {status} and wasn't able to produce a result.", " [{workflow_name}]({run_url}) {status}."]
59655965
},
59665966
"detection-failure": {
59675967
"type": "string",
5968-
"description": "Custom message template for detection job failure. Available placeholders: {workflow_name}, {run_url}. Default: '\u26a0\ufe0f Security scanning failed for [{workflow_name}]({run_url}). Review the logs for details.'",
5969-
"examples": ["\u26a0\ufe0f Security scanning failed for [{workflow_name}]({run_url}). Review the logs for details.", "\u26a0\ufe0f Detection job failed in [{workflow_name}]({run_url})."]
5968+
"description": "Custom message template for detection job failure. Available placeholders: {workflow_name}, {run_url}. Default: '⚠️ Security scanning failed for [{workflow_name}]({run_url}). Review the logs for details.'",
5969+
"examples": ["⚠️ Security scanning failed for [{workflow_name}]({run_url}). Review the logs for details.", "⚠️ Detection job failed in [{workflow_name}]({run_url})."]
59705970
},
59715971
"append-only-comments": {
59725972
"type": "boolean",
@@ -6046,12 +6046,12 @@
60466046
"additionalProperties": false
60476047
},
60486048
"roles": {
6049-
"description": "Repository access roles required to trigger agentic workflows. Defaults to ['admin', 'maintainer', 'write'] for security. Use 'all' to allow any authenticated user (\u26a0\ufe0f security consideration).",
6049+
"description": "Repository access roles required to trigger agentic workflows. Defaults to ['admin', 'maintainer', 'write'] for security. Use 'all' to allow any authenticated user (⚠️ security consideration).",
60506050
"oneOf": [
60516051
{
60526052
"type": "string",
60536053
"enum": ["all"],
6054-
"description": "Allow any authenticated user to trigger the workflow (\u26a0\ufe0f disables permission checking entirely - use with caution)"
6054+
"description": "Allow any authenticated user to trigger the workflow (⚠️ disables permission checking entirely - use with caution)"
60556055
},
60566056
{
60576057
"type": "array",

pkg/workflow/compiler.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -210,6 +210,12 @@ func (c *Compiler) validateWorkflowData(workflowData *WorkflowData, markdownPath
210210
c.IncrementWarningCount()
211211
}
212212

213+
// Emit experimental warning for plugins feature
214+
if workflowData.PluginInfo != nil && len(workflowData.PluginInfo.Plugins) > 0 {
215+
fmt.Fprintln(os.Stderr, console.FormatWarningMessage("Using experimental feature: plugins"))
216+
c.IncrementWarningCount()
217+
}
218+
213219
// Validate workflow_run triggers have branch restrictions
214220
log.Printf("Validating workflow_run triggers for branch restrictions")
215221
if err := c.validateWorkflowRunBranches(workflowData, markdownPath); err != nil {
Lines changed: 170 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,170 @@
1+
//go:build integration
2+
3+
package workflow
4+
5+
import (
6+
"bytes"
7+
"io"
8+
"os"
9+
"path/filepath"
10+
"strings"
11+
"testing"
12+
13+
"github.com/github/gh-aw/pkg/testutil"
14+
)
15+
16+
// TestPluginExperimentalWarning tests that the plugins feature
17+
// emits an experimental warning when enabled.
18+
func TestPluginExperimentalWarning(t *testing.T) {
19+
tests := []struct {
20+
name string
21+
content string
22+
expectWarning bool
23+
}{
24+
{
25+
name: "plugins enabled produces experimental warning",
26+
content: `---
27+
on: workflow_dispatch
28+
engine: copilot
29+
plugins:
30+
- github/test-plugin
31+
permissions:
32+
contents: read
33+
---
34+
35+
# Test Workflow
36+
`,
37+
expectWarning: true,
38+
},
39+
{
40+
name: "no plugins does not produce experimental warning",
41+
content: `---
42+
on: workflow_dispatch
43+
engine: copilot
44+
permissions:
45+
contents: read
46+
---
47+
48+
# Test Workflow
49+
`,
50+
expectWarning: false,
51+
},
52+
{
53+
name: "empty plugins array does not produce experimental warning",
54+
content: `---
55+
on: workflow_dispatch
56+
engine: copilot
57+
plugins: []
58+
permissions:
59+
contents: read
60+
---
61+
62+
# Test Workflow
63+
`,
64+
expectWarning: false,
65+
},
66+
{
67+
name: "multiple plugins produce experimental warning",
68+
content: `---
69+
on: workflow_dispatch
70+
engine: copilot
71+
plugins:
72+
- github/plugin1
73+
- github/plugin2
74+
permissions:
75+
contents: read
76+
---
77+
78+
# Test Workflow
79+
`,
80+
expectWarning: true,
81+
},
82+
{
83+
name: "plugins with object format produces experimental warning",
84+
content: `---
85+
on: workflow_dispatch
86+
engine: copilot
87+
plugins:
88+
repos:
89+
- github/test-plugin
90+
github-token: ${{ secrets.CUSTOM_TOKEN }}
91+
permissions:
92+
contents: read
93+
---
94+
95+
# Test Workflow
96+
`,
97+
expectWarning: true,
98+
},
99+
{
100+
name: "plugins with MCP config produces experimental warning",
101+
content: `---
102+
on: workflow_dispatch
103+
engine: copilot
104+
plugins:
105+
- id: github/test-plugin
106+
mcp:
107+
env:
108+
API_KEY: ${{ secrets.API_KEY }}
109+
permissions:
110+
contents: read
111+
---
112+
113+
# Test Workflow
114+
`,
115+
expectWarning: true,
116+
},
117+
}
118+
119+
for _, tt := range tests {
120+
t.Run(tt.name, func(t *testing.T) {
121+
tmpDir := testutil.TempDir(t, "plugin-experimental-warning-test")
122+
123+
testFile := filepath.Join(tmpDir, "test-workflow.md")
124+
if err := os.WriteFile(testFile, []byte(tt.content), 0644); err != nil {
125+
t.Fatal(err)
126+
}
127+
128+
// Capture stderr to check for warnings
129+
oldStderr := os.Stderr
130+
r, w, _ := os.Pipe()
131+
os.Stderr = w
132+
133+
compiler := NewCompiler()
134+
compiler.SetStrictMode(false)
135+
err := compiler.CompileWorkflow(testFile)
136+
137+
// Restore stderr
138+
w.Close()
139+
os.Stderr = oldStderr
140+
var buf bytes.Buffer
141+
io.Copy(&buf, r)
142+
stderrOutput := buf.String()
143+
144+
if err != nil {
145+
t.Errorf("Expected compilation to succeed but it failed: %v", err)
146+
return
147+
}
148+
149+
expectedMessage := "Using experimental feature: plugins"
150+
151+
if tt.expectWarning {
152+
if !strings.Contains(stderrOutput, expectedMessage) {
153+
t.Errorf("Expected warning containing '%s', got stderr:\n%s", expectedMessage, stderrOutput)
154+
}
155+
} else {
156+
if strings.Contains(stderrOutput, expectedMessage) {
157+
t.Errorf("Did not expect warning '%s', but got stderr:\n%s", expectedMessage, stderrOutput)
158+
}
159+
}
160+
161+
// Verify warning count includes plugins warning
162+
if tt.expectWarning {
163+
warningCount := compiler.GetWarningCount()
164+
if warningCount == 0 {
165+
t.Error("Expected warning count > 0 but got 0")
166+
}
167+
}
168+
})
169+
}
170+
}

0 commit comments

Comments
 (0)