Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions pkg/parser/import_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,13 @@ type FormattedParserError struct {
func (e *FormattedParserError) Error() string { return e.formatted }
func (e *FormattedParserError) Unwrap() error { return e.cause }

// NewFormattedParserError creates a FormattedParserError with the given pre-formatted
// message string. Use this in external packages (e.g. pkg/workflow) to return an error
// that isFormattedCompilerError can detect without double-wrapping.
func NewFormattedParserError(formatted string) *FormattedParserError {
return &FormattedParserError{formatted: formatted}
Comment on lines +89 to +91
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

NewFormattedParserError always returns a FormattedParserError with a nil cause, which means external callers (like pkg/workflow) can't preserve the underlying error chain even though FormattedParserError exposes Unwrap(). Consider adding an additional constructor (or extending this one) that accepts an optional cause error and sets it, so callers can keep errors.Is/As behavior without reintroducing double-formatting.

Suggested change
// that isFormattedCompilerError can detect without double-wrapping.
func NewFormattedParserError(formatted string) *FormattedParserError {
return &FormattedParserError{formatted: formatted}
// that isFormattedCompilerError can detect without double-wrapping. An optional cause
// may be provided so callers can preserve errors.Is/errors.As traversal without
// re-formatting the wrapped error.
func NewFormattedParserError(formatted string, cause ...error) *FormattedParserError {
var wrappedCause error
if len(cause) > 0 {
wrappedCause = cause[0]
}
return &FormattedParserError{formatted: formatted, cause: wrappedCause}

Copilot uses AI. Check for mistakes.
}

// FormatImportError formats an import error as a compilation error with source location
func FormatImportError(err *ImportError, yamlContent string) error {
importErrorLog.Printf("Formatting import error: path=%s, file=%s, line=%d", err.ImportPath, err.FilePath, err.Line)
Expand Down
4 changes: 2 additions & 2 deletions pkg/parser/schema_compiler.go
Original file line number Diff line number Diff line change
Expand Up @@ -354,7 +354,7 @@ func validateWithSchemaAndLocation(frontmatter map[string]any, schemaJSON, conte

// Format and return the error
formattedErr := console.FormatError(compilerErr)
return errors.New(formattedErr)
return &FormattedParserError{formatted: formattedErr}
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

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

FormattedParserError supports error-chain preservation via Unwrap(), but this new return drops the underlying schema validation error by leaving cause unset. Consider setting cause: err here so callers can still use errors.Is/As on the original validation error when needed (while keeping the formatted display string).

This issue also appears on line 383 of the same file.

Suggested change
return &FormattedParserError{formatted: formattedErr}
return &FormattedParserError{formatted: formattedErr, cause: err}

Copilot uses AI. Check for mistakes.
}
}

Expand Down Expand Up @@ -382,7 +382,7 @@ func validateWithSchemaAndLocation(frontmatter map[string]any, schemaJSON, conte

// Format and return the error
formattedErr := console.FormatError(compilerErr)
return errors.New(formattedErr)
return &FormattedParserError{formatted: formattedErr}
}

// Fallback to the original error if we can't format it nicely
Expand Down
5 changes: 5 additions & 0 deletions pkg/workflow/compiler_error_formatter_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,11 @@ func TestIsFormattedCompilerError(t *testing.T) {
}, "imports:\n - missing.md"),
expected: true,
},
{
name: "error from parser.NewFormattedParserError is detected as formatted",
err: parser.NewFormattedParserError("workflow.md:5:3: error: bad value"),
expected: true,
},
{
name: "plain error is not formatted",
err: errors.New("plain error"),
Expand Down
4 changes: 2 additions & 2 deletions pkg/workflow/frontmatter_error.go
Original file line number Diff line number Diff line change
Expand Up @@ -73,11 +73,11 @@ func (c *Compiler) createFrontmatterError(filePath, content string, err error, f
context := errorStr[loc[0]+1:] // +1 to skip the leading newline
// Return VSCode-compatible format on first line, followed by source context only
frontmatterErrorLog.Print("Formatting error for VSCode compatibility")
return fmt.Errorf("%s\n%s", vscodeFormat, context)
return parser.NewFormattedParserError(fmt.Sprintf("%s\n%s", vscodeFormat, context))
}

// If we can't extract source context, return just the VSCode format
return fmt.Errorf("%s", vscodeFormat)
return parser.NewFormattedParserError(vscodeFormat)
}

// Fallback if we can't parse the line/col
Expand Down
Loading