🐛 Keep the process reference when a function task excepts - #810
Open
elinscott wants to merge 2 commits into
Open
🐛 Keep the process reference when a function task excepts#810elinscott wants to merge 2 commits into
elinscott wants to merge 2 commits into
Conversation
A @task.calcfunction or @task.workfunction that raised inside wg.run() left get_task_process() returning None for that task, even though the excepted node was stored and CALL-linked. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A skeptic pass found the process-reference recovery could itself fail in two ways: MultipleObjectsError when a task's call-link label repeats (e.g. inside a While zone), and a shadowed original exception when store_provenance=False left no node to recover. - Pick the newest matching CALL link instead of an exact-label lookup, so a repeated label no longer raises. - Wrap the whole recovery in its own try/except; any failure there (including no matching link at all) now falls through to the original exception instead of replacing it. - Thread the recovery's TaskState.FAILED into update_task_state's success flag instead of discarding it. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #810 +/- ##
==========================================
- Coverage 90.91% 90.88% -0.02%
==========================================
Files 46 46
Lines 3165 3178 +13
==========================================
+ Hits 2877 2888 +11
- Misses 288 290 +2 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
elinscott
added a commit
to elinscott/aiida-workgraph
that referenced
this pull request
Aug 14, 2026
elinscott
added a commit
to elinscott/aiida-workgraph
that referenced
this pull request
Aug 17, 2026
elinscott
added a commit
to elinscott/aiida-workgraph
that referenced
this pull request
Aug 17, 2026
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
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
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.
Fixes #809
Summary
When a
@task.calcfunctionor@task.workfunctionraises insidewg.run(),WorkGraphNode.get_task_process()returnsNonefor that task, even though AiiDA's provenance shows a process actually ran and excepted e.g.Changes
AiiDAFunctionTask.execute(aiida_workgraph/tasks/aiida.py) now recovers the excepted node from provenance via its call-link label whenrun_get_noderaises, instead of losing the reference.Whilezone reuses the same label on every iteration, so several CALL links can share it, and an exact-label lookup raisesMultipleObjectsErrorin that case.try/exceptand is strictly best-effort: any failure there — including finding no matching link at all, e.g. undermetadata={'store_provenance': False}, where nothing was ever stored — falls through to the original exception instead of replacing it, so the wrapped function's real error is never shadowed by a recovery-side lookup error.task_manager.py'sexecute_function_tasknow threads the recoveredTaskState.FAILEDintoupdate_task_state'ssuccessflag instead of discarding it, mirroring howexecute_process_taskalready uses its own returned state.AiiDAFunctionTask(@task.calcfunction/@task.workfunction) only. The plain@task()PyFunctionpath also callsrun_get_nodedirectly in its non-coroutine branch and is structurally exposed to the same defect, but it doesn't manifest in practice:aiida-pythonjob'spyfunction()decorator catches the wrapped Python exception internally and converts it to a non-zero exit code rather than letting it propagate throughrun_get_node, sorun_get_nodereturns normally there instead of raising. A matching hardening pass forPyFunctionTaskis worth tracking as a follow-up if that assumption ever changes, but is out of scope here.Testing
@task.calcfunction/@task.workfunction, parametrized over both decorators, now leavesget_task_process('fail1')returning the exceptedProcessNode(exit_status302, task stateFAILED, downstream taskSKIPPEDwith no process), and that node is the same one CALL-linked into provenance.Whilezone, where the same call-link label repeats across iterations, recovers the newest matching node instead of raisingMultipleObjectsError. The recovered pk is asserted equal tomax(pk)among CALL links carrying that exact label (filtered by label, not inferred from overall scheduling order, since sibling tasks interleave innode.called).metadata={'store_provenance': False}leaves nothing to recover (node.called == [],get_task_processreturnsNone); the original exception still reaches the engine's error log unshadowed by a secondary lookup error. This is checked by intercepting the message passed to the engine's logger directly, because the workgraph-levelexit_status/exit_messageare identical with or without the fix and so don't discriminate between "the original error" and "a different error that happened to also fail the task."while/if/zone/mapmodules pass unchanged.