Skip to content

Fix unusable strategy schema for custom jobs + add compiler extraction (Option B)#19405

Merged
pelikhan merged 7 commits intomainfrom
copilot/fix-strategy-schema-definition
Mar 3, 2026
Merged

Fix unusable strategy schema for custom jobs + add compiler extraction (Option B)#19405
pelikhan merged 7 commits intomainfrom
copilot/fix-strategy-schema-definition

Conversation

Copy link
Contributor

Copilot AI commented Mar 3, 2026

  • Fix strategy schema in pkg/parser/schemas/main_workflow_schema.json with proper matrix, fail-fast, max-parallel properties
  • Refactor strategy inline object into a named $defs/job_strategy type referenced via $ref
  • Add Strategy string field to Job struct in pkg/workflow/jobs.go
  • Extract strategy in buildCustomJobs() in pkg/workflow/compiler_jobs.go using YAML marshal (same pattern as permissions)
  • Render Strategy in renderJob() in pkg/workflow/jobs.go
  • Add TestBuildCustomJobsWithStrategy test for strategy round-trip compilation
  • Run make build after schema changes
  • Merge main (×2) and resolve conflicts (keep both TestBuildCustomJobsWithStrategy and TestBuildCustomJobsRunsOnForms)
  • Recompile all 165 workflow files
Original prompt

This section details on the original issue you should resolve

<issue_title>[plan] Fix or remove unusable strategy schema definition for custom jobs</issue_title>
<issue_description>## Objective

Fix the strategy field in the custom jobs JSON schema — currently defined as an empty object that blocks all valid usage — and decide whether to implement compiler support or clearly remove it.

Context

From discussion #19383 (MEDIUM-2): In pkg/parser/schemas/main_workflow_schema.json, the strategy property under .properties.jobs.additionalProperties.properties is:

"strategy": {
  "type": "object",
  "description": "Matrix strategy for the job",
  "additionalProperties": false
}

additionalProperties: false with no properties means only {} is valid. Any real matrix strategy (e.g., {matrix: {os: [ubuntu, windows]}}) would fail schema validation. The compiler also does not extract strategy at all.

Approach

Option A — Remove strategy from the schema (recommended if not implementing support):

  • Delete the strategy entry from the custom jobs schema properties
  • This is honest: the compiler doesn't support it, users won't be misled by autocomplete

Option B — Add proper schema definition + compiler extraction:

  • Define strategy with full matrix, include, exclude, fail-fast, max-parallel sub-fields
  • Extract strategy in buildCustomJobs() using YAML marshal (same pattern as container/services)
  • Add Strategy string field to Job struct and render it in jobs.go

Implement Option A (remove from schema) unless there's strong reason to implement full support. Compiler support can be added in a follow-up.

Files to Modify

  • pkg/parser/schemas/main_workflow_schema.json — remove or fix strategy under custom jobs properties
  • If Option B: pkg/workflow/compiler_jobs.go, pkg/workflow/jobs.go

Acceptance Criteria

  • Schema no longer defines strategy as an empty-object-only type
  • If removed: strategy in a custom job produces a schema validation error with a helpful message
  • If implemented: strategy round-trips correctly through compilation
  • make build run after schema changes
  • Run make agent-finish before committing

Generated by Plan Command for issue #discussion #19383 ·

  • expires on Mar 5, 2026, 10:49 AM UTC

Comments on the Issue (you are @copilot in this section)

@pelikhan Option B

💡 You can make Copilot smarter by setting up custom instructions, customizing its development environment and configuring Model Context Protocol (MCP) servers. Learn more Copilot coding agent tips in the docs.

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copilot AI changed the title [WIP] Fix empty strategy schema for custom jobs Fix unusable strategy schema for custom jobs + add compiler extraction (Option B) Mar 3, 2026
@pelikhan pelikhan marked this pull request as ready for review March 3, 2026 11:28
Copilot AI review requested due to automatic review settings March 3, 2026 11:28
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Fixes custom job strategy support by making the schema accept real strategy objects and ensuring the compiler extracts/renders strategy so it round-trips into the generated workflow YAML.

Changes:

  • Updated the main workflow JSON schema to allow strategy.matrix, strategy.fail-fast, and strategy.max-parallel for custom jobs.
  • Added Strategy to the Job model and rendered it in job YAML output.
  • Extracted strategy from custom job frontmatter during compilation and added a compiler test.

Reviewed changes

Copilot reviewed 4 out of 4 changed files in this pull request and generated 3 comments.

File Description
pkg/parser/schemas/main_workflow_schema.json Fixes the custom jobs strategy schema so non-empty strategy objects validate.
pkg/workflow/jobs.go Adds Job.Strategy and renders it after runs-on.
pkg/workflow/compiler_jobs.go Extracts strategy from custom jobs and formats it into job YAML.
pkg/workflow/compiler_jobs_test.go Adds a test exercising custom job strategy compilation output.

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

Comment on lines +497 to +515
// Extract strategy for custom jobs
if strategy, hasStrategy := configMap["strategy"]; hasStrategy {
if strategyMap, ok := strategy.(map[string]any); ok {
// Use goccy/go-yaml to marshal strategy
yamlBytes, err := yaml.Marshal(strategyMap)
if err != nil {
return fmt.Errorf("failed to convert strategy to YAML for job '%s': %w", jobName, err)
}
// Indent the YAML properly for job-level strategy
strategyYAML := string(yamlBytes)
lines := strings.Split(strings.TrimSpace(strategyYAML), "\n")
var formattedStrategy strings.Builder
formattedStrategy.WriteString("strategy:\n")
for _, line := range lines {
formattedStrategy.WriteString(" " + line + "\n")
}
job.Strategy = formattedStrategy.String()
}
}
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

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

The strategy extraction duplicates the same marshal/indent logic used for permissions just above. This kind of copy/paste tends to diverge over time (formatting, error handling, indentation rules). Consider factoring this into a small helper (e.g., marshalJobSection(name string, v any) (string, error)) and use it for both permissions and strategy to keep behavior consistent.

Copilot uses AI. Check for mistakes.
Copy link
Contributor

Choose a reason for hiding this comment

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

@copilot refactor the strategy type in the JSON schema

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Done in 2140137 — extracted the inline strategy object definition into a named $defs/job_strategy type and replaced the inline definition with "$ref": "#/$defs/job_strategy", following the same pattern as other reusable types in the schema.

Comment on lines +2252 to +2273
// Verify matrix_job has strategy section
if !strings.Contains(yamlStr, "matrix_job:") {
t.Error("Expected matrix_job in compiled output")
}
if !strings.Contains(yamlStr, "strategy:") {
t.Error("Expected strategy section in compiled output")
}
if !strings.Contains(yamlStr, "matrix:") {
t.Error("Expected matrix section in compiled output")
}
if !strings.Contains(yamlStr, "fail-fast: false") {
t.Error("Expected fail-fast: false in compiled output")
}
if !strings.Contains(yamlStr, "max-parallel: 2") {
t.Error("Expected max-parallel: 2 in compiled output")
}

// Verify simple_job has no strategy
if !strings.Contains(yamlStr, "simple_job:") {
t.Error("Expected simple_job in compiled output")
}
}
Copy link

Copilot AI Mar 3, 2026

Choose a reason for hiding this comment

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

This test only checks strings.Contains on the entire lock file, which is brittle and can give false positives (e.g., if "strategy:" appears elsewhere in the workflow) and it doesn't actually verify that simple_job doesn't get a strategy block. A more reliable assertion would be to yaml.Unmarshal the compiled lock YAML and then check that jobs.matrix_job.strategy exists with the expected subfields, and that jobs.simple_job has no strategy key.

Copilot uses AI. Check for mistakes.
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
@pelikhan
Copy link
Contributor

pelikhan commented Mar 3, 2026

@copilot merge main, recompile

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 3, 2026

@copilot merge main, recompile

Done in bed224d — merged main, resolved the conflict in compiler_jobs_test.go (kept both TestBuildCustomJobsWithStrategy and the new TestBuildCustomJobsRunsOnForms from main), and recompiled all 165 workflows.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw (http block)
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw cfg repository(owner: $owner, name:--show-toplevel git rev-�� --show-toplevel 64/pkg/tool/linux_amd64/vet /usr/bin/git get --local ache/go/1.25.0/x--show-toplevel git (http block)
  • https://api.github.com/repos/actions/ai-inference/git/ref/tags/v1
    • Triggering command: /usr/bin/gh gh api /repos/actions/ai-inference/git/ref/tags/v1 --jq .object.sha ithub/workflows/bot-detection.md on rkflow/js/**/*.json /../../.prettiergit erignore -f sh -c /git/ref/tags/v8 x_amd64/vet /snap/bin/sh (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v3
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v3 --jq .object.sha .js&#39; --ignore-path .prettierignore --log-level=error -tests ules/.bin/node l (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v5
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha get --local ache/go/1.25.0/x64/pkg/tool/linux_amd64/vet user.name (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha --write **/*.cjs /home/node_modules/.bin/sh **/*.json --ignore-path ../../../.pretti--show-toplevel sh -c &#34;prettier&#34; --write &#39;../../../**/*.json&#39; &#39;!../../../pkg/workflow/js/**/*.json&#39; --ignore-path ../.sed 64/pkg/tool/linuconfig /usr/bin/git (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v5 --jq .object.sha --show-toplevel ache/go/1.25.0/x../../../**/*.json /usr/bin/git w/js/**/*.json&#39; git 328029/b086/vet.rev-parse ache/go/1.25.0/x--show-toplevel git rev-�� --show-toplevel ache/go/1.25.0/x64/pkg/tool/linux_amd64/vet /usr/bin/git pkg/mod/github.cgit -o odules/npm/node_--show-toplevel git (http block)
  • https://api.github.com/repos/actions/checkout/git/ref/tags/v6
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha n-sa1 &gt; /dev/null &amp;&amp; debian-sa1 1 1 rev-parse 1/x64/bin/node (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha SameOutput960518279/001/stability-test.md mcp/markitdown rgo/bin/sh (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq .object.sha --show-toplevel ache/go/1.25.0/x64/pkg/tool/linux_amd64/vet /usr/bin/gh se 328029/b039/vet.rev-parse cfg gh api /repos/actions/upload-artifact/git/ref/tags/v7 --jq /usr/bin/git js/**/*.json&#39; --git 328029/b164/vet.rev-parse ache/go/1.25.0/x--show-toplevel git (http block)
  • https://api.github.com/repos/actions/download-artifact/git/ref/tags/v8
    • Triggering command: /usr/bin/gh gh api /repos/actions/download-artifact/git/ref/tags/v8 --jq .object.sha (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/download-artifact/git/ref/tags/v8 --jq .object.sha nition^{commit} (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/download-artifact/git/ref/tags/v8 --jq .object.sha ithub/workflows 328029/b263/vet.cfg k/_temp/uv-python-dir/bash OUTPUT -d 168.63.129.16 bash --no�� on&#39; --ignore-path ../../../.prettierignore owner /opt/hostedtoolcache/go/1.25.0/x64/pkg/tool/linux_amd64/vet 0 -j ACCEPT /opt/hostedtoolcache/go/1.25.0/x../../../**/*.json (http block)
  • https://api.github.com/repos/actions/github-script/git/ref/tags/v8
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha xterm-color x_amd64/compile /usr/bin/git (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha te &#39;scripts/**/*.js&#39; --ignore-path .prettierignore --log-level=e!../../../pkg/workflow/js/**/*.jgit (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/github-script/git/ref/tags/v8 --jq .object.sha --show-toplevel ache/go/1.25.0/x64/pkg/tool/linuconfig /opt/hostedtoolcache/node/24.13.1/x64/bin/node se 328029/b041/vet.rev-parse cfg /opt/hostedtoolcache/node/24.13.1/x64/bin/node /tmp�� github.event.issue.number ache/go/1.25.0/x-trimpath /usr/bin/git js/**/*.json&#39; --git (http block)
  • https://api.github.com/repos/actions/setup-go/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-go/git/ref/tags/v4 --jq .object.sha SameOutput960518279/001/stability-test.md ctions-build/main.go 1/x64/bin/node og.go og_test.go r nt_test.go t-ha�� ithub/workflows/audit-workflows.md build_command_test.go /opt/hostedtoolcache/go/1.25.0/x64/bin/gofmt -f owner=github -f /opt/hostedtoolcache/go/1.25.0/x64/bin/gofmt (http block)
  • https://api.github.com/repos/actions/setup-node/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/setup-node/git/ref/tags/v4 --jq .object.sha rite &#39;scripts/**/*.js&#39; --ignore-path .prettierignore --log-level=error 2&gt;&amp;1 mcp/notion 1/x64/bin/node (http block)
  • https://api.github.com/repos/actions/upload-artifact/git/ref/tags/v4
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v4 --jq .object.sha k/gh-aw/gh-aw/.github/workflows -buildtags tions/setup/js/node_modules/.bin/node l -ifaceassert -nilfunc git tion�� k/gh-aw/gh-aw/.github/workflows %(refname) /usr/bin/infocmp --merged 62596b054e68754brev-parse (http block)
  • https://api.github.com/repos/actions/upload-artifact/git/ref/tags/v7
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v7 --jq .object.sha (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v7 --jq .object.sha re --log-level=error 328029/b062/vet.cfg modules/@npmcli/run-script/lib/node-gyp-bin/node (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v7 --jq .object.sha se 328029/b034/vet.cfg cfg (http block)
  • https://api.github.com/repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq .object.sha (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/a70c5eada06553e3510ac27f2c3bda9d3705bccb
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/a70c5eada06553e3510ac27f2c3bda9d3705bccb --jq .object.sha (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/v1.0.0
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/v1.0.0 --jq .object.sha ignore-path ../../../.prettierignore cfg (http block)
  • https://api.github.com/repos/githubnext/agentics/git/ref/tags/
    • Triggering command: /usr/bin/gh gh api /repos/githubnext/agentics/git/ref/tags/# --jq .object.sha (http block)
  • https://api.github.com/repos/nonexistent/action/git/ref/tags/v999.999.999
    • Triggering command: /usr/bin/gh gh api /repos/nonexistent/action/git/ref/tags/v999.999.999 --jq .object.sha get cfg 64/pkg/tool/linux_amd64/vet http.https://gitnode (http block)

If you need me to access, download, or install something from one of these locations, you can either:

@pelikhan
Copy link
Contributor

pelikhan commented Mar 3, 2026

@copilot merge main and recompile

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
Copy link
Contributor Author

Copilot AI commented Mar 3, 2026

@copilot merge main and recompile

Done in 54ea865 — merged main (4 new commits including runs-on array/object fix and scope enum validation), resolved the conflict in compiler_jobs_test.go, and recompiled all 165 workflows.

Warning

Firewall rules blocked me from connecting to one or more addresses (expand for details)

I tried to connect to the following addresses, but was blocked by firewall rules:

  • https://api.github.com/graphql
    • Triggering command: /usr/bin/gh /usr/bin/gh api graphql -f query=query($owner: String!, $name: String!) { repository(owner: $owner, name: $name) { hasDiscussionsEnabled } } -f owner=github -f name=gh-aw (http block)
  • https://api.github.com/repos/actions/download-artifact/git/ref/tags/v8
    • Triggering command: /usr/bin/gh gh api /repos/actions/download-artifact/git/ref/tags/v8 --jq .object.sha (http block)
  • https://api.github.com/repos/actions/upload-artifact/git/ref/tags/v7
    • Triggering command: /usr/bin/gh gh api /repos/actions/upload-artifact/git/ref/tags/v7 --jq .object.sha (http block)
  • https://api.github.com/repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b
    • Triggering command: /usr/bin/gh gh api /repos/astral-sh/setup-uv/git/ref/tags/eac588ad8def6316056a12d4907a9d4d84ff7a3b --jq .object.sha (http block)
  • https://api.github.com/repos/github/gh-aw/git/ref/tags/a70c5eada06553e3510ac27f2c3bda9d3705bccb
    • Triggering command: /usr/bin/gh gh api /repos/github/gh-aw/git/ref/tags/a70c5eada06553e3510ac27f2c3bda9d3705bccb --jq .object.sha (http block)
  • https://api.github.com/repos/githubnext/agentics/git/ref/tags/
    • Triggering command: /usr/bin/gh gh api /repos/githubnext/agentics/git/ref/tags/# --jq .object.sha (http block)

If you need me to access, download, or install something from one of these locations, you can either:

Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
@pelikhan pelikhan merged commit cc464de into main Mar 3, 2026
77 checks passed
@pelikhan pelikhan deleted the copilot/fix-strategy-schema-definition branch March 3, 2026 20:18
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.

[plan] Fix or remove unusable strategy schema definition for custom jobs

3 participants