← Back to list

I Dug Through X’s Open-Source Algorithm to Understand My “Ghost Ban.” It Got Weird Fast.

Blocks, reports, AI-scored replies, hidden visibility labels, 14-day and 30-day timers — and why “shadowban” may not be one thing at all.

Unsafe Block · 2026-08-14 19:24 · 3 claps · 12.5 min read
#twitter #social-media #algorithms #artificial-intelligence #content-moderation
Open on Medium ↗
Wiki topics: AI · AI · General 💻 · Programming 🔒 · Cybersecurity 🔓 · Open Source

I Dug Through X’s Open-Source Algorithm to Understand My “Ghost Ban.” It Got Weird Fast.

Blocks, reports, AI-scored replies, hidden visibility labels, 14-day and 30-day timers — and why “shadowban” may not be one thing at all.

A few days ago, my X account started doing something strange.

My replies still existed. I could open them directly from my profile or by URL.

But other people often couldn’t see them inside the conversation.

My recent posts also stopped appearing normally in Search.

No suspension. No warning. No “you violated rule X.” My account was still there, and standalone posts were still getting some impressions.

Basically, the classic thing people on Reddit call a ghost ban.

I could have joined the ancient ritual (very popular in Reddit):

stop posting for a week don’t like anything log out clear your cache change your IP sacrifice a goat to Elon

Instead, X had just published a large chunk of its algorithm.

So I opened the repo.

[embed]GitHub - xai-org/x-algorithm: Algorithm powering the For You feed on X Algorithm powering the For You feed on X. Contribute to xai-org/x-algorithm development by creating an account on…github.com

(On August 13, 2026, X published a major update to its open-source recommendation algorithm, adding visibility filtering, account-labeling systems, anti-abuse enforcement code, and the new Under the Hood transparency tooling. I started digging through it the next day for a very practical reason)

That turned out to be a mistake for my evening.

First problem: “ghost ban” may not actually be a thing

Not in the sense people usually mean it, anyway.

I went into the code expecting to find something vaguely equivalent to:

user.shadowbanned = true

Of course there is no such beautiful thing.

What X actually has is a zoo of post-level labels, user-level labels, visibility filtering rules, recommendation exclusions, spam classifiers, behavioral classifiers, challenges and enforcement states.

Different mechanisms can produce very similar symptoms from the outside:

“Why the fuck can nobody see me?”

And that immediately explains something that has always been bizarre about shadowban discussions.

  • One person says theirs disappeared after three days.
  • Another says seven.
  • Another swears the magic number is fourteen.
  • Someone else says a month.
  • And then there are people claiming they have been shadowbanned for six months.

Maybe they aren’t all experiencing the same thing.

Maybe “ghost ban” is a symptom, not an internal X enforcement state.

That became much more plausible once I started finding actual timers.

But I’m getting ahead of myself.

X really does turn blocks and reports into account signals

This part is not my interpretation.

There is a system in the repository called Agatha which calculates account-level features such as:

BlocksPerFav
ReportsPerFav
SpamReportsPerFav

The actual calculation is more complicated than “three people blocked you = bad.”

There are unique source/target pairs, smoothing, engagement normalization, different lookback windows and other machinery.

Some report-related calculations even use a lookback of up to 180 days

Source: https://github.com/xai-org/x-algorithm/blob/main/agatha/scalding/labels/rate_based_labels/RateBasedLabels.scala

And this is where I had my first:

Wait. What the fuck?

Because a block is useful as an anti-spam signal.

Obviously.

If a bot replies “BUY MY CRYPTO” under 200 posts and everyone blocks it, that is fantastic information.

But:

block ≠ spam

and

block ≠ abuse.

People block each other because they disagree.

Because they find someone annoying.

Because they lost an argument.

Because a person entered a hostile thread and said something the crowd hated.

A human being can understand why those situations are different.

A feature called BlocksPerFav cannot.

A block is still a block.

Then I found out that recent blocks are literally shown to the reply-scoring AI

This is the bit that made the whole thing much more interesting.

X has an AI-based system for scoring replies.

And the model does not receive only your reply.

Among the signals rendered into its context are:

user.has_missing_client_events
user.tfe_top_country
user.account_lang
user.num_replies_last_24hrs
user.follower_count
user.has_risky_user_safety_label
user.num_legit_blocks_received_last_24hrs
post.is_pasted

Source: https://github.com/xai-org/x-algorithm/blob/main/grox/core/lm/thread.py

So the model is not simply being asked:

“Is this reply spam?”

It gets something closer to:

“Here is the reply. Here is the conversation. Also, here is some information about the person who wrote it and how people have recently reacted to them.”

That distinction matters.

A lot.

The model also gets the conversation context — multiple ancestor posts, plus media context where available — and produces a reply score from 0 to 3.

**Source: **https://github.com/xai-org/x-algorithm/blob/main/grox/flows/reply_spam/classifier_reply_ranking.py

There is one enormous missing piece:

the actual production prompt is not public.

X explicitly withholds some prompts and rules to make the system harder to game.

Fair enough.

But that means we can see:

inputs → ??? → score

We cannot see exactly how much num_legit_blocks_received_last_24hrs matters compared with the actual words you wrote.

So anyone claiming:

“20 blocks automatically gets you shadowbanned”

is making shit up.

The code does not establish that.

What it does establish is that recent blocks are an explicit input.

A bad reply score can become a visibility label

In the reply write stage, a score of 0 can result in:

SafetyLabelType.RiskyHighVizReply

Source: https://github.com/xai-org/x-algorithm/blob/main/grox/flows/reply_spam/task_write.py

And there is separate logic deciding which conversations should go through this high-visibility reply machinery.

One public condition checks whether the root post or immediate parent has more than roughly 15,000 followers.

Source: https://github.com/xai-org/x-algorithm/blob/main/grox/flows/reply_spam/task_filter.py

So replying under some random account with 200 followers and entering a giant viral thread are not necessarily treated the same way.

Again: perfectly reasonable anti-spam design.

Until you combine it with reputation and crowd-feedback signals.

Then I found the famous “14 days”

If you have spent any time reading shadowban threads, you have probably seen this:

“Just wait two weeks.”

Usually followed by fifteen mutually contradictory instructions about what you must or must not do during those two weeks.

I assumed the number was folklore.

Then I found this Botmaker rule.

It applies:

RISKY_HIGH_VIZ_REPLY

with an expiration of:

CurrentTimeMs() + (OneDayInMillis * 14)

Fourteen days.

Source: https://github.com/xai-org/x-algorithm/blob/main/botmaker-rules/scarecrow/bot/GroxTweetProcessor.bot

At which point I thought:

Oh. So Reddit was right. It really is fourteen days.

And then I read the code more carefully.

Nope.

Because RISKY_HIGH_VIZ_REPLY is a label on a specific reply.

Not necessarily on your account.

This is exactly why looking at the implementation matters.

A visible symptom like “my replies are gone” can tempt you into collapsing several completely different internal states into one imaginary ban.

So yes:

there is a real 14-day visibility-related label.

No:

that does not mean every ghost-banned account is serving a 14-day sentence.

Account-level labels are a different thing entirely

The visibility-filtering code contains user-level labels including things like:

NSFW_HIGH_RECALL
NSFW_HIGH_PRECISION,
SPAM_HIGH_RECALL
COMPROMISED
READ_ONLY
IMPERSONATION_HIGH_PRECISION
NSFW_AVATAR_IMAGE
NSFW_BANNER_IMAGE
ABUSIVE_HIGH_RECALL
NSFW_NEAR_PERFECT
DO_NOT_AMPLIFY

Sources:

And this is where things start looking very familiar if you have ever experienced a so-called ghost ban.

Some of these rules can behave differently depending on who is looking at the content.

There are cases where the author is allowed to see their own content while other viewers receive a DROP.

I want to be precise here:

this does not prove that every detached reply, search ban or recommendation problem comes from these exact rules.

X has multiple surfaces, and not all production logic is public.

But “visible to me, dropped for other people” is certainly a recognizable shape.

And some account-level spam labels have 30-day TTLs

The Abuse Enforcement Service contains several branches which apply

labels: ["SpamHighRecall"]
ttl_msec: 2592000000

That is thirty days.

Source: https://github.com/xai-org/x-algorithm/blob/main/abuse-enforcement-service/service-lib/rules/enforcement_user.yaml

Suddenly:

“Mine lasted two weeks.”

and:

“Mine lasted a month.”

can both be true.

They may simply be talking about different things.

And if several labels overlap — or if a user gets classified again while another restriction is still active — an external observer could experience something that looks much longer.

Which also means I would be very careful with claims like:

“There is a six-month shadowban.”

I did find a 180-day lookback in some report-related feature calculations.

That is not the same thing as a six-month ban.

I have not found a public fixed six-month ghost-ban TTL.

Big accounts really are treated differently in some anti-spam paths

This part will surprise absolutely nobody, but it is still interesting to see it written down.

Some rules contain explicit exemptions for things like:

val :highPageRankOrAutoExpiringTempSkipListOrGrayVerified = {
      IsHighPageRankUser(userId) ||
      Contains(GetLongList(`AutoExpiringTempSkipList`), userId) ||
      IsUserGrayVerified(userId)
};

and various skip lists.

For example, the Botmaker rule that applies the 14-day RISKY_HIGH_VIZ_REPLY can skip high-PageRank or grey-verified users.

Source: https://github.com/xai-org/x-algorithm/blob/main/botmaker-rules/scarecrow/bot/GroxTweetProcessor.bot

This does not mean big accounts are immune from enforcement.

That would be an absurdly strong claim, and the code does not support it.

What it means is simpler:

some automated anti-spam paths explicitly give high-credibility accounts different treatment.

The engineering logic is obvious.

A long-established high-reputation account is statistically less likely to be a disposable spam bot.

But there is a social consequence:

false positives are not necessarily distributed equally.

The system may be more comfortable taking risks with a small account than with a highly trusted one.

X also watches how you behave, not just what you write

There is an entire behavioral anti-abuse system wonderfully called:

bdsm

which stands for:

Behavioral Inauthentic-Account Detection.

Less fun than I hoped.

It analyzes sequences of actions and features such as timing, burstiness, product surface, device/client signals, dwell time and engagement patterns.

It has classifier heads including:

FollowBot
LikeBot
ReplySpamBot
TweetSpamBot
EngagementAmplifier
LegitimateUser

and others.

Source: https://github.com/xai-org/x-algorithm/blob/a389166f6cf5da70a286b568c87695d4dcdce3a1/bdsm/README.md

So no, I would not assume that the best response to a visibility restriction is to suddenly behave like a malfunctioning automation script.

Deleting hundreds of posts in one night, disappearing for exactly fourteen days and then returning with a huge burst of activity is not obviously smarter than simply acting like a normal person.

Yes, X knows when a reply was pasted

One of the reply-model signals is literally: Reply Was Pasted

if post.is_pasted is not None:
  lines.append(f"  - Reply Was Pasted: {post.is_pasted}")

Source: https://github.com/xai-org/x-algorithm/blob/main/grox/core/lm/thread.py

When I found that one, I laughed.

Because I often write longer things elsewhere first.

So does half the internet.

But again, this is a good example of why individual features should not be treated as verdicts.

A spam bot pasting the same reply fifty times:

pasted = true

A human writing a thoughtful response in Notes, proofreading it and pasting it into X:

pasted = true

The feature does not know why.

The classifier has to infer that from the rest of the context.

Which is fine.

Assuming the classifier is right.

There is also literally something called llm_slop

I wish I were joking.

The enforcement rules contain signals named: llm_slop_post and llm_slop_user

- id: act_add_llm_slop_post_label
    when: '"llm_slop_post" in score.labels'
    then:
      kind: act_add_post_labels_v2
      labels: ["RiskyHighVizReply"]
      ttl_msec: 2592000000 

- id: act_add_llm_slop_label
    when: '"llm_slop_user" in score.labels'
    then:
      kind: act_add_labels_v2
      labels: ["SpamHighRecall"]
      ttl_msec: 2592000000

Sources:

Some downstream enforcement branches can turn those into restrictive safety labels.

But this is another area where it would be very easy to overstate what the code proves.

We do not have public code showing a magical detector that can reliably determine:

“ChatGPT wrote this.”

We see downstream handling of a classification called llm_slop.

We do not see enough of the upstream classifier to know exactly how that judgment is made.

So the defensible statement is:

X has internal classifiers/categories it calls “LLM slop.”

Not:

X can prove your text was written by AI.

Those are not the same claim.

And frankly, any classifier like this is going to have false positives, because there is no cryptographic watermark embedded in ordinary human prose saying “a human wrote me.”

The part I actually find worrying

None of this is especially scandalous individually.

Of course X uses anti-spam classifiers.

Of course it looks at behavior.

Of course blocks and reports contain useful information.

Of course viral threads deserve more aggressive abuse detection than a conversation between three friends.

The uncomfortable part appears when those things interact.

Imagine two accounts.

Account A

A spam bot enters a huge conversation and writes:

BUY CRYPTO NOW 🚀🚀🚀

People block it.

Great.

That block signal is useful.

Account B

A thread turns into a pile-on against someone.

One user comes in and says:

“This is getting pretty cruel.”

Everyone is annoyed by the interruption.

People tell them to fuck off.

Several users block them.

A human observer immediately understands that these are different situations.

But some account signals in both cases can move in the same direction.

And the next time Account B posts a reply, the model may receive:

user.num_legit_blocks_received_last_24hrs = <integer>
user.has_risky_user_safety_label: true

along with the actual text.

That creates the possibility of a feedback loop:

crowd reacts negatively → account signals get worse → future replies are evaluated with worse account context → another restrictive classification becomes more plausible → visibility gets worse

Do I know how strong this effect is in production?

No.

Nobody outside X does.

The prompt is hidden.

Some thresholds are hidden.

Some production rules are hidden.

So I am not claiming that this exact loop is what happened to my account.

But the architectural ingredients for it are clearly present.

And that is the part worth discussing.

No, this does not prove X bans unpopular opinions

This caveat is important enough to get its own section.

The code does not prove:

“Unpopular opinion → shadowban.”

It does not prove:

“I was restricted because I disagreed with people in one particular thread.”

It definitely does not prove some centralized political censorship conspiracy.

My current situation is one reason I started investigating, not proof of the explanation.

The narrower claim is much stronger because we can actually support it:

negative social feedback is part of X’s account and reply safety machinery.

Recent legitimate blocks are explicitly shown to a reply-scoring model.

Blocks and reports are also turned into account-level features elsewhere.

That means social disapproval can become machine-readable reputation information.

Why people disapproved is a much harder question.

So why does everyone report different “shadowban” durations?

My guess, after reading this code, is:

because shadowban is the wrong abstraction.

One person may have a post-level RiskyHighVizReply.

Another may have a user-level SpamHighRecall.

Someone may have several labels.

Someone may be re-classified.

Someone may be dealing with a completely different surface.

And someone else may simply have crappy reach that week.

From outside, all of them post:

“Am I shadowbanned?”

And then Reddit tries to reverse-engineer one magic cure for six different systems.

At least in the public 14-day rule I found, there is no:

if user_likes_cat_picture: reset_ban_timer()

The label simply gets an expiration timestamp.

So no, I have found no evidence that:

  • liking posts resets the timer;
  • logging out makes it expire faster;
  • changing your bio removes it;
  • total inactivity is required;
  • posting exactly twice a day appeases the algorithmic gods.

New activity can obviously trigger new classifications.

That is different from “activity resets the existing timer.”

X is now testing something called “Under the Hood”

And this may eventually make all of this much easier to understand.

X is testing a feature that exposes aggregate visibility labels affecting a user’s posts/account.

https://x.com/i/under_the_hood

Someone on Reddit recently showed an output containing:


"label": "SpamHighRecall",
"about": "Account detected by automated systems as likely to post spam.",
"effect": "The account's posts are hidden from recommendations to non-followers.",
"days": "13",
"daysInPeriod": 31,
"percentageOfDays": "41.93%"

The reported effect was that posts were hidden from recommendations to non-followers.

The interesting part:

the report showed the label on 13 days out of a 31-day period.

That is exactly why I think treating “shadowban” as one binary state is probably wrong.

A person can experience a month of weird visibility while one particular label existed for only part of that month.

Maybe something else overlapped.

Maybe it disappeared and returned.

Maybe different surfaces behaved differently.

Without internal data, users currently collapse all of that into:

“still shadowbanned.”

So what did I actually learn?

I did not find a secret censorship switch.

I found something much more boring and much more believable.

X has built a giant probabilistic machine which tries to answer:

Is this a normal human being, a spammer, an abusive account, an engagement manipulator, a compromised account, or something else?

To answer that question it uses content.

Behavior.

Account reputation.

Social feedback.

And models on top of those signals.

That architecture is understandable.

It is also exactly the kind of architecture where false positives become incredibly difficult for users to understand.

Especially when one of the inputs is:

how other people reacted to you.

Because sometimes the crowd is correctly identifying a spammer.

And sometimes the crowd is just a crowd.

That is the part I keep coming back to.

We take a messy human phenomenon like:

“A lot of people disliked this person.”

We turn pieces of it into numerical features.

Then we feed those numbers into automated safety systems.

And eventually the system makes a visibility decision nobody involved can really explain from the outside.

Maybe the shortest way I can put it is:

We may have partially automated the tyranny of the majority and called it anti-spam.

That is not proof of what happened to my account.

It is not proof of political censorship.

It is not even proof that the design is net harmful — spam moderation at this scale is genuinely difficult.

But the mechanism is interesting, it is now partly public, and I think it deserves more scrutiny than:

“Just don’t tweet for fourteen days, bro.”

TL;DR: what do I actually do if this happens to me?

Honestly, probably less than Reddit tells you to do.

  • Don’t assume every “shadowban” is the same thing. X has different post-level and account-level labels, so two people with the same symptoms may be dealing with completely different restrictions.
  • I found no evidence that the usual rituals help. Logging out, clearing cache, changing your bio, avoiding likes, disappearing for exactly seven days — none of that shows up as a way to make an existing label expire faster.
  • Use the account normally. Post your stuff, like things, read the timeline. Just avoid looking like a bot: huge bursts of replies, repetitive actions, or diving into several massive hostile threads in a row probably isn’t a great idea.
  • If you’re appealing, don’t immediately delete the evidence. Save screenshots, URLs, analytics and dates first.
  • Some labels really do have timers. I found a 14-day post-level label and several 30-day account-level spam-label paths. That doesn’t mean your restriction is 14 or 30 days, but it does mean that sometimes there may genuinely be nothing to “fix” — you’re just waiting for a TTL to expire.
  • New trouble can still create new labels. That’s different from “one like resets the timer.” I found no evidence for that kind of reset logic.
  • If you have Under the Hood, check it. It’s probably the most useful thing X currently gives users because it can show which visibility labels actually affected the account.

And that’s the boring answer I ended up with:

Document it, appeal it, don’t start behaving like a malfunctioning bot, and stop looking for a magic ritual. Sometimes you may simply have to wait.


메타데이터
post_id
56ce632559c3
slug
i-dug-through-xs-open-source-algorithm-to-understand-my-ghost-ban-it-got-weird-fast-56ce632559c3
url
https://medium.com/@unsafeblock/i-dug-through-xs-open-source-algorithm-to-understand-my-ghost-ban-it-got-weird-fast-56ce632559c3
canonical_url
https://medium.com/@unsafeblock/i-dug-through-xs-open-source-algorithm-to-understand-my-ghost-ban-it-got-weird-fast-56ce632559c3
author_url
https://medium.com/@unsafeblock
status
ok
fetched_at
2026-08-18 07:42:55