Skip to content

bug: session/load 回退时 external_id 被覆盖,导致数小时会话日志丢失 #146

@MoozLee

Description

@MoozLee

Bug 描述

session/load 失败(如上游 API 代理返回 500 或连接中断)时,后端回退到 session/new无条件覆盖 conversation 的 external_id 为新的 session ID。这会导致旧的 session 文件被孤立——该文件中存储的所有对话轮次永久不可访问。

根因分析

两个代码位置共同导致此问题:

1. connection.rs:1133-1158 — session/new 回退逻辑

session/load 失败后,代码 emit 一个 error 事件,然后创建全新的 session:

if !err_str.contains("Method not found") {
    emit_with_state(&state, &emitter_clone, AcpEvent::Error {
        message: format!("Failed to load session, starting new: {e}"),
        // ...
    }).await;
}
let new_resp = cx.send_request_to(Agent, build_new_session_request(agent_type, &cwd))
    .block_task().await?;
let fallback_sid = new_resp.session_id.0.to_string();
// 用 fallback_sid 发出 SessionStarted

2. lifecycle.rs:93-103 — 无条件覆盖 external_id

SessionStarted 处理器总是覆盖 external_id,没有对已有值做任何保护:

AcpEvent::SessionStarted { session_id } => {
    if let Some(cid) = conversation_id {
        conversation_service::update_external_id(db_conn, cid, session_id.clone()).await?;
    }
}

影响

  • 数据丢失:实际案例中,4 小时以上的会话日志(18:41–23:00)永久丢失。旧的 session JSONL 文件仍存在于磁盘,但不再被任何 conversation 行引用。
  • 静默失败:用户看到 "Failed to load session, starting new" 错误标记,但不知道历史记录已丢失。后续 reload 显示空会话。
  • 触发条件:任何瞬态上游故障(API 代理连接中断、网络超时、agent 崩溃)都可能触发。

复现步骤

  1. 与 AI agent 进行对话,积累多个轮次
  2. 使 session/load 失败(如上游 API 代理返回 500)
  3. 后端回退到 session/new,覆盖 external_id
  4. 重新加载会话——所有历史轮次消失

修复建议

方案 A(最小改动):在 lifecycle.rs 中,当 conversation 已有非空 external_id 时跳过覆盖:

AcpEvent::SessionStarted { session_id } => {
    if let Some(cid) = conversation_id {
        // 仅在尚未关联时设置 external_id
        if conversation_service::get_external_id(db_conn, cid).await?.is_none() {
            conversation_service::update_external_id(db_conn, cid, session_id.clone()).await?;
        }
    }
}

方案 B(更严格):回退路径不发出 SessionStarted 事件,改为发出 SessionLoadFailed 事件,由前端处理而不创建新 session。

环境

  • Agent: Claude Code (claude-cli/2.1.119)
  • 触发错误: 上游 API 代理返回 HTTP 500 + unexpected EOF

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions