Found while implementing #14087. Not that card's fix (which repairs the generator); this is the diagnostic an author gets when they write the same shape by hand. Unassigned.
The defect
FlowSchema (packages/spec/src/automation/flow.zod.ts) declares trigger: 'type' in its strictObject alias table. So a flow carrying a top-level trigger block is refused with, verbatim:
Unrecognized key(s) on this flow: trigger. Did you mean trigger → type? Until this shape was closed, these were dropped silently — the flow still parsed, so a trigger binding or config the author wrote was quietly ignored.
Taking that advice is a dead end. trigger holds an object ({ type, object, events } in every producer that writes it), and type is z.enum(['autolaunched', 'record_change', 'schedule', 'screen', 'api']) — so the rename lands the author on Invalid option: expected one of "autolaunched"|… one round later, with the binding still nowhere. The trigger does not move to type; it moves to the START node's config, as { objectName, triggerType, condition }, which is where AutomationEngine.resolveTriggerBinding reads it from.
Why the same schema gets this right two keys over
The same options block already carries the correct mechanism for exactly this case. Its guidance entries say where a key really went:
object — "a record-change flow binds its object on the START node's config ({ objectName, triggerType, condition }), not at the flow top level."
objectName, schedule — same shape, same accuracy.
trigger is the one member of that family routed to aliases instead, and an alias entry is a rename claim. The repo has already named this failure mode in this same file, on the inputSchema.optional guidance: a rename "would be actively wrong here", so the entry states the flip instead of prescribing a name. This is that case, undetected because the alias is spelled plausibly.
Why it is worth fixing rather than leaving
trigger is the single most likely key an author (or an AI author importing vocabulary from any other automation product) writes at the flow top level — #14087 exists because ObjectStack's own generator wrote it. A newcomer meeting a .strict() refusal has one actionable sentence in it, and right now that sentence sends them somewhere that cannot work, which is worse than the distance fallback saying nothing.
Suggested fix
Move trigger out of aliases and into guidance beside object / objectName / schedule, with text naming the START node's config and the record-* triggerType grammar. No accept/reject behaviour changes — the key is refused either way; only the prescription changes.
⚠️ Worth checking the same table for siblings while there: triggertype: 'type' is in the alias list too and looks like the same mistake (an author who wrote a top-level triggerType is told to rename it to type, whose accept-set does not contain a single record-* token), but I did not measure that one.
Filed unassigned, domain:* left blank per the single-producer rule.
Generated by Claude Code
Blocked-by: (resolved 2026-09-03T09:41Z — PR #14735 MERGED; the #14832 hang was quarantined on main by PR #14871)
Unlock-action: (fired — PR #14735 merged 09:40Z)
(Added by the domain:spec seat 2026-09-03T02:12Z: work complete — PR #14735 ACCEPTed 5517551364, kicked from the merge queue twice by the packages/cli run-dev-unbuilt-workspace.e2e.test.ts flake anchored on #14648 whose fix PR #14715 is open; re-check the PR when #14648 closes, do not re-dispatch.)
(Added by the domain:spec seat 2026-09-03T06:05Z: kicked from the merge queue a third time at 06:00:53Z by the same packages/cli run-dev-unbuilt-workspace.e2e.test.ts hang, now anchored on #14832 — the residue left after #14715 merged; not re-queued. Re-check PR #14735 when #14832 closes, do not re-dispatch.)
Generated by Claude Code
Found while implementing #14087. Not that card's fix (which repairs the generator); this is the diagnostic an author gets when they write the same shape by hand. Unassigned.
The defect
FlowSchema(packages/spec/src/automation/flow.zod.ts) declarestrigger: 'type'in itsstrictObjectalias table. So a flow carrying a top-leveltriggerblock is refused with, verbatim:Taking that advice is a dead end.
triggerholds an object ({ type, object, events }in every producer that writes it), andtypeisz.enum(['autolaunched', 'record_change', 'schedule', 'screen', 'api'])— so the rename lands the author onInvalid option: expected one of "autolaunched"|…one round later, with the binding still nowhere. The trigger does not move totype; it moves to the START node'sconfig, as{ objectName, triggerType, condition }, which is whereAutomationEngine.resolveTriggerBindingreads it from.Why the same schema gets this right two keys over
The same options block already carries the correct mechanism for exactly this case. Its
guidanceentries say where a key really went:object— "a record-change flow binds its object on the START node'sconfig({ objectName, triggerType, condition }), not at the flow top level."objectName,schedule— same shape, same accuracy.triggeris the one member of that family routed toaliasesinstead, and an alias entry is a rename claim. The repo has already named this failure mode in this same file, on theinputSchema.optionalguidance: a rename "would be actively wrong here", so the entry states the flip instead of prescribing a name. This is that case, undetected because the alias is spelled plausibly.Why it is worth fixing rather than leaving
triggeris the single most likely key an author (or an AI author importing vocabulary from any other automation product) writes at the flow top level — #14087 exists because ObjectStack's own generator wrote it. A newcomer meeting a.strict()refusal has one actionable sentence in it, and right now that sentence sends them somewhere that cannot work, which is worse than the distance fallback saying nothing.Suggested fix
Move
triggerout ofaliasesand intoguidancebesideobject/objectName/schedule, with text naming the START node'sconfigand therecord-*triggerTypegrammar. No accept/reject behaviour changes — the key is refused either way; only the prescription changes.triggertype: 'type'is in the alias list too and looks like the same mistake (an author who wrote a top-leveltriggerTypeis told to rename it totype, whose accept-set does not contain a singlerecord-*token), but I did not measure that one.Filed unassigned,
domain:*left blank per the single-producer rule.Generated by Claude Code
Blocked-by: (resolved 2026-09-03T09:41Z — PR #14735 MERGED; the #14832 hang was quarantined on main by PR #14871)
Unlock-action: (fired — PR #14735 merged 09:40Z)
(Added by the
domain:specseat 2026-09-03T02:12Z: work complete — PR #14735 ACCEPTed 5517551364, kicked from the merge queue twice by thepackages/clirun-dev-unbuilt-workspace.e2e.test.tsflake anchored on #14648 whose fix PR #14715 is open; re-check the PR when #14648 closes, do not re-dispatch.)(Added by the
domain:specseat 2026-09-03T06:05Z: kicked from the merge queue a third time at 06:00:53Z by the samepackages/clirun-dev-unbuilt-workspace.e2e.test.tshang, now anchored on #14832 — the residue left after #14715 merged; not re-queued. Re-check PR #14735 when #14832 closes, do not re-dispatch.)Generated by Claude Code