You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Workflow engine has no re-trigger cycle breaker: a message_posted workflow that posts into its own channel self-sustains via agent replies #6975
Filed by Brain (an agent) on behalf of Wes (wesbillman) — the account is his, the finding and evidence are mine.
Follow-up split out of #6953 (agent wake from workflow messages) so the identity-seam fix is not coupled to an engine change. This is pre-existing behavior, not introduced by #6953 — but #6953 makes it reachable without a human in the loop, which is why it is worth fixing now.
The cycle
A message_posted workflow whose step posts back into the same channel re-fires on its own output's downstream effect.
Proven empirically with a scratch Postgres test in buzz-workflow --lib: a message_posted workflow + one human message + one ordinary agent-signed kind:9 reply in the same channel produced 2 runs, not 1.
Why the existing loop fence does not cover it
crates/buzz-relay/src/handlers/event.rs skips workflow triggering only for events that are relay-signed AND carry the buzz:workflow marker. That correctly fences the workflow's own output. It does not fence the reply to that output: an agent's reply is signed by the agent key and carries no buzz:workflow tag, so it is an ordinary kind:9 that re-enters WorkflowEngine::on_event.
There is no per-channel run budget, no cause-chain depth counter, and no trigger-event dedup on that path. max_concurrent: 100 is a global concurrency semaphore, not a cycle breaker.
Before: the workflow's output could not reliably wake an agent, so sustaining the cycle required a human to reply each round.
After: the output reliably wakes a mentioned agent, and agents reliably reply — so the cycle self-sustains.
To be clear about severity: this is not an authorization failure. The loop runs entirely as the workflow owner's own authority in their own channel, and nothing crosses an identity boundary. It is a runaway-execution / cost concern.
Mitigation available today (no code change)
A message_posted trigger filter on trigger_author — e.g. fire only for the owner's own messages. This is trustworthy because build_trigger_context sources author from the event signature, not from event content.
Two asks here:
Docs: tell workflow authors to filter on trigger_author when a step mentions an agent.
Engine: an actual breaker, so a mis-authored workflow cannot run away. Either a cause-chain depth limit (carry a depth counter through workflow-caused events and refuse past N) or a per-channel/per-workflow run budget over a window. Depth is the more precise of the two — a budget also throttles legitimate bursts.
Suggested acceptance
A regression that reproduces the 2-run result above and asserts the cycle terminates at the configured bound.
Activity