Skip to content

fix(automation): bind flow triggers on cold boot, not just after HMR reload#2560

Merged
os-zhuang merged 2 commits into
mainfrom
fix/automation-bind-flows-at-boot
Jul 4, 2026
Merged

fix(automation): bind flow triggers on cold boot, not just after HMR reload#2560
os-zhuang merged 2 commits into
mainfrom
fix/automation-bind-flows-at-boot

Conversation

@os-zhuang

@os-zhuang os-zhuang commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

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 manifestregisterApp() 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 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). So nothing bound its trigger.

Fix

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(). registerFlow is idempotent, so re-binding a flow the boot pull already registered is harmless.

Verification (runtime, clean instance)

  • Before: cold boot → bound=0; updating a record fires 0 flows.
  • After: cold boot → all flows bind; one showcase_taskdone update fires 10 matching record-triggered flows (engine.execute FIRED flow=showcase_task_completed event=record-after-update, …_slack, …_assigned_notify, …_rest_ping, …).
  • Turns GREEN the pre-existing end-to-end regression 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.
  • New unit test (flow-cold-boot-bind.test.ts): an inline-app record flow served only via protocol.getMetaItems is bound after bootstrap() with no metadata:reloaded fired; fails on the pre-fix code.
  • Full @objectstack/service-automation suite green (227).

🤖 Generated with Claude Code

fix(plugin-grid): schema-aware multi-value semantics for bulk-edit params (#2206)

objectui@3e4268041bc2523bc3f40a4d54646672ce3394c5
@vercel

vercel Bot commented Jul 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
spec Ready Ready Preview, Comment Jul 4, 2026 5:09am

Request Review

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling size/m labels Jul 4, 2026
@github-actions

github-actions Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): packages/services.

5 hand-written doc(s) reference the affected code and may need an implementation-accuracy re-verification:

  • content/docs/guides/packages.mdx (via packages/services)
  • content/docs/guides/runtime-services/audit-service.mdx (via packages/services)
  • content/docs/guides/runtime-services/index.mdx (via packages/services)
  • content/docs/guides/runtime-services/settings-service.mdx (via packages/services)
  • content/docs/protocol/objectos/i18n-standard.mdx (via packages/services)

Advisory only. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs origin/main → pass the list as args.docs.

…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
os-zhuang force-pushed the fix/automation-bind-flows-at-boot branch from 4ffc95c to b6c4a8e Compare July 4, 2026 05:07
@os-zhuang
os-zhuang merged commit f84f8d5 into main Jul 4, 2026
16 checks passed
@os-zhuang
os-zhuang deleted the fix/automation-bind-flows-at-boot branch July 4, 2026 06:50
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>
@github-actions github-actions Bot mentioned this pull request Jul 4, 2026
@github-actions github-actions Bot mentioned this pull request Jul 4, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant