{"record":{"id":"be0d97286afbca00","repo":"babalae/better-genshin-impact","slug":"milliseconds-0","errorCode":null,"errorMessage":"milliseconds 不能小于 0","messagePattern":"milliseconds 不能小于 0","errorType":"exception","errorClass":"ArgumentOutOfRangeException","httpStatus":null,"severity":"error","filePath":"BetterGenshinImpact/Core/BgiVision/BvFlow.cs","lineNumber":208,"sourceCode":"    }\n\n    public BvFlow WaitUntilDisappear(BvLocator target, int? timeout = null, int? retryInterval = null)\n    {\n        ArgumentNullException.ThrowIfNull(target);\n        return AddWaitStep([target.Clone()], BvFlowCondition.AllDisappear, timeout, retryInterval);\n    }\n\n    public BvFlow WaitUntilAllDisappear(object targets, int? timeout = null, int? retryInterval = null)\n    {\n        return AddWaitStep(ParseTargets(targets, nameof(targets)), BvFlowCondition.AllDisappear,\n            timeout, retryInterval);\n    }\n\n    public BvFlow Wait(int milliseconds)\n    {\n        if (milliseconds < 0)\n        {\n            throw new ArgumentOutOfRangeException(nameof(milliseconds), \"milliseconds 不能小于 0\");\n        }\n\n        return AddStep($\"Wait({milliseconds})\", _ => _services.Delay(milliseconds));\n    }\n\n    public async Task<BvPage> Run()\n    {\n        if (Interlocked.CompareExchange(ref _isRunning, 1, 0) != 0)\n        {\n            throw new InvalidOperationException(\"同一个 BvFlow 不能并发执行\");\n        }\n\n        try\n        {\n            BvFlowStep[] steps;\n            lock (_syncRoot)\n            {\n                _hasStarted = true;","sourceCodeStart":190,"sourceCodeEnd":226,"githubUrl":"https://github.com/babalae/better-genshin-impact/blob/a7cb36712dcb409be610257d877fcea3597e9d6b/BetterGenshinImpact/Core/BgiVision/BvFlow.cs#L190-L226","documentation":"Thrown when the Kongying Tavern (空荧酒馆) cloud API endpoint 'api/marker_doc/list_page_bin_md5' returns a JSON envelope with Error=true. The HTTP call itself succeeded (EnsureSuccessStatusCode passed), but the application-level response body indicates a server-side error, carrying the server's Message field. This is a business-logic error distinct from transport failures.","triggerScenarios":"GetMarkerDocPageMd5ListAsync deserializes the response into KongyingTavernResponse<List<ListPageBinMd5Item>> and checks result.Error. Triggered when the server returns 200 OK with {\"error\":true,\"message\":\"...\"}, e.g. expired access token, invalid marker_doc scope, server maintenance, or upstream database errors.","commonSituations":"Access token expired or revoked (OAuth token stale), server-side rate limiting, API schema changes after a server update, network proxy returning an HTML error page that deserializes into a default object with Error=true, or the basic-auth credentials changed server-side.","solutions":["Check result.Message for the server's specific error text — it often names the root cause (auth, quota, maintenance).","Force re-authentication by clearing the cached OAuth token so EnsureAccessTokenAsync fetches a fresh one, then retry.","Verify the server is reachable and not in maintenance by opening https://cloud.yuanshen.site in a browser.","If the response body is not actually JSON (e.g. an HTML proxy error page), log the raw json string before deserializing to diagnose transport-layer interception."],"exampleFix":"// before\nvar result = JsonConvert.DeserializeObject<KongyingTavernResponse<List<ListPageBinMd5Item>>>(json);\nif (result is null) throw new InvalidOperationException(\"...\");\nif (result.Error) throw new InvalidOperationException($\"...: {result.Message ?? \"未知错误\"}\");\n\n// after — log raw body and retry once after re-auth\nvar result = JsonConvert.DeserializeObject<KongyingTavernResponse<List<ListPageBinMd5Item>>>(json);\nif (result is null || result.Error)\n{\n    _logger.LogWarning(\"marker_doc/list_page_bin_md5 异常响应：{RawJson}\", json);\n    await InvalidateAccessTokenAsync(ct);\n    throw new InvalidOperationException($\"marker_doc/list_page_bin_md5 返回错误: {result?.Message ?? \"未知错误\"}\");\n}","handlingStrategy":"retry","validationCode":"// Before calling the marker-doc API, verify token freshness and server reachability\nif (await IsAccessTokenExpiredAsync(ct))\n{\n    await InvalidateAccessTokenAsync(ct);\n}\nvar serverOk = await _httpClient.GetAsync(DefaultBaseUrl, ct);\n// proceed only if serverOk.IsSuccessStatusCode","typeGuard":null,"tryCatchPattern":"try\n{\n    return await GetMarkerDocPageMd5ListAsync(ct);\n}\ncatch (InvalidOperationException ex) when (ex.Message.Contains(\"list_page_bin_md5\"))\n{\n    await InvalidateAccessTokenAsync(ct);\n    return await GetMarkerDocPageMd5ListAsync(ct); // one retry after re-auth\n}","preventionTips":["Proactively refresh the OAuth token before expiry rather than waiting for a server error.","Log result.Message alongside the raw JSON body to diagnose server-side changes quickly.","Wrap marker-doc API calls in a retry policy with exponential backoff for transient server errors."],"tags":["network","api","tavern","kongying","deserialization"],"backgroundTag":null,"analyzedSha":"a7cb36712dcb409be610257d877fcea3597e9d6b","analyzedAt":"2026-08-13T16:44:57.548Z","schemaVersion":2},"datasetVersion":"2026-08-13T19:17:28.613Z"}