快速结论:这个报错出现在自托管 Dify 未合并的 main 上,调用命中历史(hit-histories)接口传入 limit=0(或 limit=-1)时,接口按每页 1 条查询、却按未钳制的 limit 计算 has_more,导致分页永不结束;优先排查该路由是否绑定了带 ge=1 约束的查询模型。
适用环境:Dify Self Hosted (Source),main at a4d74f7c;其余版本、Python、CUDA、显卡等环境信息 Issue 中未确认。
最快修复方案:暂无确认的一步修复方案。Issue 中被验证的方向是:让 AnnotationHitHistoryListApi 像同文件的 AnnotationApi.get 一样,通过 @model_validate(AnnotationHitHistoryListQuery) 绑定请求参数,使 ge=1 约束在进入 handler 前拒绝非正的 page/limit。该改动需要 maintainer 审核合并,尚未落地。
注意事项:该修复只针对 hit-history 路由;Issue 指出 AnnotationApi.get(控制台)与 Service API 的 AnnotationListApi.get 也存在同样的 has_more 重算逻辑,但它们通过 Pydantic 模型的 ge=1 保护,目前不可达,修复时应一并考虑但不要误判为已修。客户端侧对 limit<=0 做校验只能规避、不能修复服务端行为。
问题场景
在自托管 Dify 源码部署中,直接请求标注命中历史接口:
GET /console/api/apps/{app_id}/annotations/{annotation_id}/hit-histories?limit=0
客户端按页遍历、以 has_more 为 false 作为停止条件时,会在数据已取完后继续拿到空页,且每页仍然返回 has_more: true,分页循环无法结束。limit=-1 行为相同。?page=0 也有类似缺口:查询按第 1 页执行,但响应回显 page: 0。
报错原文
Annotation hit-history pager never ends for limit=0: has_more is computed from a page size the query does not use
has_more=page * effective_limit client never stopped after 10 pages
原因分析
已确认的原因(Issue 中经代码定位验证):AnnotationHitHistoryListApi.get 直接用 request.args 以 type=int 读取 page/limit,无下界校验,只通过 effective_limit = min(limit, 100) 钳制上界;而真正执行查询的 libs/pagination.paginate_query 会在查询前把两者下限抬到 1(page = max(1, page)、per_page = max(1, per_page))。于是 limit<=0 时查询按每页 1 条执行,响应里的 has_more 却用未钳制的 effective_limit 计算为 page * effective_limit < total,即 page * 0 < total 恒为真。
进一步定位:同文件已存在带 ge=1 约束的 AnnotationHitHistoryListQuery,但该路由只用 query_params_from_model(...) 喂给 OpenAPI 文档生成,并未用 @model_validate(...) 绑定请求解析;而 AnnotationApi.get 和 Service API 的 AnnotationListApi.get 都走了 @model_validate(...),所以能在 handler 执行前拒绝 limit<=0。这一“模型存在但未接线”的错配是直接原因。PaginatedResult.has_next 本身在上述复现中始终正确,问题出在 handler 自行用未钳制输入重算。
环境排查
- 确认 Dify 部署方式:Self Hosted (Source)。
- 确认代码版本是否为
mainata4d74f7c(Issue 中确认的问题版本)。 - 确认请求是否命中
/console/api/apps/{app_id}/annotations/{annotation_id}/hit-histories路由,且查询串中limit为 0 或负数(或page为 0)。 - 确认该路由是否已用
@model_validate(AnnotationHitHistoryListQuery)绑定参数;若仍只用于 OpenAPI 文档生成,则问题仍可复现。 - Python、PyTorch、CUDA、显卡等版本与本问题无直接关联,Issue 中未提供,无需作为排查项。
解决步骤
- 先确认触发条件:用客户端或
curl请求 hit-histories 接口并传limit=0,观察响应中的has_more是否在数据取完后仍为 true。 - 定位漏洞点:检查
api/controllers/console/app/annotation.py中AnnotationHitHistoryListApi.get是否直接读取request.args,且effective_limit = min(limit, 100)没有下界。 - 按 Issue 验证过的修复方向改造:为该路由改用
@model_validate(AnnotationHitHistoryListQuery)绑定请求,复用其中limit: int = Field(default=20, ge=1)(page同理)的约束,使非正参数在进入 handler 前被拒绝,从根上避免has_more用未钳制值重算。 - 代码改动需提交 PR 并由 maintainer 审核合并;Issue 中该修复尚处于待合并状态,社区版用户可先本地应用后验证。
- 临时规避:在客户端侧不要发送
limit<=0或page<=0,改用正常正整数值,可避免分页不停止。
验证方法
修复后重放同一请求:GET .../hit-histories?limit=0 应被参数校验直接拒绝(而非进入 handler),或至少返回的 limit/page 与查询实际使用的值一致;当最后一页取完后 has_more 必须变为 false,客户端按 has_more 遍历能正常终止。对照 Issue 中的七行数据场景,limit=0 不应再出现超过末页仍返回 has_more: true 的空页。同时回归 limit>100、恰好整页等 #41875 场景以及 AnnotationApi.get、AnnotationListApi.get 等兄弟路由,确认未引入回归。
参考来源
AI 工具推荐
想把多个 AI 模型放在一个入口?
GamsGo AI 集成 ChatGPT、DeepSeek、Gemini、Claude、Midjourney、Veo 等常用模型,适合写作、绘图、视频和日常 AI 工作流。
推广链接:通过此链接购买,我可能获得佣金,不影响你的价格。
这个方案解决了吗?
可以继续搜索完整报错,或查看同一工具的其他排查指南。


