tonhowtf/omniget · error
/
Error message
{} / {} What it means
unroll first tries the fxtwitter API, then falls back to GraphQL; if BOTH fail it throws `{fx_err} / {graphql_err}`, concatenating the two underlying errors. This is a wrapper error: it means the thread could not be fetched from either backend, and both root causes are preserved in the single message.
Solutions
- Read both halves of the message: fix each root cause separately (fxtwitter side vs GraphQL side)
- For 429/rate-limit causes, back off and retry after a delay; for deleted/protected tweets, surface a user-facing 'tweet unavailable' instead of retrying
- Check connectivity/proxy settings; if GraphQL repeatedly fails due to X API changes, update the omniget-core GraphQL query definitions
Example fix
// before
unroll_graphql(&id)
.await
.map_err(|e| anyhow!("{} / {}", fx_err, e))
// after
unroll_graphql(&id).await.map_err(|e| {
let combined = format!("fxtwitter: {fx_err}; graphql: {e}");
if fx_err.to_string().contains("404") && e.to_string().contains("404") {
anyhow!("post do X nao disponivel (removido ou privado)")
} else {
anyhow!(combined)
}
}) Defensive patterns
Strategy: fallback
Validate before calling
// no pre-call validation possible; both backends fail at request time.
// Optionally probe availability first:
// let probe = client.get(format!("https://api.fxtwitter.com/status/{id}")).send().await?;
// if !probe.status().is_success() { warn "post possivelmente indisponivel" } Type guard
fn is_unavailable_tweet(err: &str) -> bool {
err.contains("404") || err.to_ascii_lowercase().contains("not found")
} Try / catch
match unroll(id).await {
Err(e) => {
let msg = e.to_string(); // "fxtwitter_err / graphql_err"
if let Some((fx, gql)) = msg.split_once(" / ") {
eprintln!("fxtwitter: {fx} | graphql: {gql}");
}
if is_unavailable_tweet(&msg) {
eprintln!("post removido, privado ou inexistente");
} else {
schedule_retry_with_backoff(id); // transient: 429/5xx/network
}
}
Ok(t) => t,
} Prevention
- Parse both halves of the '{fx} / {graphql}' message to identify the real root cause
- Retry with exponential backoff on 429/5xx; never retry 404 (deleted/protected tweet)
- Monitor fxtwitter status; if the primary backend fails often, prefer the GraphQL path directly
- Keep GraphQL query IDs and guest-token handling up to date with X API changes
- Throttle bulk unrolls to avoid tripping rate limits on both backends at once
When it happens
Trigger: Calling unroll when fxtwitter returns an error (tweet deleted/NSFW-gated, network failure, fxtwitter outage) AND the GraphQL fallback also fails (guest token rejected, rate limit 429, GraphQL schema/auth change, network error).
Common situations: Deleted or protected tweets (both backends 404); fxtwitter API downtime combined with X's GraphQL guest-token rotation failing; heavy rate limiting after bulk unrolls; Twitter changing GraphQL query IDs causing the fallback to error.
Understand the failure class
Background: "API request failed": what wrapped HTTP errors from external APIs mean and how to find the real cause — this error's family across 29 libraries.
Related errors
AI-assisted analysis of tonhowtf/omniget@8600b91f42 (2026-09-12).
Data as JSON: /api/errors/392ad1bbaf26bd2f.
Report an issue: GitHub.
Appendix: source
Thrown at src-tauri/omniget-core/src/core/tools/x/thread.rs:55
match super::fx::thread(&id).await {
Ok((focal, posts, truncated)) => {
let posts = if posts.is_empty() {
vec![focal.clone()]
} else {
posts
};
Ok(Thread {
focal,
posts,
truncated,
source: "fxtwitter".into(),
})
}
Err(fx_err) => {
tracing::info!("[x] fxtwitter falhou ({}), tentando GraphQL", fx_err);
unroll_graphql(&id)
.await
.map_err(|e| anyhow!("{} / {}", fx_err, e))
}
}
}
async fn unroll_graphql(id: &str) -> anyhow::Result<Thread> {
let client = XClient::new()?;
let mut all: Vec<XPost> = Vec::new();
let mut cursor: Option<String> = None;
for _ in 0..4 {
let mut vars = json!({
"focalTweetId": id,
"with_rux_injections": false,
"rankingMode": "Relevance",
"includePromotedContent": false,
"withCommunity": true,
"withQuickPromoteEligibilityTweetFields": true,
"withBirdwatchNotes": true,
"withVoice": true,View on GitHub (pinned to 8600b91f42)