How to Migrate Legacy Workflow Configurations to the Current Schema Format in gh-aw

Migrating legacy workflow configurations to the current schema format involves replacing loosely-typed front-matter maps with strongly-typed equivalents using the FrontmatterConfig struct and its parser helpers in the github/gh-aw repository.

The gh-aw workflow compiler now enforces a typed front-matter schema that provides compile-time safety and clearer error messages. If your workflows use legacy field names like runtimes, permissions, or plugins as loosely-typed maps, you should migrate them to the current schema format to ensure future compatibility and access to enhanced validation features.

Understanding the Legacy and Modern Schema Differences

Legacy Front-Matter Structure

Legacy workflows in gh-aw used loosely-typed YAML maps for configuration sections. These fields are still accepted but marked as deprecated:

  • runtimes: Map of runtime names to version specifications (often numeric)
  • permissions: Map of permission names to access levels or shorthand strings
  • plugins: Array of repository strings or mixed-type objects
  • mcp-servers: Unstructured map of server configurations

These legacy structures lack compile-time validation and can lead to ambiguous error messages during compilation.

Current Typed Schema (FrontmatterConfig)

The modern schema centers on the FrontmatterConfig struct defined in pkg/workflow/frontmatter_types.go (lines 88-158). This struct replaces loose maps with strongly-typed fields:

  • RuntimesTyped: Structured runtime configurations with validated version strings
  • PermissionsTyped: Enumerated permission sets with strict access level validation
  • PluginsTyped: Structured plugin definitions with explicit repository and token handling

The typed schema ensures that version numbers are normalized to strings, permissions are validated against allowed values, and plugin references are properly structured before the compiler processes the workflow.

Step-by-Step Migration Process

Step 1: Identify Legacy Fields in Your Workflow

Locate the front-matter section in your workflow files (the YAML block between the triple dashes at the top of the file). Check for these legacy field patterns:

---
name: example-workflow
runtimes:
  node: { version: 20 }        # numeric version

  python: { version: 3.11 }    # numeric version

permissions:
  contents: write
  issues: read
plugins:
  - org/legacy-plugin
---

If your workflow uses these structures, it requires migration to the current schema format.

Step 2: Convert to Typed Equivalents Using Parser Helpers

The gh-aw codebase provides parser helpers that handle the conversion logic automatically. When you process a workflow through the CLI (gh aw compile), the system invokes ParseFrontmatterConfig (located at pkg/workflow/frontmatter_types.go lines 200-262).

This function automatically:

  • Calls parseRuntimesConfig (lines 55-106) to convert numeric versions to canonical string representations
  • Calls parsePermissionsConfig (lines 9-66) to normalize shorthand permissions like read-all into explicit maps
  • Calls parsePluginsConfig (lines 69-112) to convert array syntax to the structured object format

You don't need to manually rewrite these sections if you use the programmatic migration approach.

Step 3: Serialize to Modern Format with ToMap()

After parsing legacy configurations into the typed structs, export them using the ToMap() method (implemented at pkg/workflow/frontmatter_types.go lines 89-172). This method:

  • Prefers typed fields (RuntimesTyped, PermissionsTyped, PluginsTyped) over legacy maps
  • Omits deprecated keys from the output
  • Emits the modern YAML layout that the compiler expects

When writing back to YAML, use the map returned by ToMap() to ensure your workflow files contain only the current schema format.

Practical Code Examples

Legacy Workflow Configuration Example

Here is a complete example of a legacy workflow front-matter that uses deprecated typing:

---
name: legacy-demo
runtimes:
  node: { version: 18 }
  python: { version: 3.10 }
permissions:
  contents: write
  issues: read
  pull-requests: write
plugins:
  - github/security-plugin
  - github/linter-plugin
---

Modern Workflow Configuration Example

The same configuration migrated to the current schema format:

---
name: modern-demo
runtimes:
  node: { version: "18" }
  python: { version: "3.10" }
permissions:
  contents: write
  issues: read
  pull-requests: write
plugins:
  repos:
    - github/security-plugin
    - github/linter-plugin
---

Note that the plugins section now uses the explicit repos key, and runtime versions are explicitly quoted strings (though the parser normalizes these automatically).

Automated Migration Using Go

If you need to batch-migrate multiple workflows, use the gh-aw parser directly in a Go program:

package main

import (
	"fmt"
	"os"

	"github.com/github/gh-aw/pkg/workflow"
	"go.yaml.in/yaml/v3"
)

func main() {
	// Read legacy workflow file
	raw, err := os.ReadFile("legacy-workflow.md")
	if err != nil {
		panic(err)
	}

	// Extract front-matter into map (simplified for example)
	var front map[string]any
	if err := yaml.Unmarshal(raw, &front); err != nil {
		panic(err)
	}

	// Parse into typed config - handles legacy field conversion automatically
	cfg, err := workflow.ParseFrontmatterConfig(front)
	if err != nil {
		panic(err)
	}

	// Export to modern format
	newMap := cfg.ToMap()
	out, _ := yaml.Marshal(newMap)
	fmt.Println(string(out))
}

This approach leverages ParseFrontmatterConfig (lines 200-262 in frontmatter_types.go) and ToMap (lines 89-172) to handle the migration logic without manual field mapping.

Key Source Files and Functions

Understanding the implementation details in github/gh-aw helps when debugging migration issues:

  • pkg/workflow/frontmatter_types.go (lines 88-158): Contains the FrontmatterConfig struct that defines the current schema with typed fields like RuntimesTyped, PermissionsTyped, and PluginsTyped.

  • pkg/workflow/frontmatter_types.go (lines 200-262): Implements ParseFrontmatterConfig, the main entry point for converting legacy maps into typed structs. This function orchestrates the sub-parsers.

  • pkg/workflow/frontmatter_types.go (lines 55-106): Defines parseRuntimesConfig, which normalizes runtime versions (converting integers to strings) and validates the configuration.

  • pkg/workflow/frontmatter_types.go (lines 9-66): Defines parsePermissionsConfig, handling both shorthand permissions (read-all, write-all) and detailed permission maps.

  • pkg/workflow/frontmatter_types.go (lines 69-112): Defines parsePluginsConfig, converting legacy array syntax to the structured PluginsTyped format.

  • pkg/workflow/frontmatter_types.go (lines 89-172): Implements ToMap, which serializes the typed configuration back to YAML, preferring modern keys and omitting legacy fields.

Summary

  • Legacy workflows use loosely-typed maps for runtimes, permissions, plugins, and mcp-servers that lack compile-time validation.
  • Modern schema uses the FrontmatterConfig struct with typed fields (RuntimesTyped, PermissionsTyped, PluginsTyped) defined in pkg/workflow/frontmatter_types.go.
  • Automated migration is available through ParseFrontmatterConfig, which handles edge cases like numeric version conversion and permission shorthand expansion.
  • Export modern YAML using ToMap() to ensure your workflows use only the current schema format without deprecated keys.
  • Validation occurs automatically during gh aw compile, surfacing clear errors for any remaining legacy constructs.

Frequently Asked Questions

What fields are considered legacy in gh-aw workflow configurations?

Legacy fields include runtimes, permissions, plugins, and mcp-servers when used as loosely-typed maps or arrays. According to the github/gh-aw source code in pkg/workflow/frontmatter_types.go, these fields are still accepted for backward compatibility but are marked as deprecated in favor of RuntimesTyped, PermissionsTyped, and PluginsTyped struct fields.

How does ParseFrontmatterConfig handle legacy field conversion?

ParseFrontmatterConfig (located at lines 200-262 in pkg/workflow/frontmatter_types.go) automatically detects legacy map structures and converts them to typed equivalents. It delegates to specialized sub-parsers: parseRuntimesConfig normalizes version numbers to strings, parsePermissionsConfig expands shorthand like read-all into explicit permission maps, and parsePluginsConfig converts array syntax to structured objects. This ensures legacy workflows compile without manual rewriting.

Can I mix legacy and modern schema fields in the same workflow?

While the parser in github/gh-aw maintains backward compatibility and will accept mixed field types, the ToMap() method (lines 89-172 in pkg/workflow/frontmatter_types.go) explicitly prefers typed fields over legacy maps when serializing. If you mix schemas, the exported result will favor modern keys, potentially dropping legacy data that doesn't map to typed equivalents. For consistent results, migrate completely to the typed schema rather than maintaining a hybrid approach.

Where is the FrontmatterConfig struct defined in the source code?

The FrontmatterConfig struct is defined in pkg/workflow/frontmatter_types.go at lines 88-158 in the github/gh-aw repository. This struct contains the modern typed fields including RuntimesTyped, PermissionsTyped, and PluginsTyped, along with the legacy field definitions for backward compatibility. The file also contains the associated parsing logic and the ToMap() serialization method used during migration.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →