Skip to content

fix(task-graph): cap bridgeSubGraphTaskEvents depth to prevent event amplification#601

Open
sroussey wants to merge 1 commit into
mainfrom
claude/beautiful-mayer-j1anwi
Open

fix(task-graph): cap bridgeSubGraphTaskEvents depth to prevent event amplification#601
sroussey wants to merge 1 commit into
mainfrom
claude/beautiful-mayer-j1anwi

Conversation

@sroussey

Copy link
Copy Markdown
Collaborator

Summary

bridgeSubGraphTaskEvents installs one re-emit listener per event type per nesting level. A deeply nested compound task (e.g. a MapTask containing a GraphAsTask containing a MapTask …) bridges every level independently, so a single inner task_progress becomes N parent emits before reaching any wire subscriber. Combined with downstream consumers that keep a bounded event log (the sec/builder ExecutionEventLog is capped at 10k events), sustained nested fan-out can silently evict legitimate events.

This PR adds a defense-in-depth depth cap with a default of 16 levels. Once exceeded, the bridge installs no listeners and emits a single warn log noting the cap hit. The cap is overridable via a new maxDepth parameter; depth is auto-derived from a symbol-keyed marker on the parent graph so existing call sites (GraphAsTaskRunner, WhileTask, IteratorTaskRunner, FallbackTaskRunner, GraphAsTask streaming path) compile unchanged.

Approach

Depth tracking lives on the graph instance via a Symbol.for("@workglow/task-graph/SubGraphEventBridge.depth") keyed property — chosen over threading a counter through every caller because it keeps the diff to one file of fix code plus one re-export. The teardown restores the previous marker so an independently-rooted later bridge of the same subgraph instance does not inherit a stale counter.

Files

  • packages/task-graph/src/task-graph/SubGraphEventBridge.ts — depth cap + warn log
  • packages/task-graph/src/common.ts — re-export bridgeSubGraphTaskEvents so the test can drive it directly
  • packages/test/src/test/task-graph/TaskCompleteEvent.test.ts — three new tests

Backwards compatibility

Patch-bump compatible: both new parameters have defaults; every existing caller still compiles. No behavior change for graphs nesting fewer than 16 bridges deep.

Test plan

  • bun scripts/test.ts graph vitest — 74 files, 728 tests passing (including 3 new)
  • bun run build:types — 36 successful, 36 total
  • New tests cover: default cap kicks in at depth 16; warn log fires with { depth, maxDepth } fields; maxDepth override is honored

Generated with Claude Code

https://claude.ai/code/session_01V3e3m8cMRy5stFhDzGmZrF


Generated by Claude Code

…amplification

bridgeSubGraphTaskEvents installs one re-emit listener per event
type per nesting level. A deeply nested compound task (e.g., a
MapTask containing GraphAsTask containing MapTask) turns one inner
task_progress into N parent emits before reaching any wire
subscriber. Downstream consumers with a bounded event log (the
sec/builder ExecutionEventLog is capped at 10k events) can evict
legitimate events under sustained nested fan-out. Add a default
depth cap of 16 with a logged drop once exceeded, and let callers
override via the new maxDepth parameter.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V3e3m8cMRy5stFhDzGmZrF
@github-actions

Copy link
Copy Markdown

Coverage Report

Status Category Percentage Covered / Total
🔵 Lines 62.51% 25332 / 40524
🔵 Statements 62.34% 26215 / 42046
🔵 Functions 63.75% 4815 / 7552
🔵 Branches 51.09% 12371 / 24214
File CoverageNo changed files found.
Generated in workflow #2624 for commit b76b6d1 by the Vitest Coverage Report Action

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.

2 participants