anthropics / anthropics/anthropic-sdk-go

SessionToolRunner redispatches stale tool calls after a processed session-wide interrupt

未关闭
#393 1 条评论 0 个 reaction 已指派 0 人 在 GitHub 查看
主要语言
Go
星标
1.2k
派生
213
平均合并
1 天 12 小时
30 天内合并 PR
11

描述

## Summary

`SessionToolRunner` can repeatedly execute a stale tool call after a processed, session-wide `user.interrupt`.

A processed interrupt with no `session_thread_id` invalidates the session's outstanding tool calls. Those `agent.tool_use` / `agent.custom_tool_use` events remain in event history, however, and the runner currently tracks only whether calls were seen or answered. On reconnect, `reconcile` treats the invalidated call as unanswered and dispatches it again.

The server no longer accepts a result for that call, so the result POST receives a permanent 4xx. The failed call remains unanswered and is dispatched again on the next reconcile, creating an execute / reject / reconnect loop that can pin a self-hosted worker to one session.

## Version

- `anthropic-sdk-go` v1.57.0 (`b6d3cc8b6363f88d70e5c08ffb375471e7e8aab8`)
- Reproduced through a self-hosted Managed Agents worker built with `ant` v1.17.0

## Reproduction shape

The failure can be reproduced with a scripted session event service:

1. Emit an `agent.tool_use` with `processed_at = T0`.
2. Process a session-wide `user.interrupt` with `processed_at = T1`, where `T1 > T0` and `session_thread_id` is absent.
3. Reconnect the event stream. Keep the old tool-use event in list history without a corresponding result.
4. Let the runner execute the historical tool call and return a permanent 4xx from the result POST.
5. Reconnect again. The same tool call is executed and rejected again.

There is an additional ordering detail: the list endpoint is ordered by `created_at`, while queued user events receive `processed_at` later. An interrupt can therefore appear before a tool-use event in listed history even though the interrupt's processing timestamp invalidates that tool call. Clearing only the history entries encountered before the interrupt is insufficient; the cutoff must be based on `processed_at`.

## Expected behavior

- A processed session-wide interrupt settles every tool call whose `processed_at` is at or before the interrupt cutoff.
- Calls after the cutoff continue normally.
- Queued interrupts (`processed_at: null`) and thread-targeted interrupts do not cancel unrelated calls.
- A locally running tool receives context cancellation when its call is interrupted.
- A permanent result rejection cannot leave an unrecoverable call eligible for infinite redispatch. If history shows that an interrupt or another runner's result settled the call before the local SSE stream observed it, the runner should treat it as settled; otherwise it should terminate with an error so the worker can release the session.

## Impact

The worker process remains healthy while making no forward progress. With a single worker used for session/workspace affinity, one interrupted session can prevent queued sessions from being claimed until the worker is restarted or the session is cleaned up out of band.

## Related

- #377 and #387 cover a different trigger with the same liveness failure shape: an empty tool result is rejected with 400.
- This issue is specifically about processed `user.interrupt` events leaving historical tool calls eligible for redispatch.

贡献指南

打开贡献指南

调研方向

Start by finding SessionToolRunner and its reconcile logic, then trace how agent.tool_use, agent.custom_tool_use, user.interrupt, processed_at, and result POST failures are handled. Reproduce the event sequence described in the issue against a scripted session event service. Done means session-wide processed interrupts settle earlier tool calls, local tools are cancelled, later calls continue, and permanent 4xx rejections cannot cause infinite redispatch.

由索引模型根据 Issue 内容生成。

评估

技术栈
go
领域
api, backend
Issue 类型
缺陷
难度
4/5
预计耗时
3-5 天
活跃度
冷清
描述清晰度
基本清楚
新手友好度
48/100

把新 issue 发到你的邮箱

精选适合新手参与的 GitHub issue 摘要。