Hamilton: Required input missing / Cannot define function twice (tested)
Hamilton (formerly Sf-Hamilton/DAGWorks, now an Apache Software Foundation project) turns plain Python functions into a dataflow graph by matching parameter names to other functions' names. That name-matching is powerful but produces a few distinctive errors when the graph doesn't line up. We reproduced three of them on Hamilton 1.90.0 and measured the exact fix for each.
1. ValueError: Required input X not provided for nodes
Hamilton builds a dependency graph purely from function parameter names. A function parameter that doesn't match the name of any other function in your module is treated as an external input you must supply at execution time. We defined two functions where c depends on a parameter with no matching function, then called execute() without supplying it:
def a(b: int) -> int:
return b + 1
def c(a: int, missing_input: int) -> int:
return a + missing_input
dr = driver.Builder().with_modules(dataflow).build()
dr.execute(["c"], inputs={"b": 1})
ValueError: 1 errors encountered:
Error: Required input missing_input not provided for nodes: ['c'].
The fix we confirmed: add the missing name to the inputs dict passed to execute() — dr.execute(["c"], inputs={"b": 1, "missing_input": 5}) runs cleanly. If the parameter was meant to reference another function rather than be a runtime input, the real bug is usually a typo in the parameter name not matching the intended function's name exactly.
2. ValueError: Cannot define function X more than once
Hamilton builds its graph from every function across all modules passed to with_modules(). Two functions with the same name in different modules collide:
# mod_a.py
def shared(b: int) -> int:
return b + 1
# mod_b.py
def shared(b: int) -> int:
return b * 2
dr = driver.Builder().with_modules(mod_a, mod_b).build()
ValueError: Cannot define function shared more than once. Already defined by
function <function shared at 0x...> In case you want to override the
previous functions check out .allow_module_overrides() at:
https://hamilton.apache.org/reference/drivers/Driver/
The fix depends on intent. If the collision is accidental (two unrelated functions happen to share a name), rename one. If you deliberately want a later module to override an earlier one's node with the same name (a common pattern for swapping an implementation per environment), the error message names the exact method to call: .allow_module_overrides() on the Builder, before .with_modules(...).
3. A cyclic dependency does NOT give a friendly "cycle detected" error
We expected a clear cycle-detection message. Instead, two functions depending on each other by name produced Python's own generic recursion error, with Hamilton's own Slack-invite error banner on top but no cycle-specific wording underneath:
def x(y: int) -> int:
return y + 1
def y(x: int) -> int:
return x + 1
dr = driver.Builder().with_modules(cyclic).build()
dr.execute(["x"])
RecursionError: maximum recursion depth exceeded
This is worth knowing specifically because the error gives no hint that the real problem is a naming cycle, not actually-deep recursion in your own code. If you hit RecursionError from a Hamilton driver build/execute call with no deep call stack of your own, check for two function parameter names that resolve back to each other before assuming anything else — draw the dependency graph on paper if it's not obvious from reading the module.
Frequently asked questions
Why does Hamilton say an input is 'required' when I never declared it as one?
Hamilton infers the entire dependency graph from function parameter names — any parameter that doesn't match another function's name in your modules is automatically treated as an external input you must supply via the inputs dict at execute() time. We confirmed the fix is simply adding that name to inputs; if that wasn't the intent, the real bug is usually a typo in a parameter name that was meant to match another function.
Can two Hamilton functions have the same name in different modules?
Not by default — we confirmed Builder().with_modules() raises ValueError: Cannot define function X more than once the moment two loaded modules define the same function name. If you want a later module to intentionally replace an earlier node with the same name (e.g. swapping an implementation per environment), call .allow_module_overrides() on the Builder first — the error message names this method directly.
Does Hamilton detect cyclic dependencies between functions?
Not with a dedicated error — we measured that a two-function cycle (x depends on y, y depends on x) raises Python's generic RecursionError: maximum recursion depth exceeded instead of any Hamilton-specific cycle message. If you see a RecursionError from a Hamilton driver.build()/execute() call and your code isn't deeply recursive on its own, suspect a naming cycle first.
Is 'sf-hamilton' the same project as 'apache-hamilton'?
Yes — we installed via pip install sf-hamilton on 2026-10-03 and it pulled in apache-hamilton 1.90.0 as a dependency; the project (formerly developed by DAGWorks) is now distributed under the Apache Software Foundation. The sf-hamilton package name is kept for backward compatibility with existing installs/imports.
What's the actual mechanism that causes these errors — is it a config file or something else?
Neither — Hamilton has no separate config or wiring file for the graph. It reads your Python module(s) directly: each function becomes a graph node, and each parameter name is matched against other functions' names in the same set of modules. All three errors here (missing input, duplicate name, cycle) are direct consequences of that name-matching happening purely at the Python-identifier level.
Written with AI assistance. Measured on 2026-10-03 with apache-hamilton 1.90.0 (pip package sf-hamilton, now distributed under the Apache Software Foundation), Python 3.13, calling driver.Builder().with_modules(...).build().execute(...) directly. Every message and exception type below is copy-pasted output, not paraphrased. PlainHeaders is our extension; Hamilton is free/open-source.