larksuite / larksuite/cli

`lark-cli base +record-upsert` 创建记录时偶发落库两条(疑似 HTTP 传输层在响应丢失后重试非幂等 POST)

Open
#2,758 1 comment 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

domain/base domain/doc
Dominant language
Go
Stars
17.3k
Forks
1.4k
Avg merge
2d 4h
Merged PRs (30d)
105

Description

一、现象概述

使用 lark-cli base +record-upsert不带 --record-id,即创建模式)向飞书多维表格写入一条记录时,单次命令执行偶发在目标表生成两条字段完全一致的记录

调用方只发起了一次创建、只收到一条记录的响应,但服务端实际插入了两条。怀疑根因:lark-cli 的 HTTP 传输层在创建请求的响应丢失/超时后自动重试了 POST,而创建接口不具备幂等性,重试导致服务端再次插入一条。


二、环境信息

lark-cli 版本 1.0.96(已复现);1.0.93 同样复现
安装方式 用户级 npm install -g lark-cliC:\Users\<USER>\AppData\Roaming\npm\lark-cli.cmd
操作系统 Windows 11
命令形态 lark-cli base +record-upsert --as user --base-token <BASE_TOKEN> --table-id <TABLE_ID> --json @payload.json
是否含 --dry-run 否(--dry-run 已单独验证不写库,见第四节)
是否带 --record-id 否(即纯创建)

三、复现现象(确定性结果)

一次命令、单次进程、调用方无任何重试逻辑的前提下,创建后查询目标表,发现存在两条相邻 record_id、字段完全一致的记录:

  • <REC_1>
  • <REC_2>

而该命令的写入响应 stdout 仅包含一个 record id:

{
  "ok": true,
  "identity": "user",
  "data": {
    "created": true,
    "record": {
      "record_id_list": ["<REC_2>"]
    }
  }
}

即:客户端只“看到”一条响应,服务端却落了两条。两条记录内容逐字段核对一致(业务主键字段、各文本字段均相同),可判定为同一次创建的重复产物。

补充:同表中另一次创建(内容不同)也出现相同现象,产生 <REC_3> / <REC_4> 两条,后人工删除其一。两次实测分别在 1.0.93 与 1.0.96 上发生,说明该问题跨越版本、与具体 payload 内容无关。


四、已排除项

  1. 不是 --dry-run 写库:用 marker payload 单独执行 --dry-run 后查询目标表,命中 0 条,确认 --dry-run 仅打印请求体、不写库。重复记录全部来自真实写入步骤。
  2. 不是调用方重试:调用脚本对 lark-cli 是单次 subprocess.run,无任何 retry / attempt 机制;本次仅执行了一次创建命令。
  3. 不是 Agent / 人为二次执行:创建流程在会话中只触发一次。

五、根因推测(供官方排查)

  • lark-cli 为 Go 编译二进制;从其二进制字符串可见存在 HTTP 重试传输层,典型字符串如 cmdutil.RetryTransportRoundTrip retrying after failure: %vshouldRetryRequestratelimit 等,说明在网络错误 / 响应超时时会对请求做自动重试

  • base +record-upsert 创建模式(无 --record-id)为非幂等写操作--help 明确说明:

    Without --record-id this creates a record; It does not auto-upsert by business key.

  • 关键时序:创建请求已到达服务端并成功插入,但客户端在读取响应前遭遇连接重置 / 超时(如代理或网络抖动、服务端处理偏慢,或受限网络环境)。此时传输层重试该 POST;由于请求不带幂等键,服务端将其视为“第二次创建”再次插入 → 表中出现两条。客户端拿到的响应只对应其中一次重试,故 record_id_list 仅含一个 id。

该推测与“响应只返回 1 个 id、表中有 2 条相邻 id”的现象高度自洽。


六、期望行为

  1. 写入类(非幂等)请求在网络异常时不应盲目重试;或重试必须携带幂等键 / client-token,使服务端对重复请求去重。
  2. 至少在帮助文档 / 配置中显式说明该重试行为,并提供关闭重试的开关(环境变量或 flag)。
  3. 创建响应在重试场景下应聚合 / 返回所有已创建 record id,或在发生不确定写入时以非零退出码提示“写入结果不确定”,避免调用方误以为成功且仅一条。

七、建议修复方向

优先级 建议
非幂等方法(POST 创建)默认不重试;仅对可安全重试的场景(纯查询,或带幂等键的写)重试
写请求支持 --idempotency-key / client_token,服务端据此去重
提供关闭传输层重试的开关,如 LARK_CLI_NO_RETRY=1--no-retry
创建响应在重试场景下返回全部已创建 id,或在不确定时以非零退出码告警

八、当前影响与临时规避

  • 影响:自动化 / 脚本批量建记录时偶发重复数据,需人工核对清理;在 CI / Agent 编排中会被静默当作成功,埋下数据一致性隐患。
  • 规避(已在调用方实现):创建后按业务键(多个业务关键字段组合)查询目标表,命中多条即告警并保留一条、删除其余、禁止重试创建。该方案可拦截重复,但无法从根因消除,且依赖调用方自行实现。

九、给官方的复现建议

由于该问题依赖“响应丢失 / 超时”的偶发网络条件,可尝试以下方式稳定复现:

  1. 在飞书 API 网关前注入响应延迟或连接重置(如代理层在写请求返回后、客户端读取前断开连接);
  2. 或在网络受限 / 高延迟环境下连续执行若干次创建,统计目标表记录数是否大于执行次数;
  3. 开启 lark-cli 的 verbose / debug 日志(如有),观察是否存在 retrying after failure 类日志与两次请求发出。

如需要,我可以提供两次实测的完整命令序列、payload 结构与查询佐证(已脱敏)。


附:关键证据片段

创建响应(仅 1 个 id,但表中有 2 条):

{ "ok": true, "identity": "user",
  "data": { "created": true, "record": { "record_id_list": ["<REC_2>"] } } }

查表结果(同内容 2 条):

record count: 2
ids: ['<REC_1>', '<REC_2>']

dry-run 控制测试(确认不写库):

marker payload 执行 --dry-run 后查表 -> record count: 0

Contributor guide

No contributing guide indexed for this repository

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start by locating the Go HTTP retry transport and the shouldRetryRequest logic referenced in the issue, then trace the base +record-upsert create path when --record-id is absent. Reproduce with a delayed or reset response if possible and inspect verbose retry logs. Done means non-idempotent POST creation is not blindly retried, or the behavior has an explicit safe idempotency or failure signal.

Written by the indexing model from the issue text.

Assessment

Tech stack
go
Domain
cli, networking
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Active
Clarity
Mostly clear
Newbie friendliness
52/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.