fix(workflow): preserve saved workflow outputs and metadata - #298
Conversation
83c5b44 to
29e733a
Compare
- Buffer pre-adoption child previews and register loop linkage edges\n- Resolve child output workflow metadata from the root queue item
Review — PR #298Reviewed The backend side — event ancestry, nested call chains, workflow inheritance — is well designed and genuinely well covered for nesting. The blockers are concentrated in Blockers1.
|
- Bound output extraction and preview lifecycle state.\n- Preserve legacy loop-linkage behavior and regression coverage.\n- Document final metadata provenance and performance-baseline status.
Pfannkuchensack
left a comment
There was a problem hiding this comment.
Re-review — PR #298 after the follow-up commits
Re-reviewed 9ecaaa1e41 against the previously reviewed b58ee4df81, in the same worktree and against the same running instance (throwaway root, CPU, no models; model-free child workflows built from blank_image / img_blur / workflow_return).
All five blockers from the previous pass are resolved, and I verified the two runtime ones against real runs rather than only by reading. One new user-visible regression was introduced by the fix for blocker 4.
Checks on the updated head: backend 222 passed / 2 xfailed, webv2 444 passed, lint:tsc clean, architecture:check 70 passed, legacy web 29 passed.
Resolved
| Previous blocker | Fix | Evidence |
|---|---|---|
1 — a return key named image / collection / values dropped every sibling return |
outputImages.ts now dispatches on the output type discriminator instead of inferring structure from key names |
The workflow_return_output I recorded as the sole result of a root queue item now yields both images. In the running app the same workflow took the gallery from 4 to 6 items; previously only one of the two returned images arrived. |
| 2 — input images adopted as run results | no generic walker any more; a returned MetadataField or ControlField is not opened |
Both recorded repros now return only the real output. Regression cases still work: a batched call returning a list under one key, a plain image_output, and a collect_output of image fields. |
| 3 — three of four event builders untested | test_invocation_event_builders_preserve_nested_workflow_ancestry covers started / progress / complete / error |
— |
4 — call node's thumbnail and latestOutput came from child nodes |
solved twice over: child completed/failed no longer reach nodeExecution at all, and nodeExecutionStore.completed no longer falls back to the previous thumbnail |
new coordinator and store tests; see the regression below for the cost |
| 5 — metadata test did not establish its own claim | rewritten onto a real DiskImageFileStorage and a real ImageService via the public start(invoker); it now asserts the PNG the code under test actually wrote, plus images.get_workflow |
— |
Also addressed from the "worth fixing" list: _get_workflow_json is memoised on InvocationContextData; FRAME_GATE_LIMIT is replaced by per-wait gate maps that die with their wait; settleUntilRequestsStop now requires three consecutive unchanged samples; the trivial edgeTypes identity assertion is gone; ARCHITECTURE.md records the adapter's platform ownership together with the constraint that it must not become a recursive walker; and loop_linkage is carried through both the paste path and getConnectedInputNames, each with a test.
The re-recorded browser baseline now moves in both directions and the byte growth is proportional to the source addition, so the earlier concern about it is closed.
New: a failing called workflow now leaves the call node with no error state
invokeai/frontend/webv2/src/features/queue/runtime/coordinator.ts:429-435
After a run whose child workflow fails, the visible Call Saved Workflow node ends with no outcome icon and a neutral border — it looks like it never ran. Read from the DOM after a real failing run:
outcomeIcon: "none" borderColor: oklch(0.44 0.024 264.3) (neutral)
Root cause, from a socket capture of that run: the backend emits queue_item_status_changed(failed) before invocation_error, for the root item and for a plain non-call node alike.
queue_item_status_changed item 14 failed
invocation_error item 14 root 13 src workflow_return_value call_parent call-node
queue_item_status_changed item 13 failed
invocation_error item 13 src call-node
So settleWait runs first, settleRunning(…, 'failed') deletes the node state (nodeExecutionStore.ts:92-105), and the root's own invocation_error then fails the isTrackedEvent guard in handleNodeEvent and is dropped.
Before this update the child's error saved it: it arrived while the root wait was still alive and marked the node failed. Cross-check — I temporarily removed only the case 'failed' early return and repeated the identical run:
outcomeIcon: "Failed" borderColor: oklch(0.7061 0.0841 19.38) (red)
The guard is right for completed — that is what was putting intermediate child images on the node. For failed it costs the only failure indication the node has. Two ways out: keep routing child failed (only completed needs the guard), or deliver a settling item's node events before settleRunning clears them.
The new test does not let child terminal events settle the visible call node pins the current behaviour and asserts settleRunning is not called, but nothing covers the state the user is left with.
Worth noting separately: the underlying event ordering is pre-existing and affects every failing node in webv2, not just this one — a node's own invocation_error always arrives after its item's terminal status change, so nodeExecution.failed is effectively unreachable and the node state is deleted instead. The call node was only accidentally immune. That deserves its own issue independent of this PR.
Still open, minor
- The byte-identical duplicated assertion block at
invokeai/frontend/webv2/src/workbench/workbenchState.test.ts:3427-3441is still there. - QA step 2 in the PR description ("child progress and completion appear on the visible
Call Saved Workflownode") no longer matches the code — completion is now deliberately withheld from the node. - The error surfaced on the node is the child's raw message with no attribution to the child node or workflow (verified: the root item carries
Workflow return key must not be empty.verbatim). Only relevant once the error is shown again.
Verified working end to end
Load Workflowon an image produced inside a called workflow loads the calling workflow. Unchanged by this update and still a genuine fix overmain, where webv2 would have reported "missing".- A called workflow's exposed fields render as first-class inputs on the call node, including the legacy
exposedFields→ form migration, and edited values reach the child run. - Nested ancestry, progress routing, preview replay,
loop_linkagehandling, and the regenerated API artifacts all still check out.
|
@Pfannkuchensack Thanks for the review. The current PR description already says child terminal events do not replace root output. The broader status-before-invocation_error issue for ordinary non-call nodes is pre-existing and outside this PR’s scope and we can make an issue and resolve later. I'll address everything else shortly! |
- preserve root terminal errors when queue status settles a run\n- attribute called-workflow failures in the translated node tooltip\n- cover root settlement and rendered failure feedback
# Conflicts: # invokeai/frontend/webv2/performance/architecture-baseline.json # invokeai/frontend/webv2/performance/browser-baseline.json # invokeai/frontend/webv2/src/features/nodes/data/nodeExecutionStore.ts # invokeai/frontend/webv2/src/features/queue/runtime/coordinator.ts
# Conflicts: # invokeai/app/services/shared/invocation_context.py # tests/app/services/session_queue/test_session_queue_workflow_call_metadata.py
Cache workflow JSON across node contexts and bypass redundant root gate scans.\nTraverse output image results without intermediate collections and stop at the first thumbnail.
Summary
Saved-workflow execution now preserves child progress routing, returned images, workflow metadata, and graph semantics across queue submission, event delivery, preview replay, and recall.
Call Saved Workflowsource node, including nested calls.workflow_returnvalue without walking arbitrary metadata or input fields.invokeai_workflowmetadata through the real image service and disk-storage path.loop_linkageedges survive copy/paste, node migration, graph compilation, and workflow serialization.Related Issues / Discussions
QA Instructions
Focused follow-up validation:
lint:tsc,architecture:check, performance build, Ruff, and byte-identical OpenAPI regeneration.Manual behavior:
Call Saved Workflownode; child completion/error events do not replace root terminal output.image,collection, orvalues; confirm no-image returns clear stale thumbnails.invokeai_workflowdata throughImageServiceand disk storage.Review
The PR-298 follow-up review resolved findings 1-12: contract-aware output extraction, nested-input exclusion, direct event-builder coverage, root-owned terminal state, end-to-end metadata storage coverage, workflow JSON caching, request-settling stabilization, redundant-test cleanup, output-adapter boundary documentation, bounded preview gates, and legacy
loop_linkagehandling.The final review leaves no merge blockers in the implementation. Separate self-review passes covered correctness/contracts, lifecycle and architecture/performance, and test/product quality; independent review subagents were unavailable.
Compatibility / Rollout
invokeai_workflowand the child execution graph ininvokeai_graph. Webv2 recalls the parent workflow because it reproduces the image; legacy graph fallback and direct PNG readers can still observe the child graph.Checklist
What's Newcopy (if doing a release after this PR)