Skip to content

fix: return DAG with errors instead of 404 for invalid YAML files#1975

Merged
yottahmd merged 2 commits intomainfrom
fix/dag-details-404-on-invalid-yaml
Apr 8, 2026
Merged

fix: return DAG with errors instead of 404 for invalid YAML files#1975
yottahmd merged 2 commits intomainfrom
fix/dag-details-404-on-invalid-yaml

Conversation

@yottahmd
Copy link
Copy Markdown
Collaborator

@yottahmd yottahmd commented Apr 8, 2026

Summary

  • loadDAG now checks BuildFlagAllowBuildErrors at all error paths (base config, empty file, sub-DAG naming), not just loadDAGsFromFile. Uses an errorDAG helper to match the existing LoadYAMLWithOpts pattern.
  • GetDAGDetails returns 404 only for os.ErrNotExist (file truly missing), not for parse/build errors. Other errors return 500.
  • getDAGDetailsData wraps the original error with %w instead of replacing it with a misleading "not found" message.

Test plan

  • New loader tests: WithAllowBuildErrors_YAMLSyntaxError and WithAllowBuildErrors_EmptyFile verify spec.Load returns a DAG with BuildErrors for broken/empty YAML
  • New handler test: TestGetDAGDetails_InvalidYAML_Returns200WithErrors verifies the API returns 200 with errors for an invalid YAML file
  • New handler test: TestGetDAGDetails_NonExistent_Returns404 verifies missing files still return errors
  • All existing tests pass (core/spec, persis/filedag, service/frontend/api/v1, cmd)

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Missing DAGs now correctly return a 404 error instead of 500, improving error clarity.
    • Enhanced error reporting when loading invalid DAG configurations.
  • Tests

    • Added test coverage for invalid YAML and missing DAG handling scenarios.

The GetDAGDetails API returned 404 for DAG files that exist but have
YAML parse errors. Two issues caused this:

1. loadDAG had inconsistent BuildFlagAllowBuildErrors coverage — only
   the loadDAGsFromFile error path checked the flag, while base config
   errors, empty files, and sub-DAG naming errors bypassed it. Added an
   errorDAG helper to handle all error paths consistently, matching the
   existing LoadYAMLWithOpts behavior.

2. GetDAGDetails mapped every error to 404 NotFound. Now it returns 404
   only when the file doesn't exist (os.ErrNotExist) and 500 for other
   failures. The error wrapping in getDAGDetailsData preserves the
   original error chain.
@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented Apr 8, 2026

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: c6b72914-8200-4894-8561-520329405cd0

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

The PR refactors error handling in the DAG loader by introducing a helper function to centralize BuildFlagAllowBuildErrors logic, and updates the API layer to distinguish missing DAG files (HTTP 404) from other errors (HTTP 500) with contextual error wrapping.

Changes

Cohort / File(s) Summary
Core DAG Loader Refactoring
internal/core/spec/loader.go, internal/core/spec/loader_test.go
Introduced errorDAG helper in loadDAG to consolidate branching logic for the BuildFlagAllowBuildErrors flag across multiple error paths. Added subtests for YAML syntax errors and empty files with the WithAllowBuildErrors() option.
API Error Handling
internal/service/frontend/api/v1/dags.go, internal/service/frontend/api/v1/dags_internal_test.go
Updated GetDAGDetails to check errors.Is(err, os.ErrNotExist) for missing DAG detection and map to HTTP 404, with error wrapping for dagStore.GetDetails failures. Added tests for invalid YAML DAG files and non-existent DAG lookups.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main change: the PR fixes error handling for invalid YAML files in DAG loading by returning DAGs with build errors instead of 404 responses, which is reflected in the code changes across loader.go, dags.go, and corresponding tests.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/dag-details-404-on-invalid-yaml

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@internal/core/spec/loader.go`:
- Around line 319-325: When synthesizing the error DAG inside the errorDAG
closure, preserve an explicitly provided DAG name from the loader options
instead of always using defaultName(filePath); update the Name field assignment
in errorDAG to use ctx.opts.Name (or the options' Name property) when non-empty,
falling back to defaultName(filePath) only if no override is present so
spec.Load(..., spec.WithName("override"), ...) retains the explicit name on
file-based failure paths.

In `@internal/service/frontend/api/v1/dags_internal_test.go`:
- Around line 155-160: The test currently only checks for a non-nil error from
api.GetDAGDetails; instead assert the error is a *localapi.Error and that its
HTTPStatus (and/or Code) equals 404 to enforce the 404 contract. Update the
assertion after calling api.GetDAGDetails to cast or use errors.As to a
*localapi.Error (referencing localapi.Error) and then require.Equal on
err.HTTPStatus (and optionally err.Code) to 404; keep the existing call to
openapi.GetDAGDetailsRequestObject and require helpers (e.g.,
require.True/require.Equal or require.IsType) to make the check explicit.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 54a25892-4f20-457b-a5ff-1d0bd07d7ec0

📥 Commits

Reviewing files that changed from the base of the PR and between 709c98b and 1b4aa21.

📒 Files selected for processing (4)
  • internal/core/spec/loader.go
  • internal/core/spec/loader_test.go
  • internal/service/frontend/api/v1/dags.go
  • internal/service/frontend/api/v1/dags_internal_test.go

Comment on lines +319 to +325
errorDAG := func(err error) (*core.DAG, error) {
if ctx.opts.Has(BuildFlagAllowBuildErrors) {
return &core.DAG{
Name: defaultName(filePath),
Location: filePath,
SourceFile: filePath,
BuildErrors: []error{err},
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Preserve WithName when synthesizing an error DAG.

Line 322 always falls back to defaultName(filePath), so spec.Load(..., spec.WithName("override"), spec.WithAllowBuildErrors()) loses the explicit DAG name only on file-based failure paths. LoadYAMLWithOpts keeps opts.Name, so this makes the two loaders inconsistent.

Suggested tweak
 	errorDAG := func(err error) (*core.DAG, error) {
 		if ctx.opts.Has(BuildFlagAllowBuildErrors) {
+			name := ctx.opts.Name
+			if name == "" {
+				name = defaultName(filePath)
+			}
 			return &core.DAG{
-				Name:        defaultName(filePath),
+				Name:        name,
 				Location:    filePath,
 				SourceFile:  filePath,
 				BuildErrors: []error{err},
 			}, nil
 		}
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
errorDAG := func(err error) (*core.DAG, error) {
if ctx.opts.Has(BuildFlagAllowBuildErrors) {
return &core.DAG{
Name: defaultName(filePath),
Location: filePath,
SourceFile: filePath,
BuildErrors: []error{err},
errorDAG := func(err error) (*core.DAG, error) {
if ctx.opts.Has(BuildFlagAllowBuildErrors) {
name := ctx.opts.Name
if name == "" {
name = defaultName(filePath)
}
return &core.DAG{
Name: name,
Location: filePath,
SourceFile: filePath,
BuildErrors: []error{err},
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/core/spec/loader.go` around lines 319 - 325, When synthesizing the
error DAG inside the errorDAG closure, preserve an explicitly provided DAG name
from the loader options instead of always using defaultName(filePath); update
the Name field assignment in errorDAG to use ctx.opts.Name (or the options' Name
property) when non-empty, falling back to defaultName(filePath) only if no
override is present so spec.Load(..., spec.WithName("override"), ...) retains
the explicit name on file-based failure paths.

Comment on lines +155 to +160
_, err := api.GetDAGDetails(context.Background(), openapi.GetDAGDetailsRequestObject{
FileName: "does-not-exist",
})
// Should return an error (which becomes a 404)
require.Error(t, err)
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Assert the 404 contract explicitly.

Line 159 only checks that an error exists, so this still passes if GetDAGDetails regresses back to a 500. Assert *localapi.Error and its HTTPStatus/Code instead.

Suggested assertion
 	_, err := api.GetDAGDetails(context.Background(), openapi.GetDAGDetailsRequestObject{
 		FileName: "does-not-exist",
 	})
-	// Should return an error (which becomes a 404)
-	require.Error(t, err)
+	var apiErr *localapi.Error
+	require.ErrorAs(t, err, &apiErr)
+	require.Equal(t, 404, apiErr.HTTPStatus)
+	require.Equal(t, openapi.ErrorCodeNotFound, apiErr.Code)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
_, err := api.GetDAGDetails(context.Background(), openapi.GetDAGDetailsRequestObject{
FileName: "does-not-exist",
})
// Should return an error (which becomes a 404)
require.Error(t, err)
}
_, err := api.GetDAGDetails(context.Background(), openapi.GetDAGDetailsRequestObject{
FileName: "does-not-exist",
})
var apiErr *localapi.Error
require.ErrorAs(t, err, &apiErr)
require.Equal(t, 404, apiErr.HTTPStatus)
require.Equal(t, openapi.ErrorCodeNotFound, apiErr.Code)
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@internal/service/frontend/api/v1/dags_internal_test.go` around lines 155 -
160, The test currently only checks for a non-nil error from api.GetDAGDetails;
instead assert the error is a *localapi.Error and that its HTTPStatus (and/or
Code) equals 404 to enforce the 404 contract. Update the assertion after calling
api.GetDAGDetails to cast or use errors.As to a *localapi.Error (referencing
localapi.Error) and then require.Equal on err.HTTPStatus (and optionally
err.Code) to 404; keep the existing call to openapi.GetDAGDetailsRequestObject
and require helpers (e.g., require.True/require.Equal or require.IsType) to make
the check explicit.

- Preserve explicit WithName option in errorDAG closure, falling back
  to defaultName(filePath) only when no override is set, matching
  LoadYAMLWithOpts behavior.
- Assert 404 contract explicitly in TestGetDAGDetails_NonExistent_Returns404
  by checking HTTPStatus and ErrorCode on the returned *Error.
@yottahmd yottahmd merged commit 867d606 into main Apr 8, 2026
6 of 7 checks passed
@yottahmd yottahmd deleted the fix/dag-details-404-on-invalid-yaml branch April 8, 2026 10:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant