Skip to content

Go SDK: Fix the mixed language task handler binding semantic - #73648

Merged
jason810496 merged 6 commits into
apache:mainfrom
jason810496:fix/go-sdk/struct-arg-binding-strictness
Sep 29, 2026
Merged

jason810496 merged 6 commits into
apache:mainfrom
jason810496:fix/go-sdk/struct-arg-binding-strictness

Conversation

@jason810496

@jason810496 jason810496 commented Sep 24, 2026 •

Copy link
Copy Markdown
Member

Why

Sole-struct binding fails the task when an argument no field claims arrives, but silently zero-fills a field no argument supplies. Nothing checks the stub signature against the Go one, so both are the same kind of mismatch, and neither is worth failing a run over when fields bind by name.

How

Fields bind by name like keyword arguments, so neither direction shifts anything the handler reads. Both are now logged before the task runs, and the task runs. A call can be wrong both ways at once, so each direction is its own message:

  • Dag's call passed argument(s) the task handler does not declare
  • Task handler declares argument(s) the Dag's call did not pass

The same two strings are used by every lang SDK, so one query finds them across languages. Captured defaults are not reported, as before.

type ReportInput struct {
    Region    string `arg:"region_code"`
    Threshold float64
}
report(region_code=region)  # no threshold
# Threshold stays 0.0, warned: Task handler declares argument(s) the Dag's call did not pass

report(region_code=region, threshold=0.75, extra="x")
# before: task fails on the unclaimed "extra"
# now:    warned, task runs

Unchanged: flat positional bindings still fail either way, because a dropped or added positional argument shifts every later one.

A spec that arrives empty is treated as every field unfilled, since that is what an argless call like my_task() sends, so it is warned about and the task runs.

What

  • pkg/binding: the rules above, plus a log-capturing test helper.
  • adr/0006 and README.md: both stated the old behavior. The README is what go.rst points users to for the full binding rules.
  • airflow-core/adr/lang-sdk/0007: states the rule for every SDK, since the spec describes the stub signature and nothing checks it against the foreign handler's. Positional fails either way, named logs either way, and how much each runtime can see is a language difference rather than a rule difference.
  • example/bundle/taskflowbinding: two new example tasks, one per direction, both asserted end to end in airflow-e2e-tests. via_struct_more_args is passed an argument its struct does not declare; via_struct_fewer_args declares a field the stub has no parameter for and keeps its Go zero value. ViaStructUnmatchedArg is also renamed ViaStructDefaultArg, since it now only demonstrates a captured default no field claims.

Was generative AI tooling used to co-author this PR?

@FrankYang0529 FrankYang0529 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Overall LGTM. Thanks for the fix.

Comment thread go-sdk/pkg/binding/binding_test.go
@jason810496
jason810496 force-pushed the fix/go-sdk/struct-arg-binding-strictness branch from 3532d9a to 5286547 Compare September 27, 2026 14:42
@jason810496
jason810496 merged commit bc28bb0 into apache:main Sep 29, 2026
90 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants