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
11 changes: 0 additions & 11 deletions .github/skills/agentic-workflow-designer/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,13 +152,8 @@ Present a structured summary and ask for approval before generation.
| "link a sub-issue" | `link-sub-issue` |
| "add labels", "remove labels" | `add-labels`, `remove-labels` |
| "replace a label with another" | `replace-label` |
| "report missing data needed for the task" | `missing-data` (system type, auto-enabled) |
| "report unavailable or missing tool/permission" | `missing-tool` (system type, auto-enabled) |
| "signal task could not be completed due to infrastructure failure" | `report-incomplete` (system type, auto-enabled) |
| "nothing visible", "just analyze" | no safe outputs required |

> **System types** (`missing-data`, `missing-tool`, `report-incomplete`) are error-signaling outputs that are automatically available in every workflow without being declared in `safe-outputs:`. They emit structured infrastructure signals (not task results) and can be disabled explicitly (e.g. `missing-tool: false`) but should rarely be suppressed.

### Network Mapping

| User says... | Maps to |
Expand Down Expand Up @@ -317,14 +312,8 @@ After confirmation, generate one workflow file using the same skeleton style as
---
emoji: <emoji>
description: <brief description>
strict: true
on:
<trigger config>
max-turns: <integer or omit>
max-ai-credits: <integer or omit for default 1000>
max-daily-ai-credits: <integer or omit to leave disabled>
user-rate-limit: <object or omit>
models: <object or omit>
permissions:
contents: read
issues: read
Expand Down
138 changes: 138 additions & 0 deletions pkg/workflow/runtime_override_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
package workflow

import (
"maps"
"reflect"
"testing"
)

Expand Down Expand Up @@ -230,6 +232,142 @@ func TestApplyRuntimeOverrides(t *testing.T) {
}
}

func TestApplyRuntimeOverrides_KnownRuntimeActionOverrides(t *testing.T) {
var knownNode *Runtime
for _, runtime := range knownRuntimes {
if runtime.ID == "node" {
knownNode = runtime
break
}
}
if knownNode == nil {
t.Fatal("expected known node runtime to exist")
}
originalKnownNode := *knownNode
originalKnownNode.Commands = append([]string(nil), knownNode.Commands...)
originalKnownNode.ManifestFiles = append([]string(nil), knownNode.ManifestFiles...)
originalKnownNode.ExtraWithFields = maps.Clone(knownNode.ExtraWithFields)

requirements := map[string]*RuntimeRequirement{}

applyRuntimeOverrides(map[string]any{
"node": map[string]any{
"version": "22",
"action-repo": "custom/setup-node",
"action-version": "v5",
},
}, requirements)

nodeReq, ok := requirements["node"]
if !ok {
t.Fatal("expected node requirement to be added")
}
if nodeReq.Version != "22" {
t.Fatalf("expected version 22, got %s", nodeReq.Version)
}
if nodeReq.Runtime == knownNode {

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.

[/tdd] The identity check nodeReq.Runtime == knownNode tests the pointer, which is good — but the test only covers the case where both action-repo and action-version are specified. The known-runtime path branches on actionRepo != "" || actionVersion != ""; a test for the single-field case (e.g., only action-repo, no action-version) would fully specify the behaviour of the OR condition.

💡 Suggested additional test
func TestApplyRuntimeOverrides_KnownRuntimeActionRepoOnly(t *testing.T) {
    // ... look up knownNode as before ...
    requirements := map[string]*RuntimeRequirement{}
    applyRuntimeOverrides(map[string]any{
        "node": map[string]any{
            "action-repo": "custom/setup-node",
            // no action-version
        },
    }, requirements)
    nodeReq := requirements["node"]
    if nodeReq.Runtime == knownNode {
        t.Fatal("expected clone even when only action-repo is overridden")
    }
    if nodeReq.Runtime.ActionVersion != knownNode.ActionVersion {
        t.Fatalf("expected ActionVersion to be inherited, got %s", nodeReq.Runtime.ActionVersion)
    }
}

@copilot please address this.

t.Fatal("expected action overrides to clone the known runtime")
}
if nodeReq.Runtime.ActionRepo != "custom/setup-node" {
t.Fatalf("expected ActionRepo custom/setup-node, got %s", nodeReq.Runtime.ActionRepo)
}
if nodeReq.Runtime.ActionVersion != "v5" {
t.Fatalf("expected ActionVersion v5, got %s", nodeReq.Runtime.ActionVersion)
}
if knownNode.ActionRepo != "actions/setup-node" {
t.Fatalf("expected known runtime ActionRepo to remain actions/setup-node, got %s", knownNode.ActionRepo)
}
if knownNode.ActionVersion != "v6" {
t.Fatalf("expected known runtime ActionVersion to remain v6, got %s", knownNode.ActionVersion)
}
if !reflect.DeepEqual(*knownNode, originalKnownNode) {
t.Fatal("expected all known runtime fields to remain unchanged")
}
}

func TestApplyRuntimeOverrides_KnownRuntimeActionRepoOnly(t *testing.T) {
var knownNode *Runtime
for _, runtime := range knownRuntimes {
if runtime.ID == "node" {
knownNode = runtime
break
}
}
if knownNode == nil {
t.Fatal("expected known node runtime to exist")
}

originalKnownNode := *knownNode
originalKnownNode.Commands = append([]string(nil), knownNode.Commands...)
originalKnownNode.ManifestFiles = append([]string(nil), knownNode.ManifestFiles...)
originalKnownNode.ExtraWithFields = maps.Clone(knownNode.ExtraWithFields)

requirements := map[string]*RuntimeRequirement{}
applyRuntimeOverrides(map[string]any{
"node": map[string]any{
"action-repo": "custom/setup-node",
},
}, requirements)

nodeReq, ok := requirements["node"]
if !ok {
t.Fatal("expected node requirement to be added")
}
if nodeReq.Runtime == knownNode {
t.Fatal("expected action overrides to clone the known runtime")
}
if nodeReq.Runtime.ActionRepo != "custom/setup-node" {
t.Fatalf("expected ActionRepo custom/setup-node, got %s", nodeReq.Runtime.ActionRepo)
}
if nodeReq.Runtime.ActionVersion != knownNode.ActionVersion {
t.Fatalf("expected ActionVersion %s, got %s", knownNode.ActionVersion, nodeReq.Runtime.ActionVersion)
}
if !reflect.DeepEqual(*knownNode, originalKnownNode) {
t.Fatal("expected all known runtime fields to remain unchanged")
}
}

func TestApplyRuntimeOverrides_ExistingRequirementActionRepoOnlyClonesRuntime(t *testing.T) {
existingRuntime := &Runtime{
ID: "node",
ActionRepo: "actions/setup-node",
ActionVersion: "v4",
Commands: []string{"node --version"},
ManifestFiles: []string{"package.json"},
ExtraWithFields: map[string]string{"cache": "npm"},
}
originalExistingRuntime := *existingRuntime
originalExistingRuntime.Commands = append([]string(nil), existingRuntime.Commands...)
originalExistingRuntime.ManifestFiles = append([]string(nil), existingRuntime.ManifestFiles...)
originalExistingRuntime.ExtraWithFields = maps.Clone(existingRuntime.ExtraWithFields)

requirements := map[string]*RuntimeRequirement{
"node": {
Runtime: existingRuntime,
Version: "20",
},
}
applyRuntimeOverrides(map[string]any{
"node": map[string]any{
"action-repo": "custom/setup-node",
},
}, requirements)

nodeReq := requirements["node"]
if nodeReq.Runtime == existingRuntime {
t.Fatal("expected existing runtime to be cloned when applying action overrides")
}
if nodeReq.Runtime.ActionRepo != "custom/setup-node" {
t.Fatalf("expected ActionRepo custom/setup-node, got %s", nodeReq.Runtime.ActionRepo)
}
if nodeReq.Runtime.ActionVersion != "v4" {
t.Fatalf("expected ActionVersion v4, got %s", nodeReq.Runtime.ActionVersion)
}
if !reflect.DeepEqual(*existingRuntime, originalExistingRuntime) {
t.Fatal("expected existing runtime fields to remain unchanged")
}
}

func TestDetectRuntimeRequirementsWithOverrides(t *testing.T) {
tests := []struct {
name string

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.

New test only covers one of the two call sites of cloneRuntimeWithActionOverrides — the existing-requirement branch (line 95 in runtime_overrides.go) has zero coverage in this diff.

💡 Suggested fix

The test exercises the path where runtimeID is not yet in requirements (known-runtime branch, runtime_overrides.go:106). The other call site at line 95, where action overrides are applied to an already-existing RuntimeRequirement, is untested. A shallow-copy bug or nil-Runtime bug in that branch would be invisible.

Add a test that pre-populates requirements with a node entry (including a non-nil Runtime and a non-empty ExtraWithFields), then calls applyRuntimeOverrides with an action-repo override, and asserts:

  • the returned Runtime pointer is different from the pre-populated one (clone happened)
  • the overridden fields are applied
  • the original Runtime is not mutated

Expand Down
63 changes: 24 additions & 39 deletions pkg/workflow/runtime_overrides.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,31 @@ package workflow

import (
"fmt"
"maps"
"slices"
"strconv"
)

func cloneRuntimeWithActionOverrides(base *Runtime, actionRepo, actionVersion string) *Runtime {
if base == nil {
return nil
}

customRuntime := *base
customRuntime.Commands = slices.Clone(base.Commands)
customRuntime.ManifestFiles = slices.Clone(base.ManifestFiles)
customRuntime.ExtraWithFields = maps.Clone(base.ExtraWithFields)

if actionRepo != "" {
customRuntime.ActionRepo = actionRepo
}
if actionVersion != "" {
customRuntime.ActionVersion = actionVersion
}

return &customRuntime
}

// applyRuntimeOverrides applies runtime version overrides from frontmatter
func applyRuntimeOverrides(runtimes map[string]any, requirements map[string]*RuntimeRequirement) {
runtimeSetupLog.Printf("Applying runtime overrides for %d configured runtimes", len(runtimes))
Expand Down Expand Up @@ -70,27 +92,7 @@ func applyRuntimeOverrides(runtimes map[string]any, requirements map[string]*Run
// If action-repo or action-version is specified, create a custom Runtime
if actionRepo != "" || actionVersion != "" {
runtimeSetupLog.Printf("Applying custom action config for runtime %s: repo=%s, version=%s", runtimeID, actionRepo, actionVersion)
// Clone the existing runtime to avoid modifying the global knownRuntimes
customRuntime := &Runtime{
ID: existing.Runtime.ID,
Name: existing.Runtime.Name,
ActionRepo: existing.Runtime.ActionRepo,
ActionVersion: existing.Runtime.ActionVersion,
VersionField: existing.Runtime.VersionField,
DefaultVersion: existing.Runtime.DefaultVersion,
Commands: existing.Runtime.Commands,
ExtraWithFields: existing.Runtime.ExtraWithFields,
}

// Apply overrides
if actionRepo != "" {
customRuntime.ActionRepo = actionRepo
}
if actionVersion != "" {
customRuntime.ActionVersion = actionVersion
}

existing.Runtime = customRuntime
existing.Runtime = cloneRuntimeWithActionOverrides(existing.Runtime, actionRepo, actionVersion)
}
} else {
// Check if this is a known runtime
Expand All @@ -101,24 +103,7 @@ func applyRuntimeOverrides(runtimes map[string]any, requirements map[string]*Run
// Clone the known runtime if we need to customize it
if actionRepo != "" || actionVersion != "" {
runtimeSetupLog.Printf("Cloning known runtime %s with custom action config: repo=%s, version=%s", runtimeID, actionRepo, actionVersion)
runtime = &Runtime{
ID: knownRuntime.ID,
Name: knownRuntime.Name,
ActionRepo: knownRuntime.ActionRepo,
ActionVersion: knownRuntime.ActionVersion,
VersionField: knownRuntime.VersionField,
DefaultVersion: knownRuntime.DefaultVersion,
Commands: knownRuntime.Commands,
ExtraWithFields: knownRuntime.ExtraWithFields,
}

// Apply overrides
if actionRepo != "" {
runtime.ActionRepo = actionRepo
}
if actionVersion != "" {
runtime.ActionVersion = actionVersion
}
runtime = cloneRuntimeWithActionOverrides(knownRuntime, actionRepo, actionVersion)
} else {
runtimeSetupLog.Printf("Using known runtime %s as-is", runtimeID)
runtime = knownRuntime
Expand Down
Loading