Skip to content

Commit 2c3602f

Browse files
os-zhuangclaude
andcommitted
docs(skill): teach this season's flow anti-patterns in the automation authoring skill
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) <noreply@anthropic.com>
1 parent 266c0f8 commit 2c3602f

2 files changed

Lines changed: 57 additions & 0 deletions

File tree

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
---
2+
---
3+
4+
docs(skill): add "valid-but-silently-wrong" anti-patterns to the automation authoring skill
5+
6+
Documentation-only — no package change. Adds this season's flow authoring
7+
anti-patterns (the ones that pass build but fail at runtime, and that the new
8+
build lints now catch) to `skills/objectstack-automation` Common Pitfalls, so the
9+
AI author writes them right at the source: single-brace value interpolation
10+
(#1315), `create_record` outputVariable holds the record / `{var.id}` (#1873),
11+
time-relative rules as schedule+range not record-change date-equality (#1874),
12+
`script` nodes must name a callable (#1870), conditions are bare CEL / stdlib only
13+
(#1877).

skills/objectstack-automation/SKILL.md

Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -511,6 +511,50 @@ read at runtime, not Zod-validated):
511511
5. **Scheduled flows without idempotency.** If the flow runs twice
512512
accidentally, the result should be the same.
513513

514+
### Valid-but-silently-wrong (passes build, fails at runtime)
515+
516+
These are *legal* metadata that authors — AI especially — get wrong. Most are now
517+
caught by `objectstack build` (a hard error, or an advisory warning), but write
518+
them right the first time:
519+
520+
6. **Flow node VALUE interpolation uses SINGLE braces.** Value fields on a node's
521+
`config` (`fields`, `inputs`, notify `message`/`title`, …) interpolate
522+
`{token}`:
523+
- `{var}` / `{record.title}` — variable / record field
524+
- `{record.tags.0}`**array index** (e.g. a `multiple: true` lookup, stored as an array)
525+
- `{$User.Id}` / `{NOW()}` / `{TODAY() + 30}` — current user / date macros
526+
- anything without `{…}` is a **literal**
527+
528+
`body: '{{ai_reply}}'` — double-brace is the *formula / template-field* dialect, **not** flow values
529+
`ticket: '$source.id'` — a bare `$ref` is a literal string, not interpolated
530+
`body: '{ai_reply}'`, `ticket: '{source.id}'`
531+
532+
7. **`create_record`'s `outputVariable` holds the created RECORD, not its id.**
533+
Reference a field explicitly.
534+
`update_record … fields: { ref: '{newRec}' }` → yields the whole record object
535+
`fields: { ref: '{newRec.id}' }`
536+
537+
8. **Time-relative rules ("alert N days before a date") are SCHEDULE flows, not
538+
record-change date-equality.** `record.end_date == daysFromNow(60)` on a
539+
`record-*` trigger only fires if the record happens to be written on that exact
540+
day — unattended rules never run.
541+
✅ A daily `schedule` flow whose `get_record` filters `end_date` BETWEEN
542+
`{TODAY()}` and `{TODAY() + N}`, then loops over the results.
543+
544+
9. **`script` nodes must name a callable.** Set `config.actionType` to a built-in
545+
side-effect (`email` / `slack`) **or** `config.function` to a function
546+
registered via `defineStack({ functions: { my_fn: (ctx) => … } })`. An empty
547+
`script` node — or one pointing at an unregistered function — fails loudly.
548+
Inline `config.script` JS is **not executed** by the built-in runtime (no
549+
server-side sandbox) — move logic into a registered `function`.
550+
551+
10. **Conditions are bare CEL — only the stdlib is callable.** `now()`,
552+
`today()`, `daysFromNow(n)`, `daysAgo(n)`, `isBlank(v)`, `coalesce(a, b)`,
553+
`trim(s)`, plus CEL built-ins (`has`, `size`, `contains`, `startsWith`, …).
554+
An UNKNOWN function (`PRIOR()`, a typo'd name) **fails the build**. And never
555+
wrap a field reference in `{…}` inside a condition — that's a template brace
556+
and fails as CEL: write `record.x`, not `{record.x}`.
557+
514558
---
515559

516560
## CRM Automation Blueprint

0 commit comments

Comments
 (0)