Hacktoberfest 2026: the issues maintainers tagged for October, open and beginner-friendly. Browse Hacktoberfest issues

#322 幂等键在失败重试路径换 token:error 终态咽喉清 next_step × retry_wakeup 铸新 token,外部去重失效(实弹复现)

Closed
#336 0 comments 0 reactions 0 assignees View on GitHub

Maintainers usually reply within 1 day

Nobody has claimed this yet.

Assessment

Difficulty
5/5
Estimated time
Over a week
Newbie friendliness
25/100
Issue type
Bug
Clarity
Clearly specified
Activity status
Active
Tech stack
python

Research direction

Start with plaita/server/flow_worker.py at _persist_state_or_raise and the retry wakeup path around lines 4270–4287; read the ownership and worker protocol references in core/ownership.py and docs/WORKER_PROTOCOL.md §2.1. Trace how terminal error persistence clears next_step and how retry assigns a step ID, then add the proposed regression coverage for a failed side effect followed by retry. Done means the retry reuses the original connector idempotency key; the issue leaves the design choice for preserving it to the owner.

Written by the indexing model from the issue text.

Description

优先级 P1

depends-on: #322

缺陷:失败重试路径连接器幂等键漂移——「同逻辑步骤重试 N 次 = 至多 1 次副作用」核心承诺在 G1/人工 retry 主路径失效

结论

#322 的幂等键稳定性锚是「重投读同一行 ⇒ 同 token」(core/ownership.py 模块 docstring、docs/WORKER_PROTOCOL.md §2.1:「同逻辑步骤重试 N 次 = 至多 1 次副作用」)。但 error 终态化会清掉行上的 token,retry 唤醒必然重铸新 token——两次执行携带不同 Idempotency-Key,外部系统按新请求处理,重复副作用照常发生。失效点恰是重复风险最高的时刻:上一次尝试已以外发了收据(如 POST 成功后响应解析失败)。

链路(当前 main 5637548c,全部实读)
  1. step k-1 落盘声明 next_step={node_id: X, step_id: T1}(plaita/server/flow_worker.py _next_step_from_context,step_persist 铸点)。
  2. worker 循环顶部消费 _bind_step_ownership(flow_worker.py:4960 附近)绑定 T1 → 节点 X 的 HTTP POST 携带 Idempotency-Key: {root}:T1:X。
  3. X 在副作用已发生后失败(如 POST 成功、响应处理抛错)→ state.status = "error" → 经 _persist_state_or_raise 落盘。
  4. 终态咽喉无条件清 token(flow_worker.py:2371-2374,#320 不变量 1 机械化点):
    if getattr(state, "status", None) in TERMINAL_EXECUTION_STATUSES:
        state.pending = None
        state.dispatch_pending = None
        state.next_step = None   # ← T1 在此丢失,"error" ∈ TERMINAL_EXECUTION_STATUSES
    
  5. retry 载体 → retry_wakeup 铸新 token(flow_worker.py:4270-4287):if not declared_step_id(state.next_step) 恒真(已被 4 清空)→ new_step_id() 铸 T2 → 重跑 X 携 Idempotency-Key: {root}:T2:X。
  6. k1 ≠ k2:外部去重完全失效。

注:进程内 redelivery 重试(未终态化,行上 T1 原样)同键保证成立;破坏面精确限定在「终态失败 → retry 唤醒」——G1 预算耗尽终态化后的人工/自动 retry、deterministic_failure_exhausted 后的 retry 都走此路。

实弹探针(真组件复现,非推演)

用真 FlowExecution(OwnershipMixin)+ 真 declared_step_id/new_step_id/connector_idempotency_key + 真 TERMINAL_EXECUTION_STATUSES,按上述 4/5 的逐字逻辑驱动:

[首跑]   绑定 token=db2d7693…  连接器键=d323f155…:db2d7693…:call-api
[终态化] error ∈ TERMINAL? True → next_step = None
[retry唤醒] 重新铸造 token=f003318e…
[重试跑] 绑定 token=f003318e…  连接器键=d323f155…:f003318e…:call-api
判定:REPRODUCED:同逻辑步骤两次执行键不同(外部去重失效)
修复方向(供 owner 裁决,未实现任何代码)

矛盾本质:#320「终态行不得声明未完成的工作」(next_step 同清)与 #322「重投读同一行 ⇒ 同 token」在 error→retry 路径互斥。候选:

  • a) error 行豁免 next_step 清除(违 #320 不变量字面,需在不变量文本里给 error+retry 语义开口子);
  • b) 终态化时把将被重跑节点的 token 存入 state.error 扩展键或专字段,retry_wakeup 优先复用而非重铸;
  • c) retry_wakeup 判据改锚(如以 noderetry 计数键世代推 token)——与「token 不可推导」的防伪造设计冲突,估计不可取。

倾向 b:不动 #320 不变量,token 随失败证据一起落盘。

验收建议

负控:构造节点首跑带 T1 外发收据后失败 → retry 唤醒 → 断言重跑绑定 token == T1(现树必红);同键后外部去重生效的端到端断言。

Dominant language
Python
Stars
0
Forks
1
Avg merge
1h 55m
Merged PRs (30d)
13

Getting set up

This project ships no dev container, Dockerfile or contributing guide, so setting up is up to you: start from its README, and see our first-contribution guide for the general steps.

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.

More from jeffkit/plaita

All issues in jeffkit/plaita

Similar issues

More Python issues

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.