You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
fix: reject unknown with keys for the http executor (#3440) - #3570
The http executor silently drops unknown with keys. A typo such as header instead of headers passes dagu validate and sends the request without the headers, so the mistake is invisible until runtime.
Close the http executor config schema with additionalProperties: false so unknown keys fail at DAG parse time, matching the behavior already in place for xlsx, file, state, js, template, jq, and mail (#3440).
Every key the runtime actually reads (method, url, timeout, headers, query, form, files, body, silent, debug, format, output, json, skip_tls_verify) is already declared in the schema, so closing it rejects only genuinely unknown keys — no accepted configuration changes. The did you mean "headers" instead of "header"? hint comes for free from the registry's existing unknown-key suggestion.
Test plan
Added TestHTTPExecutor_RejectsUnknownWithKey: asserts a header typo is rejected with the closest-key hint, and that the valid headers key still passes.
go test ./internal/runtime/builtin/http/ passes.
Verified all conformance/spec045_http_request/testdata/*.yaml fixtures still load through spec.LoadYAML; the only two that error are the pre-existing missing_method / missing_url cases, which fail on the action's own required-field validation, not on schema closure.
The http executor now rejects unknown with keys at DAG parse time instead of silently dropping them, so a typo like header instead of headers fails validation with a closest-key suggestion rather than sending the request without headers.
Closes the http executor config schema with additionalProperties: false, matching xlsx, file, state, js, template, jq, and mail.
All keys read by the runtime are already declared, so no accepted configurations change.
Adds a regression test asserting the header typo is rejected with the did you mean "headers" instead of "header"? hint and the valid headers key still passes.
Pins the rejection in a conformance case and documents it in the http request spec.
Updates the spec builder config-passthrough test to use real http keys (silent, headers) instead of the placeholder key/map entries that no longer load.
Written for commit cd92f55. Summary will update on new commits.
Summary by CodeRabbit
Bug Fixes
HTTP executor configuration now rejects unsupported fields instead of silently accepting them. This helps catch typos, such as using header instead of headers, and provides a suggestion for the correct field. Valid headers configuration continues to be accepted, making configuration errors easier to identify and correct.
The http executor silently dropped unknown with keys, so a typo such as
header instead of headers passed dagu validate and sent the request
without the headers. Close its executor config schema with
additionalProperties: false so unknown keys fail at DAG parse time,
matching xlsx, file, state, js, template, jq, and mail.
All keys read by the runtime httpConfig (method, url, timeout, headers,
query, form, files, body, silent, debug, format, output, json,
skip_tls_verify) are already declared, so closing the schema rejects only
genuinely unknown keys. Add a regression test for the header typo, the
closest-key suggestion, and the accepted headers key.
Signed-off-by: CJstate <[email protected]>
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
Source mode: Auto
Repository: Unavailable
Branch: Unavailable
File: No configuration file from this repository applied
Commit: Unavailable
Layers: Organization UI
Review profile: ASSERTIVE
Plan: Advanced
Run ID: 349d1aa4-7c2f-4137-8892-c3b36393e07b
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
No actionable comments were generated in the recent review.
Walkthrough
The HTTP executor configuration schema now rejects undeclared properties. A test verifies that the misspelled header key is rejected with a suggestion to use headers, which is accepted.
Changes: HTTP configuration validation
Layer / File(s)
Summary
Close the HTTP configuration schema internal/runtime/builtin/http/config.go, internal/runtime/builtin/http/http_test.go
The schema disallows additional properties. The test checks that header is rejected with a suggestion to use headers, and that headers validates.
dagucloud/dagu#3439: Both changes reject unknown executor configuration keys; that PR covers js.run and template.render, while this change covers HTTP.
dagucloud/dagu#3558: Both changes close an executor configuration schema and preserve closest-key suggestions; that PR covers mail.
dagucloud/dagu#3559: Both changes reject unknown executor configuration keys; that PR covers jq, while this change covers HTTP.
Merge Risk: Minimal · previous review 01e89
The HTTP schema now rejects unknown configuration keys while preserving supported keys, with focused tests passing.
Pre-merge checks: 5
Check name
Status
Explanation
Title check
The title clearly identifies the main change: rejecting unknown configuration keys for the HTTP executor. It is concise and related to the changeset.
Description check
The description provides a detailed summary, explains the implementation, identifies the related issue, and documents regression tests and validation results. It does not use the exact Changes or Chec…
Docstring Coverage
No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentat…
Linked Issues check
Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check
Check skipped because no linked issues were found for this pull request.
Full details: Description check
Explanation
The description provides a detailed summary, explains the implementation, identifies the related issue, and documents regression tests and validation results. It does not use the exact Changes or Checklist headings, but the required change and test information is present.
Full details: Docstring Coverage
Explanation
No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff with cyclomatic complexity of at least 2. Documentation-only edits and deletions within surviving functions are included; unchanged diff context and fully deleted functions are excluded. Analyzed 0 functions across 2 files.
Finishing Touches
Generate unit tests (beta)
Create a new PR
Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
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.
File: No configuration file from this repository applied
Commit: Unavailable
Layers: Organization UI
Review profile: ASSERTIVE
Plan: Advanced
Run ID: 6d24758e-bde2-40e8-b4bc-207d02e3ffd7
Commits
Reviewing files that changed from the base of the PR and between 65934fe and 01e89fe.
Files selected for processing: 2
internal/runtime/builtin/http/config.go
internal/runtime/builtin/http/http_test.go
Included review availability: This review used your included allowance. Your plan provides up to 8 included reviews per hour; 7 remain after this review.
The http executor config schema now rejects unknown keys, so the
placeholder 'key'/'map' entries in the config-passthrough test no
longer load. Replace them with the real 'silent' and 'headers' keys,
which keeps the same scalar-and-map coverage.
Signed-off-by: CJstate <[email protected]>
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the fix — the diagnosis is right and the change is the minimal, idiomatic way to solve it.
Verdict: safe to merge
Correctness.httpConfig (internal/runtime/builtin/http/http.go:39-54) has exactly 14 mapstructure keys and the schema declares exactly those 14, so closing additionalProperties rejects only keys the runtime would have dropped. Verified against every http config in the repo — conformance/spec045_http_request/testdata/*.yaml, ui/e2e/fixtures/dags/visual-http-jq.yaml, ui/src/features/visual-editor/doc/__tests__/fixtures/map-steps.yaml, the spec tests, specs/045-http-request.md, skills/dagu/references/steptypes.md, README*.md, llms.txt — all use declared keys only. No Go code constructs an http executor config programmatically (ValidateExecutorConfig for http is reached only from internal/spec/step.go:638), and nothing merges DAG-level defaults into http configs (only redis/harness/kubernetes do).
Consistency.schemas/dag.schema.json already declares "additionalProperties": false on httpExecutorConfig, so editor-side validation has been rejecting these keys all along; this aligns the runtime parser with the published schema rather than inventing new strictness. The &jsonschema.Schema{Not: &jsonschema.Schema{}} form matches ~15 existing executors.
What should be fixed before merge
The commit message and PR body are factually wrong about two executors.jq is open — internal/runtime/builtin/jq/config.go:11-22 has no top-level AdditionalProperties and doesn't even declare filter/data, so closing it would break every jq.filter step. mail is also open (internal/runtime/builtin/mail/config.go:12-36, no AdditionalProperties). Only xlsx, file, state, js, template from your list are closed (plus wait, log, artifact, data, git, outputs, decision, action, kubernetes). Please amend the commit message, since that claim lands in history.
Non-blocking notes
Behavior change worth a release note. mapstructure matches field names case-insensitively by default, so with: { URL: ..., Method: GET } decoded fine before and now fails at parse time. The hint handles it gracefully (did you mean "url" instead of "URL"?), and dag.schema.json already rejected it, but a DAG that loads today can stop loading after upgrade. Intentional strictness increase, not a defect — just flag it in the release notes.
The script: config path is still permissive.executor: http with a JSON body in script: goes through decodeHTTPConfigFromString (http.go:396), which uses json.Unmarshal and silently ignores unknown fields; decodeHTTPConfig also keeps ErrorUnused: false. Typos in that (legacy) form remain invisible. Out of scope here, but the issue isn't fully closed for that path.
Performance / resource safety
None found by inspection; no load tests or profiling were run. Schema resolution is memoized per executor type (sync.Once + atomic.Pointer in internal/executor/registry/schema.go:130-146), so repeated DAG parsing by the scheduler resolves once per process. The added check costs one always-failing subschema evaluation per unknown key on the hot path only when a key is unknown. unknownKeyHint's Levenshtein scan (14 properties × short strings) runs on the error path only; even a permanently invalid DAG re-parsed on every scheduler poll pays a negligible cost. No new allocations retained, no goroutines, timers, files, descriptors or buffers introduced; the diff adds one small schema value per process.
Reviewed with claude-opus-5, reasoning effort: xhigh.
The reason will be displayed to describe this comment to others. Learn more.
Change itself is correct and matches the established pattern, but the commit message / PR body claim that this matches jq and mail is wrong:
internal/runtime/builtin/jq/config.go:11-22 has no top-level AdditionalProperties (and does not even declare filter/data) — jq is open.
internal/runtime/builtin/mail/config.go:12-36 has no AdditionalProperties — mail is open.
Closed siblings are xlsx, file, state, js, template (plus wait, log, artifact, data, git, outputs, decision, action, kubernetes). Please amend the commit message so the inaccurate list doesn't land in history.
The reason will be displayed to describe this comment to others. Learn more.
The reported symptom is that the typo passes dagu validate, i.e. it survives DAG loading. This test calls registry.ValidateExecutorConfig directly, which asserts the registry contract rather than the parse path, and the hint wording is already pinned by internal/executor/registry/schema_test.go:124-170.
Consider adding (or moving) the assertion next to TestStepSchemaV2_ActionHTTPRequest in internal/spec/step_v2_test.go:321, in the style of TestStepSchemaV2_ActionJSRunErrors (step_v2_test.go:489) and the template case at step_v2_test.go:616, which load YAML through LoadYAML and assert the full error. That covers the user-visible behavior end to end. Optional — the schema-level style does have a neighbor at http_test.go:337.
Record in the http spec that dagu validate rejects a with field the
action does not define, and pin the error and its suggestion with a
conformance case.
The reason will be displayed to describe this comment to others. Learn more.
Thanks for the follow-through on this — re-reviewed at HEAD cd92f55, including the two commits added after the earlier round.
Verdict: safe to merge
No code changes are required. The schema closure is correct and the two new commits are additive.
Re-review of earlier feedback. The one blocking item from the 2026-11-09 round was that the commit body of 01e89fe0 (and the PR description) claims the change matches jq and mail. That claim is still wrong at HEAD — internal/runtime/builtin/jq/config.go:11 and internal/runtime/builtin/mail/config.go:13 both still have no top-level AdditionalProperties, and jq must stay open (it doesn't declare filter/data, which ui/e2e/fixtures/dags/visual-http-jq.yaml uses). But the original worry — "it lands in history" — no longer applies: this repo squash-merges with squash_merge_commit_title: PR_TITLE and squash_merge_commit_message: BLANK, so the individual commit bodies are discarded and main gets only the PR title. The inaccuracy survives only in the PR description, which is still the record readers reach from the squashed (#3570). Worth a one-line edit before merging, but not a blocker. The other two earlier notes were non-blocking and remain accurate.
Correctness. Re-verified the key set: httpConfig (internal/runtime/builtin/http/http.go:40-53) has exactly 14 mapstructure keys and the schema declares exactly those 14, so the closure rejects only keys the runtime would have dropped. ValidateExecutorConfig("http", ...) is reached only from internal/spec/step.go:637; nothing in internal/ injects keys into an http ExecutorConfig.Config, and no DAG-level defaults merge into it. Every http config in the repo — the spec045 fixtures, ui/e2e/fixtures/dags/visual-http-jq.yaml, ui/src/features/visual-editor/doc/__tests__/fixtures/map-steps.yaml, ui/src/features/visual-editor/inspector/actionTemplate.ts:110, skills/dagu/references/steptypes.md, SCHEMA_MIGRATION.md, internal/cmd/example.go — uses declared keys only. schemas/dag.schema.json:3965 already had "additionalProperties": false on httpExecutorConfig, so this aligns the parser with the published schema rather than inventing new strictness, and the &jsonschema.Schema{Not: &jsonschema.Schema{}} form matches ~18 existing executors and is exactly what forbidsAdditionalProperties (internal/executor/registry/schema.go:92) detects, so the hint fires.
The new conformance assertion matches the real output: jsonschema-go emits unexpected additional properties %q (validate.go:509) and the registry appends ; <hint>, with all other wrapping as a prefix, so the pinned substring is contiguous.
CI. The aggregate "failed" state for this SHA is a superseded workflow run whose jobs were all cancelled. The live run for cd92f55 is green: conformance on ubuntu and windows (2 shards each), unit and intg on both OSes, Go Linter, Test compatibility, Build. I did not run tests locally.
Performance / resource safety
None found by inspection; no load tests or profiling were run. Schema resolution stays memoized per executor type (sync.Once + atomic.Pointer, internal/executor/registry/schema.go:130-146), so repeated parsing by the scheduler resolves once per process. The isFalsy branch in the library short-circuits to a single map scan over the instance's keys instead of evaluating a subschema per extra key, so the added validation is O(keys) with no allocation on the success path. unknownKeyHint's Levenshtein scan (14 short properties) runs only on the error path; even a permanently invalid DAG re-parsed on every scheduler poll pays a negligible cost. No goroutines, timers, files, descriptors, buffers or caches added — the diff contributes one small schema value per process plus one 7-line test fixture.
Before merging
Nothing blocking. One correction and three nits:
Fix the PR description (and ideally amend 01e89fe0): drop jq and mail from the "matching ..." list. Both are still open schemas, and jq has to stay that way.
Spec wording inverts which side is the misspelling — see inline on specs/045-http-request.md:35.
The spec's Status section (specs/045-http-request.md:7-9) still enumerates conformance coverage without the new unknown-field case.
The conformance row pins jsonschema-go's wording rather than Dagu's contract — see inline on conformance/spec045_http_request/http_test.go:146.
Reviewed with claude-opus-5, reasoning effort: xhigh.
The reason will be displayed to describe this comment to others. Learn more.
The suggestion clause reads backwards. The unknown field is the misspelling; the defined field is the correct spelling. As written, it says the defined field is the likely misspelling of the unknown one.
Suggested change
names the field and, when a defined field is a likely misspelling of it,
names the field and, when it is a likely misspelling of a defined field,
Also worth folding into this commit: the Status section at line 7-9 still lists conformance coverage as "... TLS verification, and required fields". The case added here — rejecting an undefined with field — belongs in that list, since this spec's Status is what tells a reader which clauses are pinned by conformance/.
The reason will be displayed to describe this comment to others. Learn more.
Two things about this row.
The struct field is named field, and the other two rows hold a field path (with.method). This row holds a whole rendered message instead. Renaming the field to wantStderr keeps the table honest now that it carries both shapes.
unexpected additional properties ["header"] is jsonschema-go's wording (jsonschema/validate.go:509), not Dagu's. A dependency bump can break this conformance case for a reason unrelated to Dagu behavior, and conformance is where that coupling costs the most. The half the spec actually promises is Dagu's own: the field name plus did you mean "headers" instead of "header"?. Asserting on header and the hint would pin the contract without pinning the library.
Related: the library builds that list by ranging a map, so the rendered order is nondeterministic. Fine for this single-key fixture, but a flake waiting to happen if a future fixture carries two unknown keys.
The reason will be displayed to describe this comment to others. Learn more.
Behavior change worth a release note. mapstructure matches field names case-insensitively by default (decodeHTTPConfig at http.go:386 sets only WeaklyTypedInput), so a legacy executor: {type: http} step whose config: used URL: or Method: decoded fine before and now fails at parse time. The action: http.request form is unaffected — requireActionStringField already demanded the lowercase keys.
schemas/dag.schema.json has carried additionalProperties: false on httpExecutorConfig all along, so editors have been flagging this, and the hint degrades gracefully (did you mean "url" instead of "URL"?). Intentional strictness, not a defect — but a DAG that loads today can stop loading after the upgrade, so it should be called out rather than discovered.
Separately, #3440 isn't fully closed by this: executor: http with a JSON body in script: still goes through decodeHTTPConfigFromString (http.go:394), which json.Unmarshals and silently ignores unknown fields. Out of scope here.
The reason will be displayed to describe this comment to others. Learn more.
Re-reviewed at HEAD cd92f55. Nothing has changed since the previous round — no commits were pushed and the PR description was not edited — so this is a confirmation pass rather than a new set of findings.
Verdict: safe to merge
No code changes are required.
Status of earlier feedback. All four open inline threads still apply verbatim at this SHA; I have not duplicated them here. Summary:
internal/runtime/builtin/http/config.go:13 — the PR description (and the body of 01e89fe0) claims this matches jq and mail. Still wrong at HEAD: internal/runtime/builtin/jq/config.go:11 and internal/runtime/builtin/mail/config.go:13 have no top-level AdditionalProperties, and jq must stay open (it does not declare filter/data). Non-blocking — the repo squash-merges with a blank commit message, so only the PR description carries the inaccuracy forward. Worth a one-line edit before merge.
specs/045-http-request.md:35 — the suggestion clause reads backwards: the unknown field is the misspelling, not the defined one.
specs/045-http-request.md:7-9 — the Status section still omits the unknown-field case from the list of conformance-covered clauses.
conformance/spec045_http_request/http_test.go:146 — the row pins jsonschema-go's wording (unexpected additional properties [...]) rather than Dagu's own contract, and the struct field is still named field while this row holds a whole message.
Items 2–4 are nits; none block merge.
Correctness (re-verified independently).httpConfig (internal/runtime/builtin/http/http.go:40-53) has exactly 14 mapstructure keys and the schema declares exactly those 14, so the closure rejects only keys the runtime would have dropped. ValidateExecutorConfig("http", ...) is reached only from internal/spec/step.go:637; no code path writes into an http ExecutorConfig.Config (every ExecutorConfig.Config[...] site in internal/ is a read), and no DAG-level defaults merge into it. Every http config in the repo uses declared keys only — the spec045 fixtures, SCHEMA_MIGRATION.md, internal/spec/builder_test.go:88, internal/spec/step_v2_cleanup_test.go:24, ui/e2e/fixtures/dags/visual-http-jq.yaml, ui/src/features/visual-editor/inspector/actionTemplate.ts:110, skills/dagu/references/steptypes.md. schemas/dag.schema.json:3965 already carried "additionalProperties": false on httpExecutorConfig, so this aligns the parser with the published schema instead of inventing new strictness, and the &jsonschema.Schema{Not: &jsonschema.Schema{}} form is what forbidsAdditionalProperties (internal/executor/registry/schema.go:92) detects, so the hint fires. closestKey gives header → headers at distance 1 against a threshold of 3, matching the pinned message.
The one behavior change still worth a release note: mapstructure matches field names case-insensitively, so a legacy executor: {type: http} step whose config: used URL: or Method: loaded before and now fails at parse time. Intentional strictness, and the hint degrades gracefully — but it should be announced rather than discovered.
CI. The aggregate failed state for cd92f55 comes from a superseded workflow run whose six jobs are all cancelled. The live run is green: 17 successes including conformance on ubuntu and windows (2 shards each, which covers the new unknown_field.yaml case), unit and intg on both OSes, Go Linter, Test compatibility, and Build. I did not run tests locally.
Performance / resource safety
None found by inspection; no load tests or profiling were run. Schema resolution stays memoized per executor type (sync.Once + atomic.Pointer, internal/executor/registry/schema.go:130-146), so repeated parsing by the scheduler resolves once per process. The closed-schema check is a single scan over the instance's keys, with no allocation on the success path. unknownKeyHint's Levenshtein scan (14 short properties) runs only on the error path; even a permanently invalid DAG re-parsed on every scheduler poll pays a negligible cost. No goroutines, timers, files, descriptors, buffers or caches added — the diff contributes one small schema value per process plus one 7-line fixture.
Before merging
Nothing blocking. Only the PR-description correction (item 1) is worth doing before the squash lands.
Reviewed with claude-opus-5, reasoning effort: xhigh.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
full-ciRun the Windows, conformance, and E2E checks on this pull request
2 participants
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
The
httpexecutor silently drops unknownwithkeys. A typo such asheaderinstead ofheaderspassesdagu validateand sends the request without the headers, so the mistake is invisible until runtime.Close the http executor config schema with
additionalProperties: falseso unknown keys fail at DAG parse time, matching the behavior already in place forxlsx,file,state,js,template,jq, andmail(#3440).Every key the runtime actually reads (
method,url,timeout,headers,query,form,files,body,silent,debug,format,output,json,skip_tls_verify) is already declared in the schema, so closing it rejects only genuinely unknown keys — no accepted configuration changes. Thedid you mean "headers" instead of "header"?hint comes for free from the registry's existing unknown-key suggestion.Test plan
TestHTTPExecutor_RejectsUnknownWithKey: asserts aheadertypo is rejected with the closest-key hint, and that the validheaderskey still passes.go test ./internal/runtime/builtin/http/passes.conformance/spec045_http_request/testdata/*.yamlfixtures still load throughspec.LoadYAML; the only two that error are the pre-existingmissing_method/missing_urlcases, which fail on the action's own required-field validation, not on schema closure.Github-Issue: #3440
Summary by cubic
The
httpexecutor now rejects unknownwithkeys at DAG parse time instead of silently dropping them, so a typo likeheaderinstead ofheadersfails validation with a closest-key suggestion rather than sending the request without headers.additionalProperties: false, matchingxlsx,file,state,js,template,jq, andmail.headertypo is rejected with thedid you mean "headers" instead of "header"?hint and the validheaderskey still passes.silent,headers) instead of the placeholderkey/mapentries that no longer load.Written for commit cd92f55. Summary will update on new commits.
Summary by CodeRabbit
headerinstead ofheaders, and provides a suggestion for the correct field. Validheadersconfiguration continues to be accepted, making configuration errors easier to identify and correct.