Problem
LocalMetadataProvider.filter_tasks_by_metadata matches metadata values with re.match, which anchors at the start but not the end:
# metaflow/plugins/metadata_providers/local.py:264
regex = re.compile(pattern)
...
and regex.match(meta.get("value", ""))
Task.parent_tasks / child_tasks build the pattern from foreach-execution-path, so for a foreach with 11 or more items the pattern middle:1 also matches the values middle:10 and middle:11.
Reproduction
Local metadata, 12 item foreach, start -> middle -> tail -> join -> end:
class ForeachPrefixFlow(FlowSpec):
@step
def start(self):
self.items = list(range(12))
self.next(self.middle, foreach="items")
# middle -> tail -> join -> end
python flow.py run --max-workers 4
Then through the Client API:
WRONG PARENTS ForeachPrefixFlow/<run>/tail/14 fep=middle:1 -> ['middle:1', 'middle:10', 'middle:11']
1 of 12 tail tasks have the wrong parent count
Only the task whose path is a string prefix of another is affected. Every other index resolves to exactly one parent.
The spin CLI turns this into a hard failure:
$ python flow.py spin ForeachPrefixFlow/<run>/tail/14
[.../tail/0] Internal error:
[.../tail/0] Step tail is not a join step but it gets multiple inputs.
[.../tail/0] Task failed.
Control, same run, a non colliding task:
$ python flow.py spin ForeachPrefixFlow/<run>/tail/15 # fep=middle:0
Task finished successfully.
Possible fix
regex.fullmatch instead of regex.match on that line looks like it covers it. The two deliberate pattern shapes seem unaffected:
".*" still matches everything, and it also short circuits earlier in the function
- the foreach join pattern
"<path>,.*" still matches deeper paths such as middle:1,inner:0
Applying that one change locally takes the run above from 1 of 12 tail tasks have the wrong parent count to 0 of 12, makes spin .../tail/14 finish successfully, and leaves the control task passing.
ServiceMetadataProvider forwards the pattern to /filtered_tasks?pattern=... rather than matching in process, so this is only about the local provider. Whether the same anchoring question applies on the service side is not something I can see from here.
Before I open a PR
plugins/metadata_providers/ is Core Runtime, and CONTRIBUTING asks for an acknowledged issue and an agreed approach first, so I would rather check than guess.
Is fullmatch the direction you want, or would you prefer the anchoring live in the pattern construction in client/core.py instead? Happy to write it either way. I have the reproduction flow and a unit test covering the prefix collision plus the .* and "<path>,.*" cases ready to go.
Problem
LocalMetadataProvider.filter_tasks_by_metadatamatches metadata values withre.match, which anchors at the start but not the end:Task.parent_tasks/child_tasksbuild the pattern fromforeach-execution-path, so for a foreach with 11 or more items the patternmiddle:1also matches the valuesmiddle:10andmiddle:11.Reproduction
Local metadata, 12 item foreach,
start -> middle -> tail -> join -> end:Then through the Client API:
Only the task whose path is a string prefix of another is affected. Every other index resolves to exactly one parent.
The
spinCLI turns this into a hard failure:Control, same run, a non colliding task:
Possible fix
regex.fullmatchinstead ofregex.matchon that line looks like it covers it. The two deliberate pattern shapes seem unaffected:".*"still matches everything, and it also short circuits earlier in the function"<path>,.*"still matches deeper paths such asmiddle:1,inner:0Applying that one change locally takes the run above from
1 of 12 tail tasks have the wrong parent countto0 of 12, makesspin .../tail/14finish successfully, and leaves the control task passing.ServiceMetadataProviderforwards the pattern to/filtered_tasks?pattern=...rather than matching in process, so this is only about the local provider. Whether the same anchoring question applies on the service side is not something I can see from here.Before I open a PR
plugins/metadata_providers/is Core Runtime, and CONTRIBUTING asks for an acknowledged issue and an agreed approach first, so I would rather check than guess.Is
fullmatchthe direction you want, or would you prefer the anchoring live in the pattern construction inclient/core.pyinstead? Happy to write it either way. I have the reproduction flow and a unit test covering the prefix collision plus the.*and"<path>,.*"cases ready to go.