fix(workflow): suppress self-echo trigger duplication on workflow-written records
One user edit cascaded into 3-5 workflow executions and hourly sync jobs
generated 1000+ wasted runs/week (7d: 15.7k of 18k executions of the
ticket-status workflow ended in skipped no-op guards). Root cause: workflow
scripts write records via REST with a runner-signed token, and record-event
triggers could not distinguish those writes from human edits — every script
PATCH re-triggered its own workflow.
Attribution chain (new):
- WorkflowTokenService signs {workflowId, executionId} into the runner token
- jwt.strategy lifts it into CLS workflowContext (User-type branch)
- all 13 automation.record.* emit sites tag payloads with writeSource
- the 4 record triggers drop SELF echoes (writeSource.workflowId === own id)
unconditionally; cross-workflow writes keep firing by default (chaining is
load-bearing: order-sync建档 -> status workflow stamps 待处理, ~142 real
actions/week). Opt-in stronger dedup via trigger config
skipWorkflowWrites: true.
Verified live: human edit (处理反馈=正在核实) -> exactly 1 execution, script
flipped 流转状态 待处理->处理中, zero follow-up echoes; restore writes likewise
1 execution each with skipped no-ops. Unit: 14 new tests, 318 adjacent green.
This commit is contained in:
@@ -71,6 +71,13 @@ inkreach-teable trigger set <wfId> --type recordUpdated --table-id tblXXX \
|
|||||||
- ⚠️ **Write-path coverage** (verified 2026-08): browser grid cell edits DO fire this trigger — every grid editor commit funnels through `record.updateCell` → the REST `updateRecord` API (no ShareDB op submit exists in the frontend). Covered surfaces: single/batch record update (REST v1+v2), grid paste (small `paste-by-id`, large `paste-*-stream`, range `paste`), grid cell clear (`clear` / `clear-by-id`), record duplicate, and programmatic/automation writes (workflow actions call the REST API). Form submissions intentionally emit ONLY the `formSubmitted` event (independent channel). Record deletions emit no automation event (no delete trigger category exists).
|
- ⚠️ **Write-path coverage** (verified 2026-08): browser grid cell edits DO fire this trigger — every grid editor commit funnels through `record.updateCell` → the REST `updateRecord` API (no ShareDB op submit exists in the frontend). Covered surfaces: single/batch record update (REST v1+v2), grid paste (small `paste-by-id`, large `paste-*-stream`, range `paste`), grid cell clear (`clear` / `clear-by-id`), record duplicate, and programmatic/automation writes (workflow actions call the REST API). Form submissions intentionally emit ONLY the `formSubmitted` event (independent channel). Record deletions emit no automation event (no delete trigger category exists).
|
||||||
- Values resolve to `undefined` (not `[]`) if the emitting path could not snapshot before-values — scripts should guard `changedFieldIds ?? []`.
|
- Values resolve to `undefined` (not `[]`) if the emitting path could not snapshot before-values — scripts should guard `changedFieldIds ?? []`.
|
||||||
|
|
||||||
|
**Workflow-write echo suppression** (all 4 record triggers: `recordCreated` / `recordUpdated` / `recordCondition` / `recordCreatedOrUpdated`):
|
||||||
|
- Writes made BY workflow executions (script `fetch` with `triggerInput._accessToken`, `createRecords`/`updateRecords` actions) are tagged `writeSource: {workflowId, executionId}` on the event.
|
||||||
|
- **Self-echoes are always dropped**: a workflow never fires on its own writes (the script's PATCH re-triggering its own workflow was the duplication bug — one edit cascaded into 3-5 executions, every echo ending in a `skipped` no-op guard).
|
||||||
|
- Writes by OTHER workflows still fire by default — cross-workflow chaining is load-bearing (e.g. the order-sync job 建档 and this workflow stamping the initial 待处理). Opted-in events carry `triggerInput.writeSource.workflowId` for loop-guarding inside scripts.
|
||||||
|
- Config `"skipWorkflowWrites": true` additionally drops cross-workflow writes (stronger dedup when a workflow should react ONLY to human edits).
|
||||||
|
- Human grid edits, form/API/CLI writes are never tagged and always fire; suppressed writes still reach other listeners (e.g. record subscriptions) — only record-trigger handlers drop them.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 3. recordCondition — When a record matches conditions
|
## 3. recordCondition — When a record matches conditions
|
||||||
|
|||||||
Reference in New Issue
Block a user