Standardize error wrapping in compiler to preserve error chains#14435
Standardize error wrapping in compiler to preserve error chains#14435
Conversation
- Updated formatCompilerError signature to accept optional cause error - Wraps errors with %w when cause is provided (preserves error chains) - Creates new errors when cause is nil (validation errors) - Updated all 22 formatCompilerError calls in compiler.go - Updated agent_validation.go calls to formatCompilerError - Added comprehensive error wrapping tests - Verified error chain preservation with errors.Is Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
- Fixed linter issues: use require.ErrorIs for critical error chain checks - Fixed linter issues: use assert.NotErrorIs instead of assert.False - Removed unused errors import - Fixed agent_validation.go to not wrap os.PathError (avoid exposing internals) - Fixed compiler_orchestrator_frontmatter.go to not wrap os.PathError - All tests pass: build ✓ test-unit ✓ lint ✓ Co-authored-by: pelikhan <4175913+pelikhan@users.noreply.github.com>
|
@copilot what do error look like? |
Error messages maintain the same clear, user-friendly format with file location, line numbers, and helpful suggestions. Here are examples: Validation Error (Unknown Property): Import Error (File Not Found): Permission Warning (with suggestions): The key improvement is that error chains are now preserved internally with |
There was a problem hiding this comment.
Pull request overview
This PR updates compiler error handling to preserve error chains while keeping the existing console-formatted compiler error output, improving debuggability of compilation failures across the workflow compiler.
Changes:
- Extends
formatCompilerErrorto accept an optionalcauseand wrap it when provided. - Updates compiler and agent validation call sites to pass underlying errors (or
nilfor pure validation errors). - Adjusts frontmatter file-read error handling to avoid wrapping
os.PathError, and adds/updates unit tests around error formatting/wrapping.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| pkg/workflow/compiler.go | Adds optional error wrapping to formatCompilerError and updates many call sites to pass a cause. |
| pkg/workflow/agent_validation.go | Updates error formatting calls to include cause where appropriate. |
| pkg/workflow/compiler_orchestrator_frontmatter.go | Stops wrapping file read errors to avoid exposing os.PathError via error chains. |
| pkg/workflow/compiler_error_formatting_test.go | Updates existing tests for the new formatCompilerError signature and adds wrapping-related assertions. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| // Wrap the underlying error if provided (preserves error chain) | ||
| if cause != nil { | ||
| return fmt.Errorf("%s: %w", formattedErr, cause) | ||
| } |
There was a problem hiding this comment.
When cause != nil, fmt.Errorf("%s: %w", formattedErr, cause) will append cause.Error() to the user-facing formatted compiler message. Several call sites already embed the underlying error text into message (e.g., fmt.Sprintf("...: %v", err)), so this leads to duplicated error details like ...: <err>: <err>, and it can also re-expose low-level error strings even if the formatted message was intended to be the primary output. Consider changing the wrapping strategy so the formatted compiler error text remains the Error() string while still providing Unwrap() for the cause (or, alternatively, ensure call sites stop including %v/err.Error() in message whenever cause is provided).
| // Verify it's a new error (not wrapping anything) | ||
| // This is a validation error, so it should not wrap | ||
| dummyErr := fmt.Errorf("some other error") | ||
| assert.NotErrorIs(t, err, dummyErr, "Should not wrap any error when cause is nil") |
There was a problem hiding this comment.
This assertion doesn’t actually verify that a nil cause results in a non-wrapping error: dummyErr is unrelated, so assert.NotErrorIs(err, dummyErr) will always pass even if err did wrap something else. A more direct check would be to assert that errors.Unwrap(err) is nil (or that errors.Is(err, someUnderlyingSentinel) is false when you intentionally pass a sentinel as the cause in a separate case).
Error wrapping was inconsistent across compiler files—
compiler_jobs.gohad 16 instances whilecompiler.gohad zero. This broke error chains, making compilation failures harder to debug.Changes
Enhanced
formatCompilerErrorwith optional cause parameter:Wrapping behavior:
%wwhen cause is non-nil → preserves error chainsos.PathErrororyaml.TypeError→ avoids exposing internal implementation detailsUpdated error calls:
compiler.go: 22 calls now pass underlying errorsagent_validation.go: 3 calls updated (2 wrapped, 1 validation)compiler_orchestrator_frontmatter.go: 1 call fixed to not exposeos.PathErrorAdded tests:
errors.IsOriginal prompt
💬 We'd love your input! Share your thoughts on Copilot coding agent in our 2 minute survey.