From 2c3602f72a760be25b7afb9c3ab6c5de3bb8f4fd Mon Sep 17 00:00:00 2001 From: os-zhuang Date: Mon, 15 Jun 2026 23:41:00 +0800 Subject: [PATCH] docs(skill): teach this season's flow anti-patterns in the automation authoring skill MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Upstream root-cause fix for AI authoring mistakes: the templates are AI-written, so the highest-leverage prevention is teaching the AI the correct patterns at the source, not only catching them at build. Adds a "Valid-but-silently-wrong (passes build, fails at runtime)" subsection to skills/objectstack-automation Common Pitfalls, capturing the anti-patterns fixed/ linted this season, each with ❌/✅: - single-brace value interpolation; no `{{double}}` / bare `$ref` (#1315) - `create_record` outputVariable is the RECORD → `{var.id}` (#1873) - time-relative rules = schedule + range query, NOT record-change date-equality (#1874) - `script` nodes must name a built-in actionType or a registered `function`; inline `config.script` is not executed (#1870) - conditions are bare CEL using only the stdlib; unknown fns fail the build; don't wrap field refs in `{…}` (#1877/#1491) Docs-only; `check:skill-docs` passes (frontmatter-derived docs unaffected); empty changeset. Co-Authored-By: Claude Opus 4.8 (1M context) --- .changeset/automation-skill-antipatterns.md | 13 ++++++ skills/objectstack-automation/SKILL.md | 44 +++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 .changeset/automation-skill-antipatterns.md diff --git a/.changeset/automation-skill-antipatterns.md b/.changeset/automation-skill-antipatterns.md new file mode 100644 index 0000000000..6691a12bd4 --- /dev/null +++ b/.changeset/automation-skill-antipatterns.md @@ -0,0 +1,13 @@ +--- +--- + +docs(skill): add "valid-but-silently-wrong" anti-patterns to the automation authoring skill + +Documentation-only — no package change. Adds this season's flow authoring +anti-patterns (the ones that pass build but fail at runtime, and that the new +build lints now catch) to `skills/objectstack-automation` Common Pitfalls, so the +AI author writes them right at the source: single-brace value interpolation +(#1315), `create_record` outputVariable holds the record / `{var.id}` (#1873), +time-relative rules as schedule+range not record-change date-equality (#1874), +`script` nodes must name a callable (#1870), conditions are bare CEL / stdlib only +(#1877). diff --git a/skills/objectstack-automation/SKILL.md b/skills/objectstack-automation/SKILL.md index 3ed1e4ebff..a8bb4ad0dd 100644 --- a/skills/objectstack-automation/SKILL.md +++ b/skills/objectstack-automation/SKILL.md @@ -511,6 +511,50 @@ read at runtime, not Zod-validated): 5. **Scheduled flows without idempotency.** If the flow runs twice accidentally, the result should be the same. +### Valid-but-silently-wrong (passes build, fails at runtime) + +These are *legal* metadata that authors — AI especially — get wrong. Most are now +caught by `objectstack build` (a hard error, or an advisory warning), but write +them right the first time: + +6. **Flow node VALUE interpolation uses SINGLE braces.** Value fields on a node's + `config` (`fields`, `inputs`, notify `message`/`title`, …) interpolate + `{token}`: + - `{var}` / `{record.title}` — variable / record field + - `{record.tags.0}` — **array index** (e.g. a `multiple: true` lookup, stored as an array) + - `{$User.Id}` / `{NOW()}` / `{TODAY() + 30}` — current user / date macros + - anything without `{…}` is a **literal** + + ❌ `body: '{{ai_reply}}'` — double-brace is the *formula / template-field* dialect, **not** flow values + ❌ `ticket: '$source.id'` — a bare `$ref` is a literal string, not interpolated + ✅ `body: '{ai_reply}'`, `ticket: '{source.id}'` + +7. **`create_record`'s `outputVariable` holds the created RECORD, not its id.** + Reference a field explicitly. + ❌ `update_record … fields: { ref: '{newRec}' }` → yields the whole record object + ✅ `fields: { ref: '{newRec.id}' }` + +8. **Time-relative rules ("alert N days before a date") are SCHEDULE flows, not + record-change date-equality.** `record.end_date == daysFromNow(60)` on a + `record-*` trigger only fires if the record happens to be written on that exact + day — unattended rules never run. + ✅ A daily `schedule` flow whose `get_record` filters `end_date` BETWEEN + `{TODAY()}` and `{TODAY() + N}`, then loops over the results. + +9. **`script` nodes must name a callable.** Set `config.actionType` to a built-in + side-effect (`email` / `slack`) **or** `config.function` to a function + registered via `defineStack({ functions: { my_fn: (ctx) => … } })`. An empty + `script` node — or one pointing at an unregistered function — fails loudly. + Inline `config.script` JS is **not executed** by the built-in runtime (no + server-side sandbox) — move logic into a registered `function`. + +10. **Conditions are bare CEL — only the stdlib is callable.** `now()`, + `today()`, `daysFromNow(n)`, `daysAgo(n)`, `isBlank(v)`, `coalesce(a, b)`, + `trim(s)`, plus CEL built-ins (`has`, `size`, `contains`, `startsWith`, …). + An UNKNOWN function (`PRIOR()`, a typo'd name) **fails the build**. And never + wrap a field reference in `{…}` inside a condition — that's a template brace + and fails as CEL: write `record.x`, not `{record.x}`. + --- ## CRM Automation Blueprint