Skip to content

fix(workflow): stamp the invocation id on an event a node built itself - #747

Open
kalenkevich wants to merge 1 commit into
mainfrom
fix/715-node-event-invocation-id
Open

fix(workflow): stamp the invocation id on an event a node built itself#747
kalenkevich wants to merge 1 commit into
mainfrom
fix/715-node-event-invocation-id

Conversation

@kalenkevich

@kalenkevich kalenkevich commented Aug 14, 2026

Copy link
Copy Markdown
Collaborator

enrichEvent (core/src/workflow/node_runner.ts:570) stamps author, nodeInfo.path, branch and isolationScope — and never invocationId. An Event a node hands back from createEvent() is passed through untouched (nodes/function_node.ts:222, base_node.ts:221), while every constructing branch does set it. createEvent defaults the field to '' rather than leaving it undefined (events/event.ts:176), so the event is persisted with a blank invocation id while every sibling event of the same invocation carries the real one:

task_A_node   invocationId = 'e-7fcd4d33-80fc-40bc-9e64-46fe899aac38'
router        invocationId = ''          <-- returns createEvent({route, output})
task_B_agent  invocationId = 'e-7fcd4d33-80fc-40bc-9e64-46fe899aac38'

The damage is to persisted data and to anything that groups by invocation. The Dev UI graph, for one, never highlights the edge into such a node: getWorkflowHighlights (dev/src/server/agent_graph.ts:489) breaks its backward walk on the first invocationId mismatch.

Reproduced on routes/branches, routes/function_node, data_handling/node_output, data_handling/routing_output, data_handling/structured_output and graphs/get_started. It is live in the repo's own fixtures too — tests/integration/workflows/route/agent.ts:44 and :62 both return a bare createEvent.

Notes for review

  • Tests the falsy value, not undefined, because createEvent writes ''.
  • Follows enrichEvent's own documented rule — only fill in what the node left unset — so a node can still set its own id.
  • The carry-forward workaround in rehydration_utils.ts:78-90 stays: sessions written before this change still hold '', and engine-minted interrupt events still exist. Its comment ("Not every node event carries an invocation id…") is this bug documented as a feature, and can be revisited separately.
  • Fixture-neutral: invocationId is in IGNORE_FIELDS (tests/integration/test_case_utils.ts:160-171) and is already regex-asserted at :239.

Tests

core/test/workflow/node_execution_test.ts — extends the existing "passes through an explicitly emitted Event" case, plus a new one asserting an id the node set itself is not clobbered. Full unit:core (3100) and workflow + docs integration (125) green.

Fixes #715

@AmaadMartin AmaadMartin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verified against source at head. The fix stamps child.invocationId only when the node left it unset, which matches enrichEvent's existing fill-when-unset rule. createEvent defaults the field to '' (events/event.ts:176), so the falsy guard is correct, and both tests match the harness (createIc sets 'inv-1'). CI is green on ubuntu, macOS and Windows.

// event a node built itself is persisted with a blank invocation id while
// every sibling event of the same invocation carries the real one. Test the
// falsy value, not `undefined`.
if (!event.invocationId) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit, optional. The docstring above (lines 562-568) still lists only author, branch and isolationScope as the fill-when-unset fields. Add invocationId to that list so the doc matches the code.

@AmaadMartin AmaadMartin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-verified at head db327be, unchanged since the prior approval. The fix stamps child.invocationId only when the event has none, which matches enrichEvent's fill-when-unset rule; createEvent defaults the field to '' (events/event.ts:176), so the falsy guard is correct. Both tests run through enrichEvent and pin the stamp and the no-clobber case. One cosmetic nit stays open: the docstring still omits invocationId. CI is green on all three runners.

`enrichEvent` stamps `author`, `nodeInfo.path`, `branch` and
`isolationScope`, but never `invocationId`. An `Event` a node hands back
from `createEvent()` is passed through untouched, and `createEvent`
defaults the field to `''` rather than leaving it undefined — so the
event is persisted with a blank invocation id while every sibling event
of the same invocation carries the real one.

The damage is to persisted data and to anything that groups by
invocation. The Dev UI graph, for one, never highlights the edge into
such a node: `getWorkflowHighlights` breaks its backward walk on the
first mismatch.

Reproduced on `routes/branches`, `routes/function_node`,
`data_handling/node_output`, `data_handling/routing_output`,
`data_handling/structured_output` and `graphs/get_started`.

The carry-forward workaround in `rehydration_utils` stays: sessions
written before this change still hold `''`, and engine-minted interrupt
events still exist.

Fixes #715
@kalenkevich
kalenkevich force-pushed the fix/715-node-event-invocation-id branch from db327be to 0c19117 Compare August 14, 2026 22:45

@AmaadMartin AmaadMartin left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-verified at head 0c19117. The fix stamps child.invocationId only when the event has none, which matches enrichEvent's fill-when-unset rule, and createEvent defaults the field to '' so the falsy guard is correct. The passthrough test fails against the unfixed code. The narration comment is gone. My earlier optional nit stays open: the docstring omits invocationId from the fill-when-unset list. CI was green at review time.

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.

Events a node returns via createEvent() are persisted with invocationId: ""

3 participants