Skip to content

Commit 69344b8

Browse files
Copilotpelikhan
andauthored
Reduce ParseWorkflow regression by optimizing frontmatter delimiter parsing (#31583)
* Initial plan * Plan ParseWorkflow regression investigation Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Optimize frontmatter delimiter checks in ParseWorkflow path Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * Tighten delimiter whitespace fast path in frontmatter parser Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * chore: merge main and recompile workflows Agent-Logs-Url: https://github.com/github/gh-aw/sessions/355f4283-dea2-411d-a18d-579513ada771 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> * chore: drop unrelated formatter-only file churn Agent-Logs-Url: https://github.com/github/gh-aw/sessions/355f4283-dea2-411d-a18d-579513ada771 Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com> Co-authored-by: Peli de Halleux <pelikhan@users.noreply.github.com>
1 parent 05694b6 commit 69344b8

3 files changed

Lines changed: 52 additions & 5 deletions

File tree

pkg/parser/frontmatter_content.go

Lines changed: 40 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ func ExtractFrontmatterFromContent(content string) (*FrontmatterResult, error) {
3232
}
3333

3434
// Check if file starts with frontmatter delimiter.
35-
if strings.TrimSpace(firstLine) != "---" {
35+
if !isFrontmatterDelimiterLine(firstLine) {
3636
log.Print("No frontmatter delimiter found, returning content as markdown")
3737
// No frontmatter, return entire content as markdown
3838
return &FrontmatterResult{
@@ -62,7 +62,7 @@ func ExtractFrontmatterFromContent(content string) (*FrontmatterResult, error) {
6262
}
6363
}
6464

65-
if strings.TrimSpace(content[lineStart:lineEnd]) == "---" {
65+
if isFrontmatterDelimiterLine(content[lineStart:lineEnd]) {
6666
frontmatterEndStart = lineStart
6767
markdownStart = nextCursor
6868
break
@@ -122,6 +122,44 @@ func ExtractFrontmatterFromContent(content string) (*FrontmatterResult, error) {
122122
}, nil
123123
}
124124

125+
// isFrontmatterDelimiterLine returns true when a line consists of "---" with optional surrounding whitespace.
126+
func isFrontmatterDelimiterLine(line string) bool {
127+
// Fast path for common delimiters.
128+
if line == "---" || line == "---\r" {
129+
return true
130+
}
131+
132+
// Fast path for ASCII-trimmable whitespace.
133+
start, end := 0, len(line)
134+
for start < end {
135+
switch line[start] {
136+
case ' ', '\t', '\n', '\r', '\v', '\f':
137+
start++
138+
default:
139+
goto leftTrimmed
140+
}
141+
}
142+
leftTrimmed:
143+
if start >= end {
144+
return false
145+
}
146+
for end > start {
147+
switch line[end-1] {
148+
case ' ', '\t', '\n', '\r', '\v', '\f':
149+
end--
150+
default:
151+
goto rightTrimmed
152+
}
153+
}
154+
rightTrimmed:
155+
if end-start == 3 && line[start] == '-' && line[start+1] == '-' && line[start+2] == '-' {
156+
return true
157+
}
158+
159+
// Fallback keeps previous behavior for uncommon Unicode whitespace.
160+
return strings.TrimSpace(line) == "---"
161+
}
162+
125163
// ExtractFrontmatterFromBuiltinFile is a caching wrapper around ExtractFrontmatterFromContent
126164
// for builtin virtual files. Because builtin files are registered once at startup and never
127165
// change, the parsed FrontmatterResult is identical across calls. This function caches the

pkg/parser/frontmatter_extraction_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,14 @@ This is a test workflow with empty frontmatter.`,
6464
},
6565
wantMarkdown: "# Content",
6666
},
67+
{
68+
name: "frontmatter delimiters with surrounding whitespace",
69+
content: " \t--- \t\r\non: push\r\n \t--- \t\r\n\r\n# Test Workflow\r\n",
70+
wantYAML: map[string]any{
71+
"on": "push",
72+
},
73+
wantMarkdown: "# Test Workflow",
74+
},
6775
}
6876

6977
for _, tt := range tests {

pkg/workflow/compiler_orchestrator_frontmatter.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -40,20 +40,21 @@ func (c *Compiler) parseFrontmatterSection(markdownPath string) (*frontmatterPar
4040
// Intentionally not wrapping to avoid exposing internal path details
4141
return nil, fmt.Errorf("failed to read file: %v", err) //nolint:errorlint // intentionally not wrapping to avoid exposing os.PathError
4242
}
43+
contentString := string(content)
4344

4445
log.Printf("File size: %d bytes", len(content))
4546

4647
// Parse frontmatter and markdown
4748
orchestratorFrontmatterLog.Printf("Parsing frontmatter from file: %s", cleanPath)
48-
result, err := parser.ExtractFrontmatterFromContent(string(content))
49+
result, err := parser.ExtractFrontmatterFromContent(contentString)
4950
if err != nil {
5051
orchestratorFrontmatterLog.Printf("Frontmatter extraction failed: %v", err)
5152
// Use FrontmatterStart from result if available, otherwise default to line 2 (after opening ---)
5253
frontmatterStart := 2
5354
if result != nil && result.FrontmatterStart > 0 {
5455
frontmatterStart = result.FrontmatterStart
5556
}
56-
return nil, c.createFrontmatterError(cleanPath, string(content), err, frontmatterStart)
57+
return nil, c.createFrontmatterError(cleanPath, contentString, err, frontmatterStart)
5758
}
5859

5960
if len(result.Frontmatter) == 0 {
@@ -62,7 +63,7 @@ func (c *Compiler) parseFrontmatterSection(markdownPath string) (*frontmatterPar
6263
}
6364

6465
// Preprocess schedule fields to convert human-friendly format to cron expressions
65-
if err := c.preprocessScheduleFields(result.Frontmatter, cleanPath, string(content)); err != nil {
66+
if err := c.preprocessScheduleFields(result.Frontmatter, cleanPath, contentString); err != nil {
6667
orchestratorFrontmatterLog.Printf("Schedule preprocessing failed: %v", err)
6768
return nil, err
6869
}

0 commit comments

Comments
 (0)