Skip to content

🐛 Keep the process reference when a function task excepts - #810

Open
elinscott wants to merge 2 commits into
aiidateam:mainfrom
elinscott:fix/function-task-process-reference
Open

🐛 Keep the process reference when a function task excepts#810
elinscott wants to merge 2 commits into
aiidateam:mainfrom
elinscott:fix/function-task-process-reference

Conversation

@elinscott

Copy link
Copy Markdown
Collaborator

Fixes #809

Summary

When a @task.calcfunction or @task.workfunction raises inside wg.run(), WorkGraphNode.get_task_process() returns None for that task, even though AiiDA's provenance shows a process actually ran and excepted e.g.

from aiida.orm import Int
from aiida_workgraph import WorkGraph, task

@task.calcfunction()
def fails(x):
    raise ValueError('deliberate failure')

wg = WorkGraph()
wg.add_task(fails, 'fail1', x=Int(1))
wg.run()

wg.process.get_task_process('fail1')  # used to be None; after this PR returns the excepted ProcessNode

Changes

  • AiiDAFunctionTask.execute (aiida_workgraph/tasks/aiida.py) now recovers the excepted node from provenance via its call-link label when run_get_node raises, instead of losing the reference.
  • The recovery picks the newest matching CALL link rather than doing an exact-label lookup: a task inside a While zone reuses the same label on every iteration, so several CALL links can share it, and an exact-label lookup raises MultipleObjectsError in that case.
  • The whole recovery is wrapped in its own try/except and is strictly best-effort: any failure there — including finding no matching link at all, e.g. under metadata={'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's execute_function_task now threads the recovered TaskState.FAILED into update_task_state's success flag instead of discarding it, mirroring how execute_process_task already uses its own returned state.
  • Scope: this fixes AiiDAFunctionTask (@task.calcfunction / @task.workfunction) only. The plain @task() PyFunction path also calls run_get_node directly in its non-coroutine branch and is structurally exposed to the same defect, but it doesn't manifest in practice: aiida-pythonjob's pyfunction() decorator catches the wrapped Python exception internally and converts it to a non-zero exit code rather than letting it propagate through run_get_node, so run_get_node returns normally there instead of raising. A matching hardening pass for PyFunctionTask is worth tracking as a follow-up if that assumption ever changes, but is out of scope here.

Testing

  • Reproduced: a raising @task.calcfunction/@task.workfunction, parametrized over both decorators, now leaves get_task_process('fail1') returning the excepted ProcessNode (exit_status 302, task state FAILED, downstream task SKIPPED with no process), and that node is the same one CALL-linked into provenance.
  • Reproduced: a task re-run under a While zone, where the same call-link label repeats across iterations, recovers the newest matching node instead of raising MultipleObjectsError. The recovered pk is asserted equal to max(pk) among CALL links carrying that exact label (filtered by label, not inferred from overall scheduling order, since sibling tasks interleave in node.called).
  • Reproduced: metadata={'store_provenance': False} leaves nothing to recover (node.called == [], get_task_process returns None); 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-level exit_status/exit_message are 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."
  • 94 pre-existing tests across the engine, task, while/if/zone/map modules pass unchanged.

elinscott and others added 2 commits August 12, 2026 09:11
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-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 89.47368% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 90.88%. Comparing base (5cccb31) to head (9dca42f).

Files with missing lines Patch % Lines
src/aiida_workgraph/tasks/aiida.py 88.24% 2 Missing ⚠️
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.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

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
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WorkGraph loses the process reference for a failed task.calcfunction / workfunction

2 participants