fix(automation): bind flow triggers on cold boot, not just after HMR reload#2560
Merged
Conversation
fix(plugin-grid): schema-aware multi-value semantics for bulk-edit params (#2206) objectui@3e4268041bc2523bc3f40a4d54646672ce3394c5
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
📓 Docs Drift CheckThis PR changes 1 package(s): 5 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:
|
…reload
Record-triggered (and other trigger-typed) flows silently never fired on a
fresh process start — in dev and in production. The automation service's
boot-time flow pull reads ql.registry.listItems('flow'), which is EMPTY for
flows defined inline in an app manifest (registerApp stores the app under type
'app' and never promotes its inline flows to standalone registry 'flow'
items). The re-sync that could see them ran only on the 'metadata:reloaded'
hook, which never fires on a cold boot (os dev restarts the process on
recompile rather than firing it; production never reloads). Net: after any
real restart, no flow bound its trigger, so record-change automations did not
fire at all.
Bind flows at kernel:ready from protocol.getMetaItems({ type: 'flow' }) — the
canonical flattened flow view that GET /meta/flow serves and that DOES surface
inline app flows — once every plugin has finished init()/start() (so the app,
hence its flows, is registered). registerFlow is idempotent, so re-binding a
flow the boot pull already registered is harmless.
Verified end-to-end on a clean instance: before, updating a record fired 0
flows (0 bound at boot); after, a cold boot binds all flows and a single
record update fires every matching record-triggered flow (10 fired). The fix
also turns GREEN the pre-existing end-to-end regression
record-change-integration.test.ts (#1909) — 'fires a flow pulled from the
registry ... bound when the trigger registers on kernel:ready' — which was red
because that binding never happened. New cold-boot unit test asserts an
inline-app record flow, served only via protocol.getMetaItems, is bound after
bootstrap() with no metadata:reloaded fired; it fails on the pre-fix code.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
os-zhuang
force-pushed
the
fix/automation-bind-flows-at-boot
branch
from
July 4, 2026 05:07
4ffc95c to
b6c4a8e
Compare
os-zhuang
added a commit
that referenced
this pull request
Jul 4, 2026
… a restart (#2576) Follow-up to #2560 (cold-boot flow binding). A flow published while the server runs — author a record-triggered automation in the Studio, publish it, immediately update a matching record — did not fire; its trigger only bound on the next restart. Two gaps, both fixed: 1. The publish path fired no rebind signal. The runtime dispatcher now announces 'metadata:reloaded' after a successful POST /packages/:id/publish-drafts — the same signal a dev artifact reload fires — so boot-cached consumers re-sync. 2. The runtime re-sync read the wrong source. The automation service's 'metadata:reloaded' re-sync pulled metadata.list('flow'), which returns 0 in a real running server; it now reads protocol.getMetaItems({type:'flow'}) — the same flattened view #2560's cold-boot bind uses — keeping teardown of removed flows. A failed/unavailable protocol read is a no-op and never tears down live flows. Verified end-to-end on a clean instance: authored a record-triggered flow in a writable package, published it without restarting, then updated a matching record and observed the flow fire (before the fix it did not). New regression tests: flow-publish-rebind.test.ts (re-sync binds a post-boot flow, tears down a removed one, reads the protocol not metadata.list, and never tears down on a failed read) plus two http-dispatcher.test.ts cases (publish fires / does not fire the hook). Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
This was referenced Jul 4, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The bug
Record-triggered (and other trigger-typed) flows silently never fired after a fresh process start — in dev and in production.
Found dogfooding a Studio-authored automation end-to-end on a clean instance: updating a record fired its audit hook but the flow never ran. 0 flows bound at boot, while the metadata layer held 20.
Root cause
On a cold boot the automation service pulls flows via
ql.registry.listItems('flow'), which is empty for flows defined inline in an app manifest —registerApp()stores the app under type'app'and never promotes its inline flows to standalone registry'flow'items. The re-sync that could see them (resyncFlowsFromMetadata) runs only on themetadata:reloadedhook, which never fires on a cold boot (os devrestarts the process on recompile rather than firing it; production never reloads). So nothing bound its trigger.Fix
Bind flows at
kernel:readyfromprotocol.getMetaItems({ type: 'flow' })— the canonical flattened flow view thatGET /meta/flowserves and that does surface inline app flows — once every plugin has finishedinit()/start().registerFlowis idempotent, so re-binding a flow the boot pull already registered is harmless.Verification (runtime, clean instance)
bound=0; updating a record fires 0 flows.showcase_task→doneupdate fires 10 matching record-triggered flows (engine.execute FIRED flow=showcase_task_completed event=record-after-update,…_slack,…_assigned_notify,…_rest_ping, …).trigger-record-change/src/record-change-integration.test.ts(test(trigger-record-change): end-to-end regression coverage for record-change flows (#1491) #1909) — "fires a flow pulled from the registry … bound when the trigger registers on kernel:ready (production ordering)" — which was red precisely because that binding never happened.flow-cold-boot-bind.test.ts): an inline-app record flow served only viaprotocol.getMetaItemsis bound afterbootstrap()with nometadata:reloadedfired; fails on the pre-fix code.@objectstack/service-automationsuite green (227).🤖 Generated with Claude Code