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 stringsplugins: Array of repository strings or mixed-type objectsmcp-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 stringsPermissionsTyped: Enumerated permission sets with strict access level validationPluginsTyped: 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 likeread-allinto 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 theFrontmatterConfigstruct that defines the current schema with typed fields likeRuntimesTyped,PermissionsTyped, andPluginsTyped. -
pkg/workflow/frontmatter_types.go(lines 200-262): ImplementsParseFrontmatterConfig, 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): DefinesparseRuntimesConfig, which normalizes runtime versions (converting integers to strings) and validates the configuration. -
pkg/workflow/frontmatter_types.go(lines 9-66): DefinesparsePermissionsConfig, handling both shorthand permissions (read-all,write-all) and detailed permission maps. -
pkg/workflow/frontmatter_types.go(lines 69-112): DefinesparsePluginsConfig, converting legacy array syntax to the structuredPluginsTypedformat. -
pkg/workflow/frontmatter_types.go(lines 89-172): ImplementsToMap, 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, andmcp-serversthat lack compile-time validation. - Modern schema uses the
FrontmatterConfigstruct with typed fields (RuntimesTyped,PermissionsTyped,PluginsTyped) defined inpkg/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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →