Skip to content

Add jobs.<job-id>.pre-steps support for custom and built-in jobs#27138

Merged
pelikhan merged 8 commits intomainfrom
copilot/add-pre-steps-support-to-workflow-jobs
Apr 19, 2026
Merged

Add jobs.<job-id>.pre-steps support for custom and built-in jobs#27138
pelikhan merged 8 commits intomainfrom
copilot/add-pre-steps-support-to-workflow-jobs

Conversation

Copy link
Copy Markdown
Contributor

Copilot AI commented Apr 19, 2026

Summary

  • add support for jobs.<job-id>.pre-steps in frontmatter
  • insert custom-job pre-steps after setup-injected steps and before regular steps (including checkout when present)
  • extend import merge behavior so conflicting jobs merge pre-steps deterministically (imported first, then main)
  • add built-in job support for jobs.<builtin-job>.pre-steps:
    • apply pre-steps to existing built-in jobs (including activation and pre_activation)
    • support pre-activation alias mapping to pre_activation
    • ensure built-in pre-steps are inserted after setup and before checkout/remaining built-in steps
  • prevent duplicate custom-job creation when jobs.<name> targets an already-generated built-in job
  • add tests for ordering, schema validation, import merge behavior, and built-in insertion order
  • update frontmatter docs for the new field and ordering guarantees

Validation

  • targeted tests passed:
    • TestCustomJobPreStepsAreInsertedBeforeCheckout
    • TestCustomJobPreStepsSchemaValidation
    • TestMergeJobsFromYAMLImports_MergesPreStepsOnConflict
    • TestBuiltinJobsPreStepsInsertionOrder
  • make agent-finish was run multiple times; it fails only on an external-network-dependent JS live API test in this environment:
    • frontmatter_hash_github_api.test.cjs (Blocked by DNS monitoring proxy)

@github-actions
Copy link
Copy Markdown
Contributor

Great work on adding pre-steps support to custom workflow jobs! 🎉 The implementation is clean and well-structured — the deterministic step ordering (setup → pre-steps → checkout → steps) is clearly documented and backed by solid test coverage across ordering, schema validation, and import merge scenarios. This PR looks ready for maintainer review.

Generated by Contribution Check · ● 1.3M ·

@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot add tests to check that "activation" pre-steps are properly inserted in activation job.

Same for all other "builtin" jobs

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 19, 2026

@copilot add tests to check that "activation" pre-steps are properly inserted in activation job.

Same for all other "builtin" jobs

Added coverage and handling for builtin jobs. New test TestBuiltinJobsPreStepsInsertionOrder verifies pre-steps insertion order for activation and pre_activation (including pre-activation alias), and compiler logic now applies jobs.<builtin>.pre-steps to existing builtin jobs. Addressed in commit 5ae4f39.

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/repos/actions/checkout/git/ref/tags/v6
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq [.object.sha, .object.type] | @tsv (http block)
    • Triggering command: /usr/bin/gh gh api /repos/actions/checkout/git/ref/tags/v6 --jq [.object.sha, .object.type] | @tsv ignore-path ../../../.prettierignore (http block)

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

Copilot AI changed the title Add jobs.<job-id>.pre-steps support Add jobs.<job-id>.pre-steps support for custom and built-in jobs Apr 19, 2026
@pelikhan pelikhan marked this pull request as ready for review April 19, 2026 13:53
Copilot AI review requested due to automatic review settings April 19, 2026 13:53
@github-actions
Copy link
Copy Markdown
Contributor

🧪 Test Quality Sentinel Report

Test Quality Score: 85/100

Excellent test quality

Metric Value
New/modified tests analyzed 4
✅ Design tests (behavioral contracts) 4 (100%)
⚠️ Implementation tests (low value) 0 (0%)
Tests with error/edge cases 2 (50%)
Duplicate test clusters 0
Test inflation detected No (1.19:1 ratio)
🚨 Coding-guideline violations 0

Test Classification Details

Test File Classification Issues Detected
TestBuiltinJobsPreStepsInsertionOrder pkg/workflow/compiler_builtin_presteps_test.go:14 ✅ Design Happy-path only; no invalid-input variant
TestCustomJobPreStepsAreInsertedBeforeCheckout pkg/workflow/compiler_jobs_test.go ✅ Design Happy-path only; no invalid-input variant
TestCustomJobPreStepsSchemaValidation pkg/workflow/compiler_jobs_test.go ✅ Design Error path covered ✅
TestMergeJobsFromYAMLImports_MergesPreStepsOnConflict pkg/workflow/compiler_orchestrator_workflow_test.go ✅ Design Conflict/merge edge case covered ✅; 2 bare assert calls missing message arg ⚠️

Flagged Tests — Requires Review

No tests are flagged as implementation tests. The following minor suggestions are offered to improve coverage and guideline compliance:

⚠️ TestBuiltinJobsPreStepsInsertionOrder — missing error / invalid-input coverage

Classification: Design test ✅
Issue: Only exercises the happy path — both activation and pre-activation jobs with valid pre-steps. There is no test case for what happens when pre-steps is an empty list on a built-in job, which would make it easy to silently regress if the merge logic skips empty slices incorrectly.
Suggested improvement: Add a sub-case (or a separate small test) that compiles a built-in job with an empty pre-steps: [] and asserts the step order remains correct with no injected extra steps.

⚠️ TestCustomJobPreStepsAreInsertedBeforeCheckout — missing error / invalid-input coverage

Classification: Design test ✅
Issue: Only the positive case is exercised. A nil or empty pre-steps slice on a custom job is not covered.
Suggested improvement: Add a table row or companion test for pre-steps: [] to ensure no steps are injected when the list is empty.

i️ TestMergeJobsFromYAMLImports_MergesPreStepsOnConflict — 2 bare assertions missing messages

Issue: Two assert calls lack descriptive message arguments, violating the project guideline:

assert.Len(t, result, 1)                          // ← should add message arg
assert.Equal(t, "ubuntu-latest", testJob["runs-on"]) // ← should add message arg

Suggested improvement:

assert.Len(t, result, 1, "Expected exactly one merged job")
assert.Equal(t, "ubuntu-latest", testJob["runs-on"], "Main job's runs-on should take precedence")

Language Support

Tests analyzed:

  • 🐹 Go (*_test.go): 4 tests — all unit (//go:build !integration) ✅
  • 🟨 JavaScript (*.test.cjs, *.test.js): 0 tests

Verdict

Check passed. 0% of new tests are implementation tests (threshold: 30%). All 4 tests verify observable behavioral contracts. No coding-guideline violations (no mock libraries, all new test files have correct build tags). Edge case coverage at 50% (2/4) is the one area with room to grow.


📖 Understanding Test Classifications

Design Tests (High Value) verify what the system does:

  • Assert on observable outputs, return values, or state changes
  • Cover error paths and boundary conditions
  • Would catch a behavioral regression if deleted
  • Remain valid even after internal refactoring

Implementation Tests (Low Value) verify how the system does it:

  • Assert on internal function calls (mocking internals)
  • Only test the happy path with typical inputs
  • Break during legitimate refactoring even when behavior is correct
  • Give false assurance: they pass even when the system is wrong

Goal: Shift toward tests that describe the system's behavioral contract — the promises it makes to its users and collaborators.

References:
§24630708772

🧪 Test quality analysis by Test Quality Sentinel · ● 619.1K ·

Copy link
Copy Markdown
Contributor

@github-actions github-actions Bot left a comment

Choose a reason for hiding this comment

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

✅ Test Quality Sentinel: 85/100. Test quality is excellent — 0% of new tests are implementation tests (threshold: 30%). All 4 new tests verify observable behavioral contracts with proper build tags and no mock library usage. Minor suggestions: add error/empty-input cases to the two ordering tests, and add missing assertion messages in TestMergeJobsFromYAMLImports_MergesPreStepsOnConflict.

Generated by Design Decision Gate workflow run 24630708768.
@github-actions
Copy link
Copy Markdown
Contributor

Commit pushed: 08149ab

🏗️ ADR gate enforced by Design Decision Gate 🏗️

@github-actions
Copy link
Copy Markdown
Contributor

🏗️ Design Decision Gate — ADR Required

This PR makes significant changes to core business logic (456 new lines in pkg/) but does not have a linked Architecture Decision Record (ADR).

AI has analyzed the PR diff and generated a draft ADR to help you get started:

📄 Draft ADR: docs/adr/27138-add-job-level-pre-steps-to-workflow-jobs.md

What to do next

  1. Review the draft ADR committed to your branch — it was generated from the PR diff
  2. Complete the missing sections — add context the AI couldn't infer, refine the decision rationale, and list real alternatives you considered
  3. Commit the finalized ADR to docs/adr/ on your branch
  4. Reference the ADR in this PR body by adding a line such as:

    ADR: ADR-27138: Add jobs.<job-id>.pre-steps Support for Custom and Built-in Jobs

Once an ADR is linked in the PR body, this gate will re-run and verify the implementation matches the decision.

Key design decisions captured in the draft

The draft ADR documents the following non-obvious decisions found in the diff:

  • Step insertion order: pre-steps are placed after id: setup and before actions/checkout@ in built-in jobs — the heuristic uses regex matching on serialized YAML strings, which is fragile if the setup step ID changes in the future
  • Import merge divergence: Unlike all other conflicting job fields (where main workflow takes precedence), pre-steps from both imported and main workflows are concatenated (imported first, then main) — this is a deliberate exception to the established merge policy
  • pre-activation alias: The frontmatter key pre-activation is silently mapped to the internal pre_activation job name — this implicit alias needs to be documented for future maintainers
  • Built-in job deduplication: Frontmatter entries targeting an existing built-in job are treated as customization-only rather than triggering new job creation

Why ADRs Matter

"AI made me procrastinate on key design decisions. Because refactoring was cheap, I could always say 'I'll deal with this later.' Deferring decisions corroded my ability to think clearly."

ADRs create a searchable, permanent record of why the codebase looks the way it does. Future contributors (and your future self) will thank you.


📋 Michael Nygard ADR Format Reference

An ADR must contain these four sections to be considered complete:

  • Context — What is the problem? What forces are at play?
  • Decision — What did you decide? Why?
  • Alternatives Considered — What else could have been done?
  • Consequences — What are the trade-offs (positive and negative)?

All ADRs are stored in docs/adr/ as Markdown files numbered by PR number (e.g., 27138-add-job-level-pre-steps-to-workflow-jobs.md for PR #27138).

🔒 This PR cannot merge until an ADR is linked in the PR body.

References: §24630708768

🏗️ ADR gate enforced by Design Decision Gate 🏗️ · ● 141.4K ·

Copy link
Copy Markdown
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

Adds support for jobs.<job-id>.pre-steps in frontmatter, including deterministic import-merging and insertion into both custom jobs and selected built-in jobs.

Changes:

  • Merge conflicting imported/main job pre-steps deterministically (imported first, then main).
  • Insert pre-steps for custom jobs (after compiler-injected setup step(s), before the job’s regular steps).
  • Add built-in job pre-steps insertion support (including pre-activation alias mapping) plus tests and docs updates.
Show a summary per file
File Description
pkg/workflow/workflow_import_merge.go Adds deterministic merge logic for pre-steps when imported and main jobs conflict.
pkg/workflow/compiler_pre_activation_job.go Allows pre-steps in pre-activation custom field validation (handled elsewhere for insertion).
pkg/workflow/compiler_orchestrator_workflow_test.go Adds unit test for deterministic pre-steps merge behavior.
pkg/workflow/compiler_jobs.go Implements custom-job and built-in-job pre-steps insertion, plus duplicate built-in job handling changes.
pkg/workflow/compiler_jobs_test.go Adds tests for custom job pre-steps ordering and schema validation; adds step-order helper.
pkg/workflow/compiler_builtin_presteps_test.go Adds test verifying built-in job pre-steps insertion ordering and alias handling.
pkg/parser/schemas/main_workflow_schema.json Extends schema to support jobs.<job-id>.pre-steps.
docs/src/content/docs/reference/frontmatter.md Documents pre-steps and ordering guarantees.

Copilot's findings

Tip

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

  • Files reviewed: 9/9 changed files
  • Comments generated: 3

Comment on lines +832 to +861
for jobName, jobConfig := range data.Jobs {
targetJobName := jobName
if jobName == "pre-activation" {
targetJobName = string(constants.PreActivationJobName)
}

job, exists := c.jobManager.GetJob(targetJobName)
if !exists {
continue
}

configMap, ok := jobConfig.(map[string]any)
if !ok {
return fmt.Errorf("jobs.%s must be an object, got %T", jobName, jobConfig)
}
if _, hasPreSteps := configMap["pre-steps"]; !hasPreSteps {
continue
}

preSteps, err := c.extractPinnedJobSteps("pre-steps", jobName, configMap, data)
if err != nil {
return fmt.Errorf("failed to process pre-steps for built-in job '%s': %w", jobName, err)
}
if len(preSteps) == 0 {
continue
}

job.Steps = insertPreStepsAfterSetupBeforeCheckout(job.Steps, preSteps)
compilerJobsLog.Printf("Inserted %d pre-steps into built-in job '%s'", len(preSteps), targetJobName)
}
Comment on lines +501 to +506
// Built-in jobs are already created before buildCustomJobs; treat jobs.<builtin>
// entries as customization-only and do not create duplicate jobs.
if _, exists := c.jobManager.GetJob(jobName); exists {
compilerJobsLog.Printf("Skipping jobs.%s (built-in job already exists)", jobName)
continue
}
Comment on lines +899 to +928
func assertStepOrderInSection(t *testing.T, section string, orderedSteps ...string) {
t.Helper()

prev := -1
for _, step := range orderedSteps {
idx := indexInNonCommentLinesInSection(section, step)
if idx == -1 {
t.Fatalf("Expected step %q in section:\n%s", step, section)
}
if prev >= idx {
t.Fatalf("Expected step order %v in section, but %q appeared at %d after previous index %d\n%s",
orderedSteps, step, idx, prev, section)
}
prev = idx
}
}

func indexInNonCommentLinesInSection(content string, target string) int {
lines := strings.Split(content, "\n")
for i, line := range lines {
trimmed := strings.TrimSpace(line)
if strings.HasPrefix(trimmed, "#") {
continue
}
if strings.Contains(line, target) {
return i
}
}
return -1
}
@pelikhan
Copy link
Copy Markdown
Collaborator

@copilot recompile

Copy link
Copy Markdown
Contributor Author

Copilot AI commented Apr 19, 2026

@copilot recompile

Ran make recompile on this branch; it completed successfully and produced no file changes, so no new commit was needed. Current head: 5ae4f39.

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/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, .object.type] | @tsv (http block)

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

@pelikhan pelikhan merged commit 1b80b46 into main Apr 19, 2026
25 checks passed
@pelikhan pelikhan deleted the copilot/add-pre-steps-support-to-workflow-jobs branch April 19, 2026 14:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants