Guardrails for a Feishu Automation That Touches Real Data
The first version of a Feishu automation usually works. The trouble starts on day three, when it writes into the wrong table, pings an external group at 02:00, or overwrites a field a human was editing. Tool selection is the easy part (the 2026 automation guide covers that); what follows is the pre-flight checklist for a flow that is about to touch live records.
1. Scope the blast radius before you request a token
Write a manifest before you write code: the base token, the table ids, and the field names the flow may touch. Anything not in the manifest is a bug, not a feature.
Two rules do most of the work:
- Read scope is wider than write scope. Reading a whole base so you can compute a diff is fine. Writing should be limited to a named list of fields and nothing else.
- No dynamic target discovery. The moment your code does “find every table whose name matches /OKR/” you have turned a logic error into an incident. Hardcode the ids; keep them in config, not in a regex.
Decide identity early too. A tenant token acts as the app: stable, unaffected by staff changes, and broad. A user_access_token inherits one person’s permissions and silently breaks when they change roles or leave. Scheduled flows should use the tenant token and constrain scope in code.
2. Permission minimums
Feishu scopes are granted per app, so over-granting is permanent until someone remembers to revoke it. Request the smallest set that survives a dry run.
| Capability | Scope | Grant when |
|---|---|---|
| Read one base | bitable:app:readonly | Always, for the base in the manifest |
| Write records | bitable:app | Only after a clean dry run |
| Send group messages | im:message:send_as_bot | Only if the flow posts, and only to named chats |
Resolve names to open_id | contact:user.base:readonly | Only if you actually map people |
| Refresh user tokens | offline_access | Only for user-identity flows |
Avoid drive:drive unless you are genuinely manipulating files across the drive. Avoid running the app as a tenant administrator. Verify what you actually got:
# Tenant token for a custom app. Keep the secret in a secret manager, not in the repo.
resp=$(curl -s -X POST \
'https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal' \
-H 'Content-Type: application/json' \
-d '{"app_id":"'"$FEISHU_APP_ID"'","app_secret":"'"$FEISHU_APP_SECRET"'"}')
TOKEN=$(echo "$resp" | jq -r .tenant_access_token)
# Probe the exact table you plan to write to. If this 403s, stop here.
curl -s "https://open.feishu.cn/open-apis/bitable/v1/apps/$APP_TOKEN/tables/$TABLE_ID/records?page_size=1" \
-H "Authorization: Bearer $TOKEN" | jq '.data.items[0].fields | keys'
3. A dry-run pass is a mode, not a test you ran once
A dry run means the flow computes every intended operation, logs them, and executes none. It is not “call the API and undo it.” Build it as a first-class mode that shares the same code path as the real run. If the dry run lives on a separate branch, it validates a program you will never ship.
Three passes, in order:
- Plan-only. No network reads beyond schema. Output the operation list.
- Diff against live. Read current records, compute what would change, print before/after per field.
- Apply with approval. The same operations, plus a human gate.
# flow.yaml
feishu:
app_token: !env FEISHU_APP_TOKEN
table_id: tblXXXXXXXXXXXX
write_fields: [status, owner_open_id, due_date] # manifest: nothing else
mode: dry_run # dry_run | apply
approval:
require_human: true
channels: [ops-review]
limits:
max_records_per_run: 50
abort_on_field_mismatch: true
Idempotency belongs here too. Give every row a stable business key (project_id + period) and store the last-written value. Run the flow twice; the second run should produce an empty operation list. If it does not, you have non-determinism, and no amount of logging will fix ordering problems. That same discipline is what makes an unattended agent runbook readable.
4. Audit logs you can actually query
One JSON line per operation, appended, never edited. The fields that earn their keep: run_id, trace_id (Feishu returns it in the X-Tt-Logid response header), app_token, table_id, record_id, field, before, after, actor, dry_run. Without trace_id you cannot ask Feishu support what happened; without before you cannot rebuild the rollback.
import json, time
def log_op(sink, run_id, trace_id, record_id, field, before, after, dry_run):
sink.write(json.dumps({
"ts": time.time(), "run_id": run_id, "trace_id": trace_id,
"table_id": TABLE_ID, "record_id": record_id, "field": field,
"before": before, "after": after, "dry_run": dry_run,
}, ensure_ascii=False) + "\n")
sink.flush()
def rollback_entry(op):
# For every forward write, emit the inverse update.
return {"record_id": op["record_id"], "fields": {op["field"]: op["before"]}}
Write to a sink the automation cannot modify inside its own logic: a separate base, a log bucket, or a file opened append-only. A log the flow can rewrite will eventually agree with the bug.
5. Rollback and the human-approval threshold
Some writes are reversible, some are not. Batch field updates can be inverted with the values in your audit log. A message that already landed in a group chat cannot. A deleted record may be recoverable from the Bitable recycle bin or a record’s version history, but check the retention window in your tenant before you depend on it, and export the affected records to JSON before any batch mutation.
Set the approval threshold by consequence, not by volume:
| Situation | Default action |
|---|---|
| Field updates inside the manifest, within row limits | Apply, log, report |
| Writes to a watched field (owner, status, budget) | Queue for human approval |
| Message to a chat with external guests | Always human |
| Delete, archive, or move between bases | Always human |
| Failures after midnight | Queue until morning; do not retry blindly |
Pair the threshold with triage habits so the 03:00 failure path is a queue, not a page. Re-run the full dry run after every scope change, because permission changes are code changes.
What to do next
- Write the manifest, base token, table id and field list, and delete every code path that discovers targets dynamically.
- Request read-only scopes, run the probe request above, and only then add
bitable:appfor the tables you named. - Implement
dry_runas a shared code path, then run the plan and diff passes against a staging base that mirrors production. - Add the append-only JSON log with
trace_idandbefore/after, and generate the rollback file in the same run. - Set one approval rule this week, a watched field or any external chat, and confirm it actually blocks.
Get the free AI Prompt Library sample
AI Prompt Library โ Free Sample โ Free, instant download.
Related reading
- The Ultimate Guide to AI Automation 2026
- How I Built a 10-Product Digital Business
- Building an AI Content Factory
๐ Want to try before you buy?
Both samples are 100% free. Enter your email to unlock the download links and get our 3-day AI toolkit drip in your inbox.
No spam. Unsubscribe anytime. We respect your privacy.
Or browse the full catalogue in the Slashman Tools storefront.
Published by slashman413 โ writing practical, evergreen guides on money, productivity, developer tooling and the web. More about this site โ
Frequently Asked Questions
Does a Feishu bot need bitable:app just to read records?
No. bitable:app:readonly covers reads, and you should add bitable:app only for the tables in your manifest after a clean dry run.
How do I roll back a Feishu automation that wrote bad data?
Store before/after values per field in an append-only log and replay the inverse batch update. For deletes, confirm the recycle bin and version history retention window in your tenant first.
When should a Feishu automation require human approval?
When it writes to watched fields such as owner, status or budget, when it posts to a chat with external guests, and for any delete, archive or cross-base move.
๐ Recommended Tools
๐ Related Articles
- Every Product We Sell on Gumroad: The Complete CatalogEvery live Slashman Tools product on Gumroad in one place โ AI prompts, agent workflows, dashboards, courses โฆ
- What Makes a Feishu Template Worth Paying ForA breakdown of Feishu template parts: data model, views, permission scopes, automation flows โ and which ones โฆ
- Buying a Template vs Building Your Own: Variables That DecideA practical decision guide for buying a template versus building your own workspace: data model stability, โฆ
- n8n vs Custom Multi-Agent Workflows: When to Stop Dragging Nodes and Write Code (2026)n8n or a custom code multi-agent workflow? An honest 2026 comparison of versioning, testing, cost, LLM โฆ
๐ Free AI Productivity Toolkit
50+ curated prompts + tools comparison + workflow templates. Free download โ sent to your inbox instantly.
Free forever ยท No spam ยท Unsubscribe anytime ยท Sent instantly