Skip to content

Member::ban() returns Ok(()) but bans do not accumulate on the banlist #85

Description

@derekross

Summary

Multiple successful Member::ban() calls do not accumulate on the community banlist. Each call returns Ok(()), but the folded banlist grows by fewer entries than bans issued, and fewer banlist editions are published than bans made.

Versions

  • vector-core / vector_sdk built from master (e66f5c9)
  • Community minted by armada (Concord v2, split control plane)

Observed

One run of a moderation bot against 11 throwaway spam identities. The bot's own audit log, recorded immediately after each SDK call returns:

ban   enforced=true   x4      ← 4 distinct npubs, all Ok(())
ban   enforced=false  x1      (unrelated: write-key race at startup)
kick  enforced=true   x3
warn  enforced=true   x6

The four enforced=true bans were confirmed to be 4 distinct npubs (not escalation re-bans of one identity):

npub1pzx60ar6vlaqux03t5n…
npub1ukkl5cxk8heanjwg5k0…
npub1ug90snrv57yw8tfhnpg…
npub13sx4zjwhnjef5qhm8l0…

Resulting state:

bot banlist   90 → 92 entries      (+2 for 4 successful bans)
banlist head  v6 → v8              (2 publishes for 4 bans)

So two of the four bans produced no banlist edition at all, and the two that did are the only ones that stuck.

Independent confirmation from a second member

An ordinary member (separate identity, separate process) folding the same community during the run:

bot      banlist  92 entries, folded v8, published v8
observer banlist   0 entries, folded v0

Across an earlier run the two agreed exactly (88 entries, v4 on both), so the fold path works in general — but bans issued during a run do not reach other members.

Caller side

The bot's entire involvement is one call per decision:

member.ban().await

No retry, no batching, no local banlist manipulation, and no reconcile logic. Every call in the sample above returned Ok(()).

Reproduce

  1. Community with a moderator holding BAN (verified: role permissions 2943, bit 4 = BAN set).
  2. Have several distinct identities join and post.
  3. Call Member::ban() on each in quick succession, checking each returns Ok(()).
  4. Compare the number of successful calls against communities.banlist length and banlist_at version.

Bans issued with gaps between them appear more likely to stick than bans issued in quick succession, which is consistent with a read-modify-write of the banlist entity racing itself — each publish derived from a snapshot taken before the previous one landed.

Impact

ban() reporting success while the ban neither persists nor reaches members is silent moderation failure: staff and bots believe an account is banned, and members keep seeing it. For automated moderation the loss rate scales with how fast abuse arrives, which is exactly when it matters most.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions