8000 fix(agent): 拆分 history_inconsistent,过期的重试与编辑返回 409 (#1357) · felinics/Memoh@18a101e · GitHub
[go: up one dir, main page]

Skip to content

Commit 18a101e

Browse files
Fodesuchen-ran
andauthored
fix(agent): 拆分 history_inconsistent,过期的重试与编辑返回 409 (#1357)
* fix(agent): run 终态携带落库轮次,重试与编辑校验按客户端过期报 409 Web 端对已结束但未落库的 run 点击重试或编辑时收到 `session_runtime.history_inconsistent`(500)。原因是服务端与前端对"最新可见轮次" 的判定依据不同:服务端查 `bot_history_messages`,前端取本地列表尾部第一条已结束的 assistant 消息。run 以 errored 结束却未落库时(无可见输出的失败按"未发送"处理), 运行时快照仍保留该 run,`error_code` 恒非空,前端据此判为 `stream` 阶段而保留投影 出的轮次,并把数据库里不存在的 turn id 发给服务端。 变更分三部分: 1. Session Runtime 的 `CurrentRunView` 新增 `persisted_turn`,由应用层在 round 落库 的当下经 `Manager.RecordPersistedTurn` 写入(`storeRoundWithOptionsResult`、 `agentStepCommitter.persist`、`replacePersistedTurn` 三处挂点),先于 `FinishRun`, 终态视图自然保留。前端以该字段判定失败阶段(替代 `error_code`)、把已结束且无 `persisted_turn` 的 run 标为 `unpersisted`(其帧只用于结束屏幕上已有的轮次,不再 引入新轮次),并通过 `isTurnUnpersisted` 不对这类轮次提供重试与编辑。 2. 重试/编辑目标校验拆出 409 编码:`session_runtime.turn_not_latest`(目标非最新或 会话无可见轮次)、`session_runtime.turn_incomplete`(缺 assistant 锚点)。前端收到 后自动刷新历史。`history_inconsistent` 保留给真实的持久化故障。 3. 其余复用该码的位置按领域拆分:`session_runtime.reset_unavailable`(503,重置协调 器未配置)、`session_runtime.reset_conflict`(409,重置租约获取失败或丢失)、 `history.delete_failed`(500)、`external_runtime.session_resume_failed`(502,Codex / Claude Code 会话恢复失败,同步替换 `isRuntimeConfigurationError` 列表中的 旧码以保持行为)。新增 `catalog_guard_test.go`:每个 `Code` 常量必须有 catalog 条目, catalog 与 en/zh/ja 的 `errors.*` 叶子键双向对齐,`testdata/codes.golden` 记录已发布 编码与 HTTP 状态并要求只增不改。补齐缺失的 `queue_capacity_exceeded` 文案。 已知残留:owner 失联后由 reaper 结束的 run 没有 `persisted_turn`,前端按未发送处理; `runtime_run_failed` 字面量与队列域扁平编码的迁移另开任务。 * fix(web): 已完成的 run 缺少 persisted_turn 时不按未发送处理 `unpersisted` 与 `isTurnUnpersisted` 只对失败结束(errored / aborted / lost)且无 `persisted_turn` 的 run 生效。完成即意味着 round 已写入;来自尚未携带该字段的服务端 的完成帧仍需追加到 transcript(`run-turn-ownership.test.ts`、 `turn-identity-pairing.test.ts` 覆盖的场景)。 * fix(agent): persisted_turn 走增量补丁;替换运行未落库失败时刷新历史 审阅发现的修正: - `RecordPersistedTurn` 改为发布 `CurrentRunPatch.persisted_turn` 增量而非整份 `current_run_view`。逐步提交的 run(steer / 工具循环)每步都会记录一次,整份视图 会让每个订阅者承担步数 × 内容的传输与拷贝成本。前端 `applyRunPatch` 补上该字段。 - 重试 / 编辑的 run 已产生可见输出但未落库就失败时,屏幕上是失败的新回复而数据库仍是 旧尾部,且新回复不可重试。`send.ts` 在这种情况下刷新历史,让旧尾部回来。 - `agentStepCommitter` 记录的 `request_message_id` 固定为 run 自身的请求消息,不再随 steer 覆写。 - tool approval / ask_user 续跑的请求没有 `TurnID`,`notePersistedTurn` 回退到 `RunHandle.TurnID`。 - 记录失败(租约过期、归属丢失)时以 error 级别带 request / assistant 消息 id 记日志, 便于对账客户端可能的重复发送。 - `send.ts` 超出 max-lines,把两个恢复判定移到 `replacement-recovery.ts`。 * refactor(agent): persisted_turn 收窄为 turn_id,补记录遗漏审计与设计说明 按止血修复的标准收尾: - `PersistedTurnView` 只保留 `turn_id`。前端只需要知道历史里有这一轮,其余信息从历史读; 字段越窄,事件日志重构落地时越容易删除。相应去掉 `agentStepCommitter` 里为它维护的 请求消息 id。 - 前端把"这一轮是否已写入历史"的判定收拢为 `runHistoryState`(active / written / unwritten),投影、阶段判定、重试门禁、替换失败恢复四处都调用它,重构时只换一个函数。 - Session Runtime 新增 `SetPersistedTurnAuditor`:run 以失败结束且没有记录 `persisted_turn` 时,应用层查历史是否已有该 turn(新增 `DBService.GetHistoryTurn`)。有则说明某条持久化 路径漏掉了 `notePersistedTurn`(或记录在租约过期后被拒),前端会把已写入的轮次当成未 发送,此时以 error 级别记录 run / turn / 消息 id,让遗漏可见而不是静默产生重复发送。 - `docs/design/turn-history-event-log.md` 记录根因(turn 不是一等数据结构、三处状态靠 应用层对齐)、事件日志的目标形态、设计文档需要回答的问题,以及本过渡方案的删除条件。 * docs: 移除事件日志设计说明 按要求不入库;代码注释中的引用同步删除。 * fix(i18n): 错误文案只说现象与下一步 用户可见的文案不再描述内部状态(记录、合并、保存/恢复)或操作分支,改为一句话: 出了什么,接下来做什么(刷新 / 稍后重试 / 联系管理员 / 新开会话)。涉及 `session_runtime.history_inconsistent`、`turn_not_latest`、`turn_incomplete`、 `reset_unavailable`、`reset_conflict`、`history.delete_failed`、 `external_runtime.session_resume_failed` 的 en / zh / ja 文案与 Go 侧兜底 Detail。 * fix(i18n): 重置不可用文案改为联系开发者 * fix(agent): 运行时状态发布失败与会话中止失败改用各自的错误码 `history_inconsistent` 只表示历史写入失败。运行时事件发布到 Session Runtime 失败 (Redis 不可用、run 归属丢失)改为 `session_runtime.publish_failed`(503);删除直连 运行时会话后中止在途 run 失败改为 `session_runtime.abort_failed`(500)。补三语文案与 golden。 * refactor(agent): 收窄为错误码拆分,移除 persisted_turn 与落库审计 同行(LibreChat、Onyx、Dify、Open WebUI、LobeChat、Cherry Studio 等)都在 受理时就确定一轮的持久性,失败记在 assistant 位上;用事后通知的 persisted_turn 去补「按失败类型决定是否落库」会引入落库与记录之间的窗口。 这部分改为另起方案处理,本 PR 只保留错误码拆分: - 重试/编辑目标过期返回 409 turn_not_latest / turn_incomplete - 重置、删除历史、外部会话恢复、运行时发布、会话中止各用独立错误码 - history_inconsistent 只表示历史写入失败 - 错误码 catalog 与三语文案的 golden 守卫测试 移除:run 视图的 persisted_turn、RecordPersistedTurn、持久化审计、 GetHistoryTurn,以及前端据此判断未发送/隐藏重试/刷新历史的逻辑。 Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: 晨苒 <16112591+chen-ran@users.noreply.github.com>
1 parent 9262b6e commit 18a101e

17 files changed

Lines changed: 526 additions & 49 deletions

File tree

‎apps/web/src/i18n/locales/en.json‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@
164164
"queue_admission_overloaded": "The queue is busy. Please retry shortly.",
165165
"queue_admission_unavailable": "Queue admission is temporarily unavailable. Please retry shortly.",
166166
"queue_item_not_pending": "This message is no longer pending in the queue.",
167+
"queue_capacity_exceeded": "This session queue is full. Cancel or wait for pending items before adding more.",
167168
"profile": {
168169
"request_invalid": "The profile update request is invalid.",
169170
"title_model_invalid": "The selected title model is unavailable or is not a chat model.",
@@ -274,7 +275,16 @@
274275
"session_runtime": {
275276
"session_busy": "This conversation is still working on your previous message. Please try again in a moment.",
276277
"invocation_conflict": "This message was already sent with different content. Reload the conversation to see what was saved.",
277-
"history_inconsistent": "The conversation history could not be reconciled. Refresh and try again."
278+
"history_inconsistent": "The conversation could not be saved. Refresh and try again.",
279+
"turn_not_latest": "The conversation has newer messages. Refresh and try again.",
280+
"turn_incomplete": "This message cannot be retried right now. Refresh the conversation.",
281+
"reset_unavailable": "Conversation history cannot be reset right now. Contact the developers.",
282+
"reset_conflict": "The conversation is busy. Please try again shortly.",
283+
"publish_failed": "The conversation could not be updated. Please try again shortly.",
284+
"abort_failed": "The conversation was deleted, but its running task could not be stopped. Refresh and try again."
285+
},
286+
"history": {
287+
"delete_failed": "The conversation history could not be deleted. Please try again."
278288
},
279289
"context_lifecycle": {
280290
"request_invalid": "The context lifecycle request is invalid.",
@@ -285,7 +295,8 @@
285295
},
286296
"external_runtime": {
287297
"auth_required": "Codex is not connected yet. Open Agent settings to connect ChatGPT or configure an API key.",
288-
"unavailable": "The external Agent runtime is unavailable. Check the Agent setup and workspace, then try again."
298+
"unavailable": "The external Agent runtime is unavailable. Check the Agent setup and workspace, then try again.",
299+
"session_resume_failed": "The session could not be resumed. Try again or start a new conversation."
289300
},
290301
"app": {
291302
"not_found": "This App installation was not found.",

‎apps/web/src/i18n/locales/ja.json‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -161,6 +161,7 @@
161161
"queue_admission_overloaded": "キューが混雑しています。しばらくしてから再試行してください。",
162162
"queue_admission_unavailable": "キューサービスは一時的に利用できません。しばらくしてから再試行してください。",
163163
"queue_item_not_pending": "このメッセージは待機中のキューにありません。",
164+
"queue_capacity_exceeded": "このセッションのキューは満杯です。待機中の項目をキャンセルするか完了を待ってから追加してください。",
164165
"profile": {
165166
"request_invalid": "プロフィール更新リクエストが無効です。",
166167
"title_model_invalid": "選択したタイトルModelは利用できないか、チャットModelではありません。",
@@ -271,7 +272,16 @@
271272
"session_runtime": {
272273
"session_busy": "このセッションはまだ前のメッセージを処理しています。しばらくしてからもう一度お試しください。",
273274
"invocation_conflict": "このメッセージは別の内容で既に送信されています。セッションを再読み込みして保存済みの内容を確認してください。",
274-
"history_inconsistent": "会話履歴を正しく統合できませんでした。再読み込みして、もう一度お試しください。"
275+
"history_inconsistent": "会話を保存できませんでした。再読み込みして、もう一度お試しください。",
276+
"turn_not_latest": "会話に新しいメッセージがあります。再読み込みして、もう一度お試しください。",
277+
"turn_incomplete": "このメッセージは今は再試行できません。会話を再読み込みしてください。",
278+
"reset_unavailable": "現在、会話履歴をリセットできません。開発者に連絡してください。",
279+
"reset_conflict": "会話は処理中です。しばらくしてからもう一度お試しください。",
280+
"publish_failed": "会話を更新できませんでした。しばらくしてからもう一度お試しください。",
281+
"abort_failed": "会話は削除されましたが、実行中のタスクを停止できませんでした。再読み込みしてください。"
282+
},
283+
"history": {
284+
"delete_failed": "会話履歴を削除できませんでした。もう一度お試しください。"
275285
},
276286
"context_lifecycle": {
277287
"request_invalid": "コンテキストライフサイクルのリクエストが無効です。内容を確認してもう一度お試しください。",
@@ -282,7 +292,8 @@
282292
},
283293
"external_runtime": {
284294
"auth_required": "Codex はまだアカウントに接続されていません。Agent の設定で ChatGPT に接続するか、API Key を設定してください。",
285-
"unavailable": "External Agent の Runtime を利用できません。Agent の設定と Workspace を確認して、もう一度お試しください。"
295+
"unavailable": "External Agent の Runtime を利用できません。Agent の設定と Workspace を確認して、もう一度お試しください。",
296+
"session_resume_failed": "セッションを再開できませんでした。もう一度お試しいただくか、新しい会話を開始してください。"
286297
},
287298
"app": {
288299
"not_found": "このアプリのインストールは見つかりませんでした。",

‎apps/web/src/i18n/locales/zh.json‎

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,7 @@
164164
"queue_admission_overloaded": "队列当前繁忙,请稍后重试。",
165165
"queue_admission_unavailable": "队列服务暂时不可用,请稍后重试。",
166166
"queue_item_not_pending": "这条消息已不在待处理队列中。",
167+
"queue_capacity_exceeded": "该会话队列已满,请取消或等待待处理项完成后再添加。",
167168
"profile": {
168169
"request_invalid": "个人资料更新请求无效。",
169170
"title_model_invalid": "所选标题模型不可用或不是聊天模型。",
@@ -274,7 +275,16 @@
274275
"session_runtime": {
275276
"session_busy": "这个会话还在处理上一条消息,请稍后再试。",
276277
"invocation_conflict": "这条消息已用不同内容提交过,请刷新会话查看已保存的内容。",
277-
"history_inconsistent": "会话历史无法正确合并,请刷新后重试。"
278+
"history_inconsistent": "会话记录保存出错,请刷新后重试。",
279+
"turn_not_latest": "会话已有新内容,请刷新后重试。",
280+
"turn_incomplete": "这条消息暂时无法重试,请刷新会话。",
281+
"reset_unavailable": "当前无法重置会话历史,请联系开发者。",
282+
"reset_conflict": "会话正忙,请稍后再试。",
283+
"publish_failed": "会话状态更新失败,请稍后再试。",
284+
"abort_failed": "会话已删除,但运行中的任务未能停止,请刷新。"
285+
},
286+
"history": {
287+
"delete_failed": "会话历史删除失败,请重试。"
278288
},
279289
"context_lifecycle": {
280290
"request_invalid": "上下文生命周期请求无效,请检查后重试。",
@@ -285,7 +295,8 @@
285295
},
286296
"external_runtime": {
287297
"auth_required": "Codex 尚未连接账号。请前往 Agent 设置连接 ChatGPT,或配置 API Key。",
288-
"unavailable": "外部 Agent 运行时暂不可用。请检查 Agent 配置和工作区后重试。"
298+
"unavailable": "外部 Agent 运行时暂不可用。请检查 Agent 配置和工作区后重试。",
299+
"session_resume_failed": "会话无法恢复,请重试或新开会话。"
289300
},
290301
"app": {
291302
"not_found": "未找到该应用安装记录。",

‎internal/agent/application/service_retry_edit.go‎

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -175,10 +175,12 @@ func (s *Service) prepareRetryLatestTurnOperation(ctx context.Context, sessionID
175175
}
176176
// The turn's own assistant anchor, not a message the client named: a turn
177177
// that reached the client without one is a history the server cannot cut.
178+
// The request message is still there, so the client may edit instead.
178179
replaceFromMessageID := strings.TrimSpace(turn.AssistantMessageID)
179180
if replaceFromMessageID == "" {
180-
return messagepkg.HistoryTurn{}, "", apperror.New(
181-
apperror.CodeSessionHistoryInconsistent,
181+
return messagepkg.HistoryTurn{}, "", apperror.Wrap(
182+
apperror.CodeSessionTurnIncomplete,
183+
fmt.Errorf("turn %s has no assistant anchor", turn.ID),
182184
nil,
183185
)
184186
}
@@ -267,14 +269,15 @@ func (s *Service) resolveLatestVisibleTurn(ctx context.Context, sessionID, turnI
267269
// A session with no visible turn at all and a session whose latest turn
268270
// is a different one are the same answer to the client: the round it
269271
// named is not the one the server can replace, so it has to reload.
270-
// Both are reported with that code instead of the store's own wording.
272+
// Both are stale-client conflicts, not history faults, and are reported
273+
// with the 409 code instead of the store's own wording.
271274
if errors.Is(err, messagepkg.ErrNoVisibleTurn) {
272-
return messagepkg.HistoryTurn{}, apperror.Wrap(apperror.CodeSessionHistoryInconsistent, err, nil)
275+
return messagepkg.HistoryTurn{}, apperror.Wrap(apperror.CodeSessionTurnNotLatest, err, nil)
273276
}
274277
return messagepkg.HistoryTurn{}, fmt.Errorf("load latest visible turn: %w", err)
275278
}
276279
if strings.TrimSpace(latest.ID) != turnID {
277-
return messagepkg.HistoryTurn{}, apperror.Wrap(apperror.CodeSessionHistoryInconsistent, errors.New(notLatest), nil)
280+
return messagepkg.HistoryTurn{}, apperror.Wrap(apperror.CodeSessionTurnNotLatest, errors.New(notLatest), nil)
278281
}
279282
return latest, nil
280283
}

‎internal/agent/application/service_retry_edit_test.go‎

Lines changed: 14 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,13 @@ func TestPrepareReplacementOperationUsesPersistedTurnBoundary(t *testing.T) {
121121
}
122122
service := &Service{messageService: messages}
123123

124-
if _, err := service.PrepareRetryLatestTurnOperation(context.Background(), "session-1", "turn-old"); err == nil {
125-
t.Fatal("prepare retry operation on a superseded turn: want error")
124+
_, err := service.PrepareRetryLatestTurnOperation(context.Background(), "session-1", "turn-old")
125+
if got := apperror.CodeOf(err); got != apperror.CodeSessionTurnNotLatest {
126+
t.Fatalf("retry on a superseded turn: error code = %q, want %q", got, apperror.CodeSessionTurnNotLatest)
126127
}
127-
if _, err := service.PrepareEditLatestTurnOperation(context.Background(), "session-1", "turn-old"); err == nil {
128-
t.Fatal("prepare edit operation on a superseded turn: want error")
128+
_, err = service.PrepareEditLatestTurnOperation(context.Background(), "session-1", "turn-old")
129+
if got := apperror.CodeOf(err); got != apperror.CodeSessionTurnNotLatest {
130+
t.Fatalf("edit on a superseded turn: error code = %q, want %q", got, apperror.CodeSessionTurnNotLatest)
129131
}
130132
})
131133

@@ -147,19 +149,22 @@ func TestPrepareReplacementOperationUsesPersistedTurnBoundary(t *testing.T) {
147149
service := &Service{messageService: messages}
148150

149151
_, err := service.PrepareRetryLatestTurnOperation(context.Background(), "session-1", "turn-old")
150-
if got := apperror.CodeOf(err); got != apperror.CodeSessionHistoryInconsistent {
151-
t.Fatalf("error code = %q, want %q", got, apperror.CodeSessionHistoryInconsistent)
152+
if got := apperror.CodeOf(err); got != apperror.CodeSessionTurnIncomplete {
153+
t.Fatalf("error code = %q, want %q", got, apperror.CodeSessionTurnIncomplete)
154+
}
155+
if apperror.CauseOf(err) == nil {
156+
t.Fatal("incomplete turn error must carry a private cause for logs")
152157
}
153158
})
154159

155160
// The store's own no-rows wording used to reach the composer verbatim.
156-
t.Run("a session with no visible turn reports the history code", func(t *testing.T) {
161+
t.Run("a session with no visible turn reports the stale-turn code", func(t *testing.T) {
157162
messages := &replacementOperationMessageService{latestErr: messagepkg.ErrNoVisibleTurn}
158163
service := &Service{messageService: messages}
159164

160165
_, err := service.PrepareRetryLatestTurnOperation(context.Background(), "session-1", "turn-old")
161-
if got := apperror.CodeOf(err); got != apperror.CodeSessionHistoryInconsistent {
162-
t.Fatalf("error code = %q, want %q", got, apperror.CodeSessionHistoryInconsistent)
166+
if got := apperror.CodeOf(err); got != apperror.CodeSessionTurnNotLatest {
167+
t.Fatalf("error code = %q, want %q", got, apperror.CodeSessionTurnNotLatest)
163168
}
164169
})
165170
}

‎internal/agent/application/service_runtime_turn.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1030,7 +1030,7 @@ func isRuntimeConfigurationError(err error) bool {
10301030
switch apperror.CodeOf(err) {
10311031
case apperror.CodeExternalRuntimeAuthRequired, apperror.CodeExternalRuntimeUnavailable,
10321032
apperror.CodeRuntimeControlGoalRequiresDefaultMode,
1033-
apperror.CodeSessionHistoryInconsistent,
1033+
apperror.CodeExternalRuntimeSessionResumeFailed,
10341034
apperror.CodeACPModelSelectionUnsupported, apperror.CodeACPModelIDRequired,
10351035
apperror.CodeACPModelUnavailable, apperror.CodeACPReasoningUnsupported,
10361036
apperror.CodeACPReasoningEffortRequired, apperror.CodeACPReasoningUnavailable,

‎internal/agent/application/turn_service.go‎

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -252,12 +252,22 @@ func runtimeHistoryError(err error) error {
252252
return apperror.Wrap(apperror.CodeSessionHistoryInconsistent, err, nil)
253253
}
254254

255+
// runtimePublishError names a failure to publish the run's live state to the
256+
// Session Runtime. History is not involved (Redis down, run ownership lost),
257+
// so it must not read as a history fault.
258+
func runtimePublishError(err error) error {
259+
if err == nil || apperror.CodeOf(err) != "" {
260+
return err
261+
}
262+
return apperror.Wrap(apperror.CodeSessionPublishFailed, err, nil)
263+
}
264+
255265
func (s *Service) turnAgentEventPublisher(handle sessionruntime.RunHandle) func(context.Context, native.StreamEvent) error {
256266
if s == nil || s.publishTurnEvent == nil {
257267
return nil
258268
}
259269
return func(ctx context.Context, event native.StreamEvent) error {
260-
return runtimeHistoryError(s.publishTurnEvent(ctx, handle, event))
270+
return runtimePublishError(s.publishTurnEvent(ctx, handle, event))
261271
}
262272
}
263273

‎internal/agent/application/turn_service_test.go‎

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -315,8 +315,8 @@ func TestStartTurnFailsWhenRuntimeEventPublicationFails(t *testing.T) {
315315
if got := admitter.awaitFinish(t); got.status != sessionruntime.RunStatusErrored {
316316
t.Fatalf("publication failure status = %q, want %q", got.status, sessionruntime.RunStatusErrored)
317317
}
318-
if got := apperror.CodeOf(publicErr); got != apperror.CodeSessionHistoryInconsistent {
319-
t.Fatalf("publication failure code = %q, want %q", got, apperror.CodeSessionHistoryInconsistent)
318+
if got := apperror.CodeOf(publicErr); got != apperror.CodeSessionPublishFailed {
319+
t.Fatalf("publication failure code = %q, want %q", got, apperror.CodeSessionPublishFailed)
320320
}
321321
if got := apperror.CauseOf(publicErr); !errors.Is(got, publishErr) {
322322
t.Fatalf("private publication cause = %v, want %v", got, publishErr)

‎internal/agent/runtime/claudecode/driver.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -324,7 +324,7 @@ func (d *Driver) Prompt(ctx context.Context, input external.PromptInput) (extern
324324
workDir := strings.TrimSpace(metadataString(input.RuntimeMetadata, "project_path"))
325325
storedSessionID, err = d.ensureResumableSession(ctx, client, input, storedSessionID)
326326
if err != nil {
327-
return external.PromptResult{}, apperror.Wrap(apperror.CodeSessionHistoryInconsistent, err, nil)
327+
return external.PromptResult{}, apperror.Wrap(apperror.CodeExternalRuntimeSessionResumeFailed, err, nil)
328328
}
329329
// The mount must survive a caller disconnect exactly as long as the CLI
330330
// process does (the interrupt handshake still runs tools).

‎internal/agent/runtime/codex/checkpoint.go‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -350,7 +350,7 @@ func readRolloutMeta(ctx context.Context, fs checkpointFS, root, rel string) (ro
350350
}
351351

352352
func checkpointError(err error) error {
353-
return apperror.Wrap(apperror.CodeSessionHistoryInconsistent, err, nil)
353+
return apperror.Wrap(apperror.CodeExternalRuntimeSessionResumeFailed, err, nil)
354354
}
355355

356356
func validateRolloutPath(rel string) error {

0 commit comments

Comments
 (0)
0