---
title: "JWT secret 未设置导致 dev mode user_id 丢失 + reload 登录失效"
date: 2026-06-28
category: docs/solutions/integration-issues
module: server/auth, server/app, tools/calendar_tool, frontend/stores/auth
problem_type: integration_issue
component: authentication
symptoms:
  - "agent 创建日历事件后回复'已完成'，但日历 UI 中看不到事件"
  - "浏览器 reload 后跳回登录页，refresh token 未过期却验证失败"
  - "calendar.db 中事件 user_id 为 default/zhangsan（LLM hallucinate），UI 查询用 user_id=None"
  - "AuthMiddleware 处于 dev mode，所有请求 current_user.user_id=None"
root_cause: config_error
resolution_type: config_change
severity: critical
tags: [jwt, auth, dev-mode, calendar, user-id, ephemeral-secret, login-state]
---

# JWT secret 未设置导致 dev mode user_id 丢失 + reload 登录失效

## Problem

用户报告4个问题：(1) 启动后出现一堆测试对话且无法删除；(2) 要求创建下周一日历事件，agent 回复已完成但日历里看不到；(3) 日历默认周日开始；(4) reload 后跳回登录页。深入调查发现问题2和4同根同源：`AGENTKIT_JWT_SECRET` 从未设置，触发 dev mode 一系列连锁反应。

## Symptoms

- **日历事件数据不一致**：calendar.db 中事件 `user_id="default"`/`"zhangsan"`（LLM hallucinate），但 `/calendar/events` 路由用 `user_id=None` 查询 → 返回空列表
- **reload 登录失效**：服务器重启后，之前签发的 refresh token 全部失效，前端 `startupCheck` → `whoami(refresh)` → 401 → `startupState='invalid'` → 跳 `/login`
- **AuthMiddleware dev mode**：所有请求 `current_user={"user_id": None, "username": "dev", "role": "admin"}`
- **CalendarTool schema 要求 LLM 提供 user_id**：LLM 不知道真实值，hallucinate 假值写入 DB

## What Didn't Work

- **前次"修复"日历 401**：在 AuthMiddleware dev mode 分支添加 synthetic dev user（`user_id=None`），解决了 401 但引入了 `user_id=None` 数据不一致问题——治标不治本
- **前次"修复"登录状态**：在 auth.ts 注册4个 token provider，但未解决 refresh token 跨重启失效的根本问题
- **假设 API 删除失败**：curl 直接测试 DELETE 端点返回 200，DB 行数减少——API 正常，问题是预存测试数据 + 前端视觉复活

## Solution

### 核心修复：设置持久化 JWT secret

在 `.env` 中添加（`.env` 已被 gitignore，不会泄露）：

```bash
# 生成持久化 secret
python3 -c "import secrets; print(secrets.token_urlsafe(48))"

# 写入 .env
AGENTKIT_JWT_SECRET=<generated_secret>
```

这一步同时解决问题2（AuthMiddleware 退出 dev mode，`current_user.user_id` 从 JWT payload 获取真实值）和问题4（refresh token 跨重启持久化）。

### CalendarTool 注入真实 user_id

[calendar_tool.py](file:///Users/Chiguyong/Code/Fischer/fischer-agentkit/src/agentkit/tools/calendar_tool.py) 修改：

```python
# 之前：schema 要求 LLM 提供 user_id，LLM 会 hallucinate
"required": ["action", "user_id"],

# 之后：移除 user_id from required，改用注入的 default_user_id
def __init__(self, calendar_service, default_user_id: str | None = None):
    ...
    self._default_user_id = default_user_id

def _resolve_user_id(self, kwargs) -> str | None:
    provided = kwargs.get("user_id")
    if provided and isinstance(provided, str) and provided.strip():
        return provided
    return self._default_user_id
```

[app.py](file:///Users/Chiguyong/Code/Fischer/fischer-agentkit/src/agentkit/server/app.py) 在 lifespan 中查询 admin 用户并注入：

```python
# 查询第一个 active admin 用户作为 default_user_id
async with aiosqlite.connect(str(DEFAULT_AUTH_DB_PATH)) as db:
    db.row_factory = aiosqlite.Row
    cur = await db.execute(
        "SELECT id FROM users WHERE is_active = 1 "
        "ORDER BY CASE role WHEN 'admin' THEN 0 ELSE 1 END, created_at LIMIT 1"
    )
    row = await cur.fetchone()
    if row is not None:
        default_cal_user_id = str(row["id"])

calendar_tool = CalendarTool(
    calendar_service=cal_service,
    default_user_id=default_cal_user_id,
)
```

### CalendarGrid firstDay 配置

[CalendarGrid.vue](file:///Users/Chiguyong/Code/Fischer/fischer-agentkit/src/agentkit/server/frontend/src/components/calendar/CalendarGrid.vue) 添加 `firstDay: 1`。

### 数据清理

```bash
# 清理测试对话
sqlite3 ~/.agentkit/conversations.db "DELETE FROM messages; DELETE FROM conversations;"

# 修复 calendar.db 中已存在的 hallucinate user_id
sqlite3 data/calendar.db "UPDATE calendar_events SET user_id = '<admin_user_id>' WHERE user_id IN ('default', 'zhangsan')"
```

## Why This Works

**根因因果链**：

1. `AGENTKIT_JWT_SECRET` 未设置 → `get_jwt_secret()` 返回 None
2. [app.py:812-837](file:///Users/Chiguyong/Code/Fischer/fischer-agentkit/src/agentkit/server/app.py#L812-L837) 中 `explicit_jwt_secret or ""` 传给 AuthMiddleware → `jwt_secret=""` → `_is_dev_mode()=True`
3. dev mode 下 `get_or_create_jwt_secret()` 每次进程启动生成**新的 ephemeral secret**（不持久化）
4. **问题4**：服务器重启 → 新 ephemeral secret → 旧 refresh token 签名不匹配 → whoami 401 → 跳登录页
5. **问题2**：dev mode 下 `current_user.user_id=None`，CalendarTool 由 LLM hallucinate `user_id="default"`，UI 路径用 `None` 查询不匹配 → 日历看不到事件

设置 `AGENTKIT_JWT_SECRET` 后：
- AuthMiddleware 收到真实 secret → `_is_dev_mode()=False` → JWT 验证启用 → `current_user.user_id` 从 payload 获取
- `get_jwt_secret()` 返回持久化 secret → 跨重启一致 → refresh token 持久有效

## Prevention

- **环境变量检查**：在 `app.py` 启动时检查 `AGENTKIT_JWT_SECRET` 是否设置，未设置时打印明显警告（当前 dev mode 日志不够醒目）
- **e2e 测试覆盖**：添加 e2e 测试覆盖以下场景：
  1. 服务器重启后 reload 页面，验证登录状态保持
  2. agent 创建日历事件后，UI 能看到该事件（数据一致性 + `calendar_event_created` WS 消息到达前端，详见 [calendar-agent-create-no-refresh.md](file:///Users/Chiguyong/Code/Fischer/fischer-agentkit/docs/solutions/ui-bugs/calendar-agent-create-no-refresh.md)）
  3. 无效 token 返回 401（非 dev mode 验证）
- **CalendarTool user_id 来源**：多用户场景需通过 agent 框架 contextvar 传递 per-request user_id，当前 `default_user_id` 是 dev 模式单用户简化（代码中已标注 `ponytail:` 注释）
- **配置审计**：`.env.example` 应列出 `AGENTKIT_JWT_SECRET` 并说明必须设置

## Related Issues

同一症状（"agent 创建日历事件后 UI 看不到"）现已记录**三个不同的根因**，调查时需同时排查：

- [日历能力缺失修复 + UI 布局优化](file:///Users/Chiguyong/Code/Fischer/fischer-agentkit/docs/solutions/logic-errors/calendar-capability-and-ui-fixes.md) — 前次日历问题（CalendarTool 接入 ReAct），本次是认证配置导致的 user_id 不匹配，同一领域不同根因
- [Calendar events created via agent chat do not refresh the calendar UI](file:///Users/Chiguyong/Code/Fischer/fischer-agentkit/docs/solutions/ui-bugs/calendar-agent-create-no-refresh.md) — `CalendarService.create_event` 创建成功后未广播 `calendar_event_created` WS 消息（第三个根因，2026-06-29 记录）
- [Portal 平台安全可靠性修复](file:///Users/Chiguyong/Code/Fischer/fischer-agentkit/docs/solutions/security-issues/portal-platform-security-reliability-fixes.md) — 认证相关修复
