Exit code 1 means new mentions found, and exit code 1 also means error
Fetch X/Twitter tweets, replies, timelines, and articles without login or API keys — field tool for AI agents.
At a glance
- What is it?
- An X/Twitter fetcher that routes between three backends with automatic fallback, normalises everything into one schema, and can archive every fetch into a local SQLite ledger. It has no runtime dependencies, a zero-configuration path for single tweets, and a set of operational details worth reading before any of it goes near a cron job.
- Who is it for?
- Understand what you are pointing this at before you run it. It reads public X pages through FxTwitter, through a Nitter instance you host yourself, and through a browser driver, so the platform's terms and your own rate-limit exposure are the first thing to settle, not an afterthought.
- Can I use it commercially?
- Yes. MIT is a permissive licence: you can use, modify and sell software built on it, as long as you keep its copyright and licence notices.
- Is it still maintained?
- Yes. The repository last received commits 29 days ago.
- What is it written in?
- Mainly Python, according to GitHub's language statistics.
Answers come from the project's GitHub data, last synced on October 4, 2026, and from our analysis. They are not legal advice.
Editorial analysis
The cron exit codes put new mentions and errors on the same number
A mentions monitor is the feature the exit codes are built for, and the mapping is unusual enough to read twice.
Exit code 0 means success or no new mentions. Exit code 1 means an error or new mentions found. Exit code 2 is a monitor setup error.
The middle entry is the problem. Finding something new is the outcome a monitor exists to produce, and it shares an exit code with failure. A wrapper that treats any non-zero result as an error will alert on every mention the monitor catches, which is the opposite of what the feature is for. A wrapper that treats 1 as success cannot distinguish a real error from a find, because the tool does not put them on separate numbers.
The capability table does mark the monitor as incremental and cron-friendly, and the caching directory behind it is a documented environment variable, so the intent is clearly unattended operation. If you deploy it, you are the one who has to decide which half of exit code 1 you are reading.
Six error codes, and never a silent empty result
The failure surface is a closed set of six codes attached to every error: invalid_input, not_found, rate_limited, upstream_down, backend_unavailable and all_backends_failed. The last one carries an extra per-backend breakdown under error_causes, so a total failure arrives with the reason each route gave up.
That last case is the one the documentation dwells on. If no Nitter instance is reachable, you get all_backends_failed with each backend's reason underneath, and the stated intent is that you never get a silent empty result. For an agent that has to branch on what happened, that is the difference between a usable failure and a hallucinated success.
The taxonomy also tells you what the tool expects from the outside world. rate_limited is separated from upstream_down, so a throttled response is not confused with a dead service, and backend_unavailable is separate again, which covers the case where a driver or instance you configured is simply not there.
Nothing in that set covers partial reads. A post whose media never arrived is not an error code here, so if your pipeline needs to distinguish a text-only fetch from a complete one, that distinction has to come from the payload rather than the exit status.
The manifest says 3.1.0, the newest tag is v3.0.0, and the homepage is another repository
Three small metadata facts that all point the same way.
The packaging manifest declares version 3.1.0. The newest published release is v3.0.0, tagged on 2026-07-05, whose own title calls it the installable xtf package. So the tree on main is a minor version ahead of anything downloadable, and the repository also carries a VERSION file at its root, which is a third place a version number can live.
The project's homepage field points at a different repository, one named openclaw-qa, while the manifest's own Homepage URL points back at x-tweet-fetcher. Anyone who lands from the project metadata lands on someone else's repository.
The rest of the root is telling about how the project is run. There is a CHANGELOG, a MIGRATION document for the v1 layout, a SKILL file at the top level next to the README, and a workflows directory that sits alongside .github. The skill file and the JSON-first design are the clearest signals about the intended audience: an agent that will call this rather than a person at a terminal.
No runtime dependencies at all, and eight lint rules switched off to keep CI green
The dependency list is empty, with a comment saying stdlib only and that browser mode optionally uses Camofox or Playwright. Playwright is available as an extra at 1.40 or newer, and the development extra is pytest and ruff. A fetcher for a platform with no free API that needs nothing but the standard library to read a single tweet is a reasonable thing.
The ruff configuration is the more revealing part:
ignore = [
"BLE001",
"S110",
"S112",
"EXE001",
"SIM102",
"PLW1510",
"RUF059",
"PIE810",
]The comment above that list says these are pre-existing style issues in the drivers and tests, that CI was failing on 200 or more of them, and that the remaining noisy rules are ignored because they are unrelated to feature work. The set is dominated by exception-handling and subprocess checks, which in a project that wraps three remote services in a fallback chain is the category where you would most want the linter active rather than switched off.
Line length is 100 and the target is Python 3.10, which matches the manifest's floor.
The browser driver defaults to Camofox on localhost:9377, and lists need it
Routing is explicit and there are four modes rather than three. FxTwitter handles single tweets and user profiles with no dependencies at all. Nitter handles timelines, search, replies and mentions over direct HTTP, given a reachable instance. The browser driver handles everything the first two cover plus X Lists and X Articles. The default is auto, which tries Nitter first and falls back to the browser.
The driver choice is an environment variable, XTF_BROWSER, defaulting to camofox, with the port in XTF_BROWSER_PORT at 9377. Playwright is an alternative, taken either by setting the variable or by passing the driver flag, and the README shows the extra install alongside it.
Two capabilities are locked to that slowest route. X List tweets and X Article full text are marked browser-only, and the example line for a list says lists always use the browser, without an exception. Mentions monitoring can go through either Nitter or the browser.
So the zero-configuration claim holds for exactly one thing: a single tweet URL. Everything else needs either a Nitter instance you run or a browser driver you have installed, and the two richest outputs are on the most fragile path.
Self-hosted Nitter is the recommended route, and the default language is zh
The recommendation is blunt: public Nitter instances are unreliable and frequently dead, and self-hosting is strongly recommended for timelines, search, replies and mentions. The suggested route is a container:
# See https://github.com/zedeus/nitter for full setup
docker run -d -p 8788:8080 --name nitter zedeus/nitter:latest
export XTF_NITTER=http://127.0.0.1:8788Failover is a comma in a variable. XTF_NITTER takes a list of instances tried in order, so a local one and a remote backup cover each other, and the v1 variable name NITTER_URL is still honoured as a fallback for people who never migrated.
The rest of the configuration table is short: XTF_BROWSER defaults to camofox, XTF_BROWSER_PORT to 9377, XTF_CACHE_DIR to ~/.x-tweet-fetcher for the mentions monitor cache, and XTF_LANG to zh. That last default is the odd one for a project whose documentation is otherwise in English, including a section heading that is still in Chinese. Every message the tool emits will arrive in Chinese until you change it.
--ledger turns fetches into a local library with a schema borrowed from another tool
The archive mode is the most substantial feature, and it changes the tool from a reader into a database front end:
# Fetch + archive a timeline
xtf --user YuLin807 --limit 20 --ledger ~/tweets.db
# Search the archive (offline)
xtf --ledger ~/tweets.db --query "sop"
# Stats: totals, languages, media/urls, time ranges
xtf --ledger ~/tweets.db --statsEverything a fetch returns is written to a SQLite file, deduplicated on tweet_id with INSERT OR IGNORE, so re-running the same fetch is idempotent. The schema is declared compatible with the tweets table of another tool, the tweet-ledger from OpenClaw, so the same database file can be read by both.
Two details show the seams. Single-tweet results from the FxTwitter path arrive without a tweet_id, so the command line injects one from the URL. Reply results are stored with is_reply set and in_reply_to_status_id pointing at the parent. And archiving is deliberately non-blocking: a ledger failure never fails a successful fetch, it surfaces as a ledger_error field in the JSON envelope instead.
The table itself has twelve columns, from tweet_id and created_at through urls_json, media_json and raw_json to imported_at, so the original payload is kept alongside the extracted fields.
The structure listing ends mid-word, and a v1 feature has quietly disappeared
The project structure section is short and stops early. It names models.py for the Tweet, Reply, Profile and Article dataclasses, then backends/fxtwitter.py for single tweets and profiles, then backends/nitter.py described as direct HTTP across multiple instances, where the description itself ends partway through the word.
A second thing is missing rather than cut. The release history names v1.9.0 as an Obsidian export for X Articles and arXiv papers into a local knowledge base. Neither Obsidian nor arXiv appears anywhere in the capability table, the slash command list, or the configuration the documentation gives today. A feature that was the entire reason for a minor version bump is absent from the current picture without a line saying it was removed.
The rest of the shape is consistent with a small, agent-facing tool. A Python API with a Router object and typed exceptions exposes fetch_tweet, fetch_timeline, fetch_replies and search, and every backend normalises into one Tweet, Reply, Profile or Article schema so downstream prompts describe a single shape. There is also a scripts directory whose entry point works straight from a clone with the same flags, for anyone who would rather not install the package.
Editorial conclusion
Understand what you are pointing this at before you run it. It reads public X pages through FxTwitter, through a Nitter instance you host yourself, and through a browser driver, so the platform's terms and your own rate-limit exposure are the first thing to settle, not an afterthought. With that said, the tool is honest about where it is weak: timelines, search, replies and mentions all need a working Nitter instance, and X Lists and X Articles only come back through a browser driver at its default of Camofox on localhost port 9377. For an agent pipeline that wants public posts in a single JSON shape and can run a container, that is a workable arrangement. Before wiring it into cron, read the exit codes, because 1 means new mentions were found and 1 also means error, and a monitor that treats non-zero as failure will page on every hit. Also decide whether you want Chinese messages, which is the default, and download from a tag rather than trusting the manifest, which reads 3.1.0 while the newest release is v3.0.0.
Frequently asked questions
What does ythx-101/x-tweet-fetcher need to fetch an X timeline?
A reachable Nitter instance, set through XTF_NITTER, which defaults to http://127.0.0.1:8788 and takes a comma-separated list of instances with automatic failover. Public instances are described as unreliable, so the documented recommendation is to self-host Nitter in Docker. X Lists and X Articles need the browser driver instead, which defaults to Camofox on port 9377.
Does ythx-101/x-tweet-fetcher need an API key or a login?
No. Single tweets and user profiles are read through FxTwitter with the Python standard library and no dependencies at all, which is why the install line is a plain pip install with an empty runtime dependency list. The browser alternative is Playwright, available as an extra.
What exit codes does the x-tweet-fetcher mentions monitor use?
Exit code 0 is success or no new mentions, exit code 1 is an error or new mentions found, and exit code 2 is a monitor setup error. Because a find and an error share code 1, a cron wrapper has to decide which of the two it is reading.
What is the --ledger option in ythx-101/x-tweet-fetcher?
It archives every fetch into a SQLite database, deduplicated on tweet_id with INSERT OR IGNORE so reruns are idempotent, and the schema is compatible with the tweets table of the tweet-ledger from OpenClaw. The same tool can then search and report stats on that database offline. A ledger failure never fails a successful fetch; it appears as ledger_error in the JSON envelope.
Which version of ythx-101/x-tweet-fetcher should I install?
The newest published release is v3.0.0 from 2026-07-05, titled as the installable xtf package, while the packaging manifest on main declares 3.1.0. The repository also carries a VERSION file, so download from a tag rather than trusting the manifest number. The command to use is xtf, and a clone can also be run directly with python3 scripts/fetch_tweet.py using the same flags.
Official sources
Add this badge to your README
If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.
[](https://hysenlabs.com/projects/ythx-101-x-tweet-fetcher)