Skip to content

fix(qoder-checkin): 每日活动 id 批次轮换自愈——最小头补学 + conf 覆盖表 + 409 同轮重试 - #106

Closed
wenzhongliangzai wants to merge 1 commit into
smart-open:docker_mainfrom
wenzhongliangzai:fix/qoder-campaign-id-selfheal
Closed

wenzhongliangzai wants to merge 1 commit into
smart-open:docker_mainfrom
wenzhongliangzai:fix/qoder-campaign-id-selfheal

Conversation

@wenzhongliangzai

Copy link
Copy Markdown

问题:每日活动 id 批次轮换后全天签到失败,且「自动重学」失效

生产实证(2026-10-14,docker 1.4.x,4 账号):全天 6 轮调度(10:15/10:40/12:41/14:42/16:43/18:44)全部失败,报错均为

每日领 100 Credits 盲发失败:每日活动 id 已过期(409 CAMPAIGN_NOT_ACTIVE,活动每日轮换)——将从活动列表自动重学

但「自动重学」全天未命中 → 409 死循环(重学只发生在列表请求后,而列表里根本没有可学条目),当日 4×100 Credits 全部丢失,只能靠外部脚本手工补领。

根因(两层,均实证)

  1. KNOWN_DAILY_CAMPAIGNS 兜底表停在 act-20260930 批次 id;上游已轮换到 act-20261013 批次(批次内 id 多日有效,跨批次必然 409)。
  2. 重学失效的直接根因:GET /sash/api/v1/me/campaigns 按请求形态分桶投放——
    • 带设备指纹头族的容器形态(Cosy-MachineId/MachineToken/派生 MachineCode/MachineType 等,即现 build_auth_headers 形态)在领取前看不到 CLAIMABLE 条目(已领 grant 才无条件可见);
    • 于是列表内没有任何 CLAIM_BENEFIT + CREDITS + 窗口内 条目,learn_daily_campaign_ids 每轮必然扑空;
    • 对照实验(同 IP、同 token,仅差请求头):最小头(仅 Authorization / Cosy-ClientType / User-Agent / Content-Type)→ CLAIMABLE 可见;且最小头形态盲发 claim 直领成功(4/4 账号 200 + CLAIMED + replayed=false),claim 端点不受列表分桶约束。

修复

# 改动 说明
1 兜底表更新为 act-20261013-603 批次内多日窗口有效
2 conf 覆盖表 <conf>/qoder_campaigns.json 格式 [{"id":"…","name":"…"}] 或 {"campaigns":[…]};条目优先于内置表、每轮读取;批次再轮换时改文件即换 id,免重编译;解析容错(非法 id/垃圾 JSON 忽略并 app_log 留痕)
3 最小头补学 learn_from_min_headers 主列表零可学条目时以最小头再拉一次列表重学;learned 整轮共享,仅每轮首个扑空账号实际发起
4 409 同轮自愈 fallback_with_relearn 盲发候选全部 CAMPAIGN_NOT_ACTIVE 时最小头补学并同轮重试(不再等下一轮 30 分钟起步退避);过期 id 从 learned 与共享候选表剔除,同轮后续账号不再重复盲发同一 409
5 诊断日志 最小头列表视野(脱敏:activityKey/campaignId/状态/奖励类型/窗口)与 409 时候选池现状落 app.log——排障时能直接看到"工具到底看到了什么"

测试

  • cargo test -p aiwork-core qoder:136 passed / 0 failed(含新增 parse_conf_campaigns_shapes、merge_campaign_table_conf_first;cargo check 零警告);
  • 生产验证(官方 1.4.12 镜像 overlay 本补丁二进制):切换后手动触发一轮,4/4 账号「already 每日奖励已领取」(已领 grant 判定路径命中,409 消失);最小头直领路径已在故障当日 4/4 账号实证。

Refs #66(健壮性①②的同场景补全:列表分桶对容器形态不可见 CLAIMABLE 时,重学与兜底双双失效的路径)

生产实证(2026-10-14):4 账号全天 6 轮签到全败 409 CAMPAIGN_NOT_ACTIVE,自动重学全天未命中。根因:

1. KNOWN_DAILY_CAMPAIGNS 停在 act-20260930 批次 id,上游已轮换 act-20261013 批次;
2. campaigns 列表按请求形态分桶投放:带设备指纹头族的容器形态领取前看不到 CLAIMABLE 条目
   (已领 grant 无条件可见),列表无可学条目,learn_daily_campaign_ids 必然扑空;
   同 IP 同 token 最小头(鉴权四件套)可见 CLAIMABLE 且盲发 claim 直领成功(4/4 账号实测)。

修复:兜底表更新 act-20261013-603;conf 覆盖表 <conf>/qoder_campaigns.json(优先内置表,
每轮读取,轮换改文件即换 id 免重编译);learn_from_min_headers 最小头补学(零可学条目时触发,
整轮共享);fallback_with_relearn 409 全过期时最小头补学并同轮重试,过期 id 跨账号剔除;
诊断日志(最小头列表视野脱敏 + 409 候选池现状落 app.log)。

cargo test -p aiwork-core qoder:136 passed / 0 failed。

Refs smart-open#66
@wenzhongliangzai

Copy link
Copy Markdown
Author

补充一下这个问题的发现和处理过程,给后面同样撞上 409 的人一个参照。

一、怎么发现的

13 号日历还是全绿(4 个号全部已领),14 号 10:15 调度跑完直接变红格,点开明细 4 个号全是同一句:409 CAMPAIGN_NOT_ACTIVE 每日活动 id 已过期——将从活动列表自动重学。一开始没当回事,觉得重学机制会自己兜住,结果 10:40、12:41、14:42、16:43、18:44 每轮退避重试都是同一个 409——"将从活动列表自动重学"这句话喊了一整天,一个新 id 都没学回来,这才意识到不对劲:报错承诺的兜底路径本身就是断的。

二、什么时候确认 ID 会切换*

兜底表里那个 id 是 9 月底抓包记下来的(act-20260930 那批),断断续续用了差不多两周都正常,14 号突然全体 409,再对照报错文案里的"活动每日轮换",基本就清楚了:上游活动按批次投放,一批 id 能用一两周,跨批次必死。这次就是 13/14 号批次轮换,表里的 id 过期了。

三、定位过程

既然报错说"从活动列表重学",那容器眼里的列表到底长什么样?我导出一个号的凭证,直接去调 campaigns 接口,同一个号、同一个 IP、同一个 token,两种头各拉一遍:

  • 按容器现在的形态(带全套设备指纹头)请求:列表里只有个 9 月的 VIEW_DETAILS 旧活动,当天可领的活动根本不出现——所以重学每轮都扑空,不是学习逻辑的锅,是列表压根不给你看;
  • 只带 Authorization / Cosy-ClientType / User-Agent / Content-Type 四个头再拉:当天 CLAIMABLE 的活动就躺在列表里,id 现成的。顺手拿这个 id 盲发 claim,4 个号全部 200 + CLAIMED + replayed=false,当天 4×100 积分就这么捡回来了。

结论:上游按请求形态分桶投放,claim 接口不挑形态(id 对就能领),但列表接口会把工具这套头归进"看不到可领活动"的桶。所以本 PR 的思路是:列表看不到就换最小头去拉列表重学(learn_from_min_headers),拿到 id 照常盲发直领;409 之后当场再补学一次重试(fallback_with_relearn),不等下一轮退避;conf 覆盖表是最后保底,万一哪天连最小头也被过滤,改 conf/qoder_campaigns.json 手动喂新 id 即可,不用重编镜像。

四、打个比方

像拿同一张会员卡去柜台领每日礼品:穿得"像机器人"(带一整套设备指纹头)去,柜员按规程只给你看历史记录,可领清单不外传;把行头脱了、只刷卡报身份,今天的清单就直接给你。更妙的是真发礼品的时候柜员不看穿着——报对清单上的编号就能领。补丁干的事就是:清单看不到就换便装去要清单,拿到编号再去领;要是这次扑空了,当场再去要一次清单重试,而不是干等半小时后下一轮。

smart-open pushed a commit that referenced this pull request Oct 14, 2026
移植自 #106(分支 fix/qoder-campaign-id-selfheal,
head 663b5cb,base docker_main),按本地 main
分支代码位置(src-tauri/src/tasks/qoder_checkin.rs)重新落位:

- 兜底表 KNOWN_DAILY_CAMPAIGNS 更新为 act-20261013-603 批次 id
- 新增 conf 覆盖表 conf/qoder_campaigns.json:每轮读取免重编译换 id,
  解析容错(id 白名单/长度校验)+ 异常 app_log 留痕
- 新增最小头补学 learn_from_min_headers:容器形态列表不可见 CLAIMABLE 时
  以鉴权四件套形态补学;脱敏视野落 app_log
- 新增 fallback_with_relearn 409 同轮自愈:过期候选从 learned/table 双池
  剔除跨账号防重复盲发;409 候选池现状诊断落 app_log
- CAMPAIGN_NOT_ACTIVE 特征码收敛为常量

与 PR 的差异:保留 issue #105 主列表当轮重学闭环并叠加最小头补学层;
401 重试路径最小头学习令牌随刷新同步更新(修正 PR 使用轮换前旧令牌隐患)。

验证:cargo test 914 passed / 0 failed(11 ignored),零警告。
smart-open pushed a commit that referenced this pull request Oct 14, 2026
移植 PR #106(head 663b5cb,手工语义移植,未 merge),适配本地 BlindRound
结构与 issue #105 当轮重学闭环:

- 兜底表 KNOWN_DAILY_CAMPAIGNS 更新至 act-20261013-603 批次 id(旧
  act-20260930 批次已过期,409 CAMPAIGN_NOT_ACTIVE 全天死循环的根因之一)
- conf 覆盖表 <conf>/qoder_campaigns.json:免重编译人工下发,条目优先于
  内置表,每轮 load_campaign_table 读取;解析容错(非法 id/垃圾 JSON
  忽略并 app_log 留痕)
- 最小头补学 learn_from_min_headers:仅鉴权四件套(Authorization/
  Cosy-ClientType/User-Agent/Content-Type)的请求形态可见 CLAIMABLE
  (容器形态分桶投放领取前不可见,2026-10-14 探针实证),叠加进当轮
  重学链(主形态宽松档重学 → 仍空 → 最小头补学);learned 整轮共享,
  仅每轮首个扑空账号实际发起;列表视野脱敏落 app_log
- 409 过期 id 同步从跨账号共享候选表 table 剔除(blind_round),同轮
  后续账号不再重复盲发同一 409;conf 表 409 诊断与候选池现状落 app_log
- 移植修正:401 刷新后 min_token 同步更新为当前有效令牌(PR 原实现重试
  路径误用旧令牌,最小头请求会 401 失败)
- CAMPAIGN_NOT_ACTIVE_TAG 特征码常量化;409 文案对齐两级重学实际行为

测试:新增 parse_conf_campaigns_shapes / merge_campaign_table_conf_first;
cargo test -p aiwork-core qoder 145 passed / 0 failed
@smart-open

Copy link
Copy Markdown
Owner

已处理 ✅ —— 本 PR 的修复已手工移植合入 docker_main 分支(commit 6e34f22),回归验证全绿(cargo test --workspace 685+12 通过、tsc 0 错误、vitest 61 通过)。

为什么没有直接 merge

docker_main 是 Docker/Web-only 轻量产品线(axum 单体 + 浏览器管理面),与 main(桌面版)不是同一产品形态;本仓库对跨分支修复的约定是手工语义移植、不使用 merge 操作,以保证与目标分支架构的兼容。具体到本 PR:

  1. 结构适配(叠加而非照搬):PR 新增的 fallback_with_relearn 与本分支 issue qoder cn 签到失败 #105 已落地的「当轮重学闭环」(BlindRound 重构 + 宽松档三档重学)功能重叠,直接合并会产生两套并行的重学路径。移植时将 PR 的核心贡献——最小头补学(仅鉴权四件套请求形态;列表端点按请求形态分桶投放,容器形态领取前不可见 CLAIMABLE、最小头可见,10-14 全天 409 事故实证)——叠加进本地当轮重学链:主形态宽松档重学 → 仍空 → 最小头补学,保持单一重学链路。
  2. 移植修正一处 bug:PR 原实现的重试路径在 401 刷新后仍用旧令牌做最小头补学(会 401 失败),移植时已改为刷新后的新令牌。
  3. 随移植一并落地:KNOWN_DAILY_CAMPAIGNS 兜底表更新至 act-20261013-603 批次;conf 覆盖表 <conf>/qoder_campaigns.json(免重编译人工下发,每轮读取,条目优先于内置表);409 过期 id 同步从跨账号共享候选表剔除(同轮后续账号不再重复盲发同一 409);最小头列表视野与 409 时候选池现状落 app_log 诊断。

感谢定位与修复 🙏 最小头可见性的探针实证是本次「批次轮换全天 409」自愈闭环的关键输入。main 分支(桌面版)如需同款修复,可直接采用本 PR 或参考上述移植点。本 PR 关闭处理。

@smart-open smart-open closed this Oct 14, 2026
smart-open pushed a commit that referenced this pull request Oct 14, 2026
冲突解决(11 文件):
- routes/wb_route 记账:取 main 即时落账(rotating 口径 + M4 缓存 tokens 旁路),
  删除 macos_main 遗留 attempt_usage 延迟消费防重复记账;保留 backoff_wait
  断连感知退避并并入 M5 失败终态补账
- qoder_checkin:FallbackOutcome::Failed 保 macos_main 三元组(reward+明细不丢账),
  fallback_with_relearn(PR #106)与 classify_blind_step kind 防线取 main,
  409 剔除/调用点两侧合流,测试吸收 main ①-⑥ 用例并适配七参签名
- migrate rename_no_clobber / switcher 错误文案 / wb_sse·qoder_common 注释:取 main
- qoder_common/qoder_refresh:near_expiry_no_refresh(macos_main)与 refresh_busy(main)并存
- user-manual:v3.7.12 + macOS 平台标注保留

验证:cargo test 936 passed / 0 failed;npm run build 通过;合并改动无新增
Windows 专属 API(runtime-info/schtasks 相关均为注释或既有平台门控代码)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants