BigPizzaV3/CodexPlusPlus · error
倍率接口缺少观测时间
Error message
倍率接口缺少观测时间
What it means
validate_sub2api_billing_response checks that a sub2api billing/user-info API response carries all required fields before the app trusts it. Besides validating the user rate multiplier, it requires a non-empty `observed_at` field: without an observation timestamp the billing data cannot be presented as current, so the library bails instead of displaying stale or untraceable rate info.
Solutions
- Upgrade the sub2api server to a version whose billing endpoint returns `observed_at`.
- Inspect the raw JSON response (curl the billing endpoint) and confirm the field name is exactly `observed_at` and non-empty.
- Remove any proxy/mock/fixture layer that strips the field, or update test fixtures to include a timestamp.
- If the upstream truly has no timestamp, display a placeholder instead of calling this validation path.
Example fix
// before
let response: Sub2apiBillingResponse = serde_json::from_str(&body)?;
fetch_sub2api_billing_info(...)?;
// after
// ensure server returns observed_at, or guard before calling:
if body.contains("observed_at") {
fetch_sub2api_billing_info(...)?;
} else {
// handle legacy server: show multiplier without timestamp
} Defensive patterns
Strategy: validation
Validate before calling
let resp: serde_json::Value = serde_json::from_str(&body)?;
let observed_at = resp.get("observed_at").and_then(|v| v.as_str()).unwrap_or("");
if observed_at.trim().is_empty() {
// legacy server: skip billing display or fetch from another endpoint
} Type guard
fn has_observed_at(v: &serde_json::Value) -> bool {
v.get("observed_at").and_then(|x| x.as_str()).map_or(false, |s| !s.trim().is_empty())
} Try / catch
match fetch_sub2api_billing_info(...) {
Ok(info) => render(info),
Err(e) if e.to_string().contains("观测时间") => render_stale_banner(),
Err(e) => return Err(e),
} Prevention
- Pin sub2api server to a version whose billing endpoint includes observed_at.
- Add observed_at to any mock/test fixtures of the billing response.
- Integration-test the billing endpoint after every server upgrade.
When it happens
Trigger: Calling fetch_sub2api_billing_info against a sub2api endpoint whose JSON response omits `observed_at` or returns it as an empty/whitespace-only string. The multiplier check may pass (field optional) but the missing timestamp still aborts.
Common situations: Pointing the app at an older sub2api server version that predates the `observed_at` field; a reverse proxy or mock that strips/renames fields; hand-written test fixtures that only include `user_rate_multiplier`.
Understand the failure class
Background: "must not be empty", "cannot be empty" — required-field validation errors across open-source libraries — this error's family across 41 libraries.
Related errors
AI-assisted analysis of BigPizzaV3/CodexPlusPlus@b1ed92e5e4 (2026-09-19).
Data as JSON: /api/errors/8270255cb545e8ae.
Report an issue: GitHub.
Appendix: source
Thrown at crates/codex-plus-core/src/sub2api.rs:125
{
anyhow::bail!("倍率接口返回结构不是 sub2api.key_billing");
}
for value in [
response.group_rate_multiplier,
response.resolved_rate_multiplier,
response.effective_rate_multiplier,
] {
if !value.is_finite() || value < 0.0 {
anyhow::bail!("倍率接口返回了无效倍率");
}
}
if let Some(value) = response.user_rate_multiplier {
if !value.is_finite() || value < 0.0 {
anyhow::bail!("倍率接口返回了无效用户倍率");
}
}
if response.observed_at.trim().is_empty() {
anyhow::bail!("倍率接口缺少观测时间");
}
Ok(())
}
fn sub2api_error_message(body: &str) -> Option<String> {
let payload: Value = serde_json::from_str(body).ok()?;
for path in [&["error", "message"][..], &["message"][..], &["error"][..]] {
let mut current = &payload;
for key in path {
current = current.get(*key)?;
}
if let Some(message) = current
.as_str()
.map(str::trim)
.filter(|value| !value.is_empty())
{
return Some(message.to_string());
}View on GitHub (pinned to b1ed92e5e4)