Repository navigation
fix(wechat): 视频号瞬态失败有限重试 + attempts 带脱敏失败原因(#34 #33,取代 #37) - #38
Merged
Merged
Conversation
…easons _run_chain retries retryable per endpoint (wechat: 3 attempts + 0.3s backoff, others: 1 = unchanged). classify may return (decision, reason); _call_endpoint raises EndpointHttpError(status, body) on wechat POST non-2xx dict bodies so attempts records error_body + http_status. fetch_wechat_channels_media drops its hand-rolled loop (was 3x3=9). Task-Id: Fixes-Issue: #34, #33 Agent-Executor: ocgo-muse-pi Agent-Model: muse-spark-1.3-contributor Agent-Effort: unknown Dispatch-Id: dlg-20260923-082148-152a8c Task-Id: MediaResolverAPI-20260923-01
Classify now returns tuples; empty-envelope chain asserts 3 attempts. New: transient-then-hit, exhausted VideoNotFoundError with attempts, tri-state distinctness, cross-boundary desensitization (exc + resolve API), no-retry on ProviderError. Stream media tests stub _call_endpoint. Task-Id: Fixes-Issue: #34, #33 Agent-Executor: ocgo-muse-pi Agent-Model: muse-spark-1.3-contributor Agent-Effort: unknown Dispatch-Id: dlg-20260923-082148-152a8c Task-Id: MediaResolverAPI-20260923-01
…POST Error shape no longer forks on HTTP verb: _call_endpoint raises EndpointHttpError(name, status, body) for every non-2xx dict body so _run_chain always runs classify + has_playable and records http_status (single-endpoint chains like Twitter included). Rewrite the two _call_endpoint contract tests to assert the raise (+ body/status + classify conclusion); add a chain-level case proving the body still reaches the classifier (DouyinTerminalError short-circuit). Task-Id: Fixes-Issue: #34, #33 Agent-Executor: ocgo-muse-pi Agent-Model: muse-spark-1.3-contributor Agent-Effort: unknown Dispatch-Id: dlg-20260923-083824-a438d8 Task-Id: MediaResolverAPI-20260923-01
Agent-Executor: pi-lead Agent-Session: pi-lead-1790151094413-2777230
F1: _should_retry reserves per_timeout + backoff; TimeoutError with retryable attempts below the cap converts to VideoNotFoundError (first-hop hangs and single-endpoint chains unchanged). F2: describe_failure only carries int object_type scalars, non-int yields a fixed type name; _error_body_reason pins int status + str message. Extend desensitization test with dict-object_type chain coverage; add budget-truncation test; append progress paragraph (F3). Task-Id: Fixes-Issue: #34, #33 Agent-Executor: ocgo-muse-pi Agent-Model: muse-spark-1.3-contributor Agent-Effort: unknown Dispatch-Id: dlg-20260923-090551-d1d4f8 Task-Id: MediaResolverAPI-20260923-01
Agent-Executor: codex Agent-Model: gpt-5.6-luna Agent-Effort: xhigh Dispatch-Id: dlg-20260923-093403-18aac2 Task-Id: MediaResolverAPI-20260923-04
Required Gate v2 — 状态面板当前状态:pass · 可合并 当前裁决: Gate 历史(v1;来源为持久化
|
| Run | Attempt | Head | 状态 | 收件人动作 |
|---|---|---|---|---|
| 35844762288 | 1 | 1a03965 |
fail |
要修代码 |
| 35846095955 | 1 | dacff4f |
fail |
要修代码 |
| 35848613996 | 1 | 0230311 |
pass |
可合并 |
历史行按 run_id + run_attempt 去重并只增不删;删除本评论后可由 gate_terminal 制品重建。
Agent-Executor: codex Agent-Model: gpt-5.6-luna Agent-Effort: xhigh Dispatch-Id: dlg-20260923-095531-c5d386 Task-Id: MediaResolverAPI-20260923-04
Agent-Executor: codex Agent-Model: gpt-5.6-luna Agent-Effort: xhigh Dispatch-Id: dlg-20260923-101724-798c9e Task-Id: MediaResolverAPI-20260923-05
This was referenced Sep 23, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
为什么
2026-09-23 12:23 生产一次瞬时的上游空结果把视频号解析打成终态失败(
All providers failed ... attempts=[{'endpoint': 'fetch_video_detail', 'decision': 'retryable'}]),调用方按终态处理、用户任务失败并收告警;同链接 15:59 重放完全正常(issue #34)。事后又答不出「12:23 到底哪一种失败」,因为retryable只记了一个词(issue #33)。根因
retryable的语义是「可能瞬时,值得再来一次」,但引擎对它的唯一补救动作是「换下一个端点」——「重试」和「换源」是两个正交的轴,被合并成了一个动作。多头链上这条错配被兜底掩盖,视频号(TikHub 单源、单端点、无 Cobalt)上直接退化成终态。佐证:下载侧fetch_wechat_channels_media自己手搓了 3 次循环,解析侧一次都没有——同一行为两套实现,行为必然漂移。归因契约的边界(完备性声明,审查按此定 scope)
本 PR 只承诺有界归因,不承诺对任意上游响应形态完备——「对所有形态都能回答为什么」是无限域上的全称命题,既不可被有限测试证明,也不该由本 PR 承担:
tests/fixtures/wechat_channels/*.json记录的形态,以及 TikHub 文档化的 4xx/5xx JSON 错误包(body 无data节点,或带code/message)。retryable与parse_failed两类 attempts 条目都带归因字段——reason ∈ {data_missing, object_type_mismatch, error_body},其中error_body另带 inthttp_status与上游message截断片段;object_type只带 int,或长度 ≤32 且匹配[A-Za-z0-9_.:-]+的 str。bool/float充当object_type等)只承诺三件事:① 凭据不泄漏;② 不崩溃;③ 分类仍只出ok/retryable两态。不承诺归因完备,域外形态的归因精度按需增补(需真实样本,见 backlog issue backlog: 视频号归因的域外形态(畸形上游响应)不承诺完备,待真实样本再增补 #39)。risk-tier: internal,收敛判据数的是本仓判定的 P1(数据丢失/静默出错/崩溃/越权/损坏他人数据),不是外部工具的 severity 标签。本 PR 历轮 finding 的「工具定级 / 本仓判定 / 两问答案」见下方备注。改法
_run_chain(max_attempts_per_endpoint=..., retry_backoff=...),默认1= 现状(其余 8 平台逐字节不变);视频号链传3/0.3s。只对decision=retryable重试;http_error(含超时)与parse_failed各只算一次尝试。fetch_wechat_channels_media改为单次调用(_fetch_wechat_channels内部即 3 次尝试),其后的 media 缺失/不完整/file_size非法等终态转换原样保留。不下移会叠加成 3×3=9 次上游请求。attempt,既有endpoint/decision键名与取值不变),归因字段见上节边界声明。classify契约向后兼容扩展为str(等价(decision, None))或(decision, reason),由引擎归一化;_call_endpoint非 2xx 且 body 为 dict 时一律抛EndpointHttpError(name, status, body)(与 HTTP verb 无关),链上仍以该 body 跑 classify + has_playable,保持既有终态短路与命中语义。type(ot) is int and ot == 0——False/0.0/"0"/ 缺失一律走「非视频」分支(带归因 + 有限重试),不再因False == 0被当成视频节点。asyncio.timeout(total_budget)在某次尝试中途触发)保持既有语义ProviderError("... timed out"),不改判成「内容找不到」——上游可用性故障与内容侧失败,调用方的重试与告警语义不同(video_resolver.py:275把VideoNotFoundError与ProviderError放进同一个 handler,改判没有调用方收益,只有语义污染)。用例:test_chain_budget_timeout_raises_provider_error_not_not_found。_call_endpoint自身抛ProviderError)按既有设计记decision: http_error、链走完后终态为VideoNotFoundError,且异常串不含链级标记endpoint chain timed out。用例:test_chain_attempt_provider_error_is_not_chain_timeout。_should_retry要求剩余预算 >per_timeout + retry_backoff:预算装不下的下一次尝试根本不会启动,因此「retryable 反复失败」这条路径走不到总预算超时,仍是VideoNotFoundError(P2: 视频号解析侧单端点链无瞬态重试,TikHub 瞬时空结果直接终态失败 #34 验收 1),由test_chain_budget_does_not_start_attempt_that_does_not_fit锁住。实测
uv run --extra dev python -m pytest tests/ -q→ 见本次 head 的报告输出(基线 432 passed,本 PR 末轮 +2 用例)_call_endpoint内部契约变更只改写抖音/小红书各一条对应用例(断言异常 +.body/.status,保留「body 交分类器」的 codex #4 本意)len({data_missing, object_type_mismatch, error_body}) == 3full_url/decode_key/url_token)后:str(exc)与POST /api/resolve响应文本中三值零出现;同时断言上游message片段确有出现(防恒真)。另覆盖data.object_type为容器(内含凭据)那一形态:不进 attempts,只留类型名1→ 带值;"1"→ 带值;40 字符 str / 含空白的疑似凭据 str /True/ dict → 只带类型名(收严后False/0.0也不再算合法视频节点)VideoNotFoundError,_call_endpoint调用数 1、消息不含timed outProviderError且含timed out,调用数 2decision: http_error;终态VideoNotFoundError;不含endpoint chain timed out_call_endpoint调用数 == 3、异常类型 isVideoNotFoundError(非 timed out)parse_failed条目带reason: error_body+ inthttp_status(末轮收口)raise EndpointHttpError→return body;超时兜底条件> 1→> 99;isinstance(ot,int)→isinstance(ot,object);_should_retry预算判据→> 0;把 R2 的超时改判注入回去(链级用例转红、attempt 级用例仍绿,证明两条路径互不冒充);末轮两处(删parse_failed归因合并、object_type 判据改回!= 0)。转红原文均贴在各轮报告影响范围
只影响视频号链(重试 + attempts 字段)与
_call_endpoint的内部异常形状(链上行为等价,见上表回归证据)。不新增配置项、不新增端点、不改响应结构与缓存。备注
a3bdc81是主脑的 CHANGELOG 记账(运维备忘:生产 PUBLIC_BASE_URL 已设为内网直连地址(2026-09-02) #15 的启动告警条目,PR fix(startup): PUBLIC_BASE_URL 留空时启动打 WARNING 提示 (#15) #35 合并时声明由持有CHANGELOG.md写权的卡补齐),非实现改动。object_type容器可带凭据进日志/响应VideoNotFoundErrorobject_type归因丢失parse_failed丢error_body归因bool被当0Refs #34、Refs #33(关单由主脑在合入后带证据执行)。