Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 14 additions & 14 deletions .github/workflows/pdf-summary.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/pdf-summary.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ on:
inputs:
url:
description: 'URL(s) to resource(s) to analyze (comma-separated for multiple URLs)'
required: true
required: false
type: string
query:
description: 'Query or question to answer about the resource(s)'
Expand Down
28 changes: 14 additions & 14 deletions .github/workflows/scout.lock.yml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion .github/workflows/scout.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ on:
inputs:
topic:
description: 'Research topic or question'
required: true
required: false
history:
description: "Git history to fetch: shallow (default) or full"
required: false
Expand Down
62 changes: 62 additions & 0 deletions pkg/workflow/command_dispatch_validation.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package workflow

import (
"fmt"
"strings"
)

// validateCommandWorkflowDispatchInputs rejects required workflow_dispatch inputs when
// slash_command or label_command triggers are configured.
// Returns an error if any workflow_dispatch input has required: true.
func validateCommandWorkflowDispatchInputs(workflowData *WorkflowData) error {
if workflowData == nil || workflowData.RawFrontmatter == nil {
return nil
}

hasSlashCommand := len(workflowData.Command) > 0
hasLabelCommand := len(workflowData.LabelCommand) > 0
if !hasSlashCommand && !hasLabelCommand {
return nil
}

onMap, ok := workflowData.RawFrontmatter["on"].(map[string]any)
if !ok {
return nil
}

workflowDispatchMap, ok := onMap["workflow_dispatch"].(map[string]any)
if !ok {
return nil
}

inputsMap, ok := workflowDispatchMap["inputs"].(map[string]any)
if !ok {
return nil
}

for inputName, inputDef := range inputsMap {
inputDefMap, ok := inputDef.(map[string]any)
if !ok {
continue
}

required, ok := inputDefMap["required"].(bool)
if ok && required {
var triggerNames []string
if hasSlashCommand {
triggerNames = append(triggerNames, "slash_command")
}
if hasLabelCommand {
triggerNames = append(triggerNames, "label_command")
}
triggerNamesPhrase := strings.Join(triggerNames, " and ")

return fmt.Errorf(
"on.workflow_dispatch.inputs.%s.required: true is not allowed when using %s; these triggers are dispatched automatically and cannot enforce required manual inputs; set required: false in workflow_dispatch.inputs",
inputName, triggerNamesPhrase,
Comment on lines +8 to +56
)
}
}

return nil
}
6 changes: 6 additions & 0 deletions pkg/workflow/compiler_validators.go
Original file line number Diff line number Diff line change
Expand Up @@ -185,6 +185,12 @@ func (c *Compiler) validateToolConfiguration(workflowData *WorkflowData, markdow
return formatCompilerError(markdownPath, "error", err.Error(), err)
}

// Validate workflow_dispatch required inputs with slash/label command triggers.
log.Printf("Validating workflow_dispatch input requirements for command triggers")
if err := validateCommandWorkflowDispatchInputs(workflowData); err != nil {
return formatCompilerError(markdownPath, "error", err.Error(), err)
}
Comment on lines +188 to +192

// Validate workflow-level concurrency group expression
log.Printf("Validating workflow-level concurrency configuration")
if workflowData.Concurrency != "" {
Expand Down
69 changes: 69 additions & 0 deletions pkg/workflow/slash_command_centralized_compile_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,3 +84,72 @@ tools:
require.Contains(t, compiled, "fromJSON(github.event.inputs.aw_context || '{}').trigger_label == 'triage'")
require.Contains(t, compiled, "fromJSON(github.event.inputs.aw_context || '{}').event_type == 'issues'")
}

func TestCompileWorkflow_SlashCommandRejectsRequiredDispatchInputs(t *testing.T) {
tmpDir := testutil.TempDir(t, "workflow-centralized-slash-dispatch-inputs-test")

markdownPath := filepath.Join(tmpDir, "scout.md")
content := `---
on:
slash_command:
name: scout
strategy: centralized
workflow_dispatch:
inputs:
topic:
description: "Research topic"
required: true
type: string
tools:
github:
allowed: [list_issues]
---

# Scout
`
require.NoError(t, os.WriteFile(markdownPath, []byte(content), 0644))

compiler := NewCompiler()
err := compiler.CompileWorkflow(markdownPath)
require.Error(t, err)
require.Contains(t, err.Error(), "on.workflow_dispatch.inputs.topic.required: true is not allowed when using slash_command")

lockPath := stringutil.MarkdownToLockFile(markdownPath)
_, statErr := os.Stat(lockPath)
require.Error(t, statErr)
require.True(t, os.IsNotExist(statErr))
}

func TestCompileWorkflow_LabelCommandRejectsRequiredDispatchInputs(t *testing.T) {
tmpDir := testutil.TempDir(t, "workflow-label-dispatch-inputs-test")

markdownPath := filepath.Join(tmpDir, "triage.md")
content := `---
on:
label_command:
name: triage
workflow_dispatch:
inputs:
topic:
description: "Research topic"
required: true
type: string
tools:
github:
allowed: [list_issues]
---

# Triage
`
require.NoError(t, os.WriteFile(markdownPath, []byte(content), 0644))

compiler := NewCompiler()
err := compiler.CompileWorkflow(markdownPath)
require.Error(t, err)
require.Contains(t, err.Error(), "on.workflow_dispatch.inputs.topic.required: true is not allowed when using label_command")

lockPath := stringutil.MarkdownToLockFile(markdownPath)
_, statErr := os.Stat(lockPath)
require.Error(t, statErr)
require.True(t, os.IsNotExist(statErr))
}
Loading