Generated by labelwatch
Question this answers: Are any production Bluesky clients converting third-party labelers into default visibility behavior without explicit user adoption?
Short answer (as of 2026-06-08, sampled corpus): No. Every sampled client either hardcodes only
moderation.bsky.app(did:plc:ar7c4by46qjdydhdevvrndac) or inherits the appview's defaults transparently. None hardcodes any third-party labeler DID as a default subscription.Therefore: the Bundle G opt-in-consumer-adoption machinery is fire code for a building not yet built — defensible, not urgent. The wildfire-perimeter framing in F-001/F-004 was inflated. The discipline still holds; the urgency does not.
For each client, search the public source for:
| pattern | what it would mean |
|---|---|
BSKY_LABELER_DID / moderation.bsky.app / did:plc:ar7c4by46qjdydhdevvrndac |
the official default labeler is referenced/hardcoded |
other did:plc: string in a labeler-config context |
a third-party labeler is hardcoded as default |
labelersPref / app.bsky.actor.defs#labelersPref / readLabelers |
user-chosen labelers are read from app.bsky.actor.getPreferences |
appLabelers / BskyAgent.configure({appLabelers}) |
the SDK is configured with a specific labeler set |
queryLabels / com.atproto.label.queryLabels |
labels are pulled from labeler endpoints |
app.bsky.labeler.service |
the client reads labeler service records |
For each match, distinguish: hardcoded default vs user-chosen vs test-only.
Sampled via gh api search/code (where available; rate-limited) and
WebFetch of raw files (where direct paths were guessable). Seven
clients/SDKs sampled. The Bluesky showcase lists ~47 client-shaped
projects; this is a focused-sample audit, not exhaustive.
| client | source | hardcoded official labeler | hardcoded third-party labeler | reads user labelersPref |
applies hide/warn/badge | evidence path |
|---|---|---|---|---|---|---|
bluesky-social/social-app (official iOS/Android/web) |
public | YES — imports BSKY_LABELER_DID from @atproto/api; passes via BskyAgent.configure({appLabelers: [BSKY_LABELER_DID]}) |
NO | YES — readLabelers(account.did) from persistent storage; entries appended to appLabelers (excludes default to avoid double-subscribe) |
YES — full moderation pipeline | src/lib/constants.ts, src/state/session/moderation.ts, src/lib/moderation/useModerationCauseDescription.ts |
bluesky-social/atproto → @atproto/api (SDK) |
public | YES — export const BSKY_LABELER_DID = 'did:plc:ar7c4by46qjdydhdevvrndac' is the ONLY content of packages/api/src/const.ts |
NO | N/A (SDK; provides primitives, not a client) | YES — provides decideLabelModeration, getModerationUI etc. via packages/api/src/moderation/ |
packages/api/src/const.ts, packages/api/src/moderation/const/labels.ts (8-entry global LABELS map: !hide, !warn, !no-unauthenticated, porn, sexual, nudity, graphic-media, gore) |
mimonelu/klearsky (web, Vue.js, popular) |
public | YES — OFFICIAL_LABELER_DID = "did:plc:ar7c4by46qjdydhdevvrndac" in src/consts/consts.json; UI prevents un-subscribing from it; prepended if missing from labelersPref |
NO | YES — reads app.bsky.actor.defs#labelersPref from currentPreferences |
YES — subscribe/unsubscribe UI, full labeler settings popup | src/consts/consts.json, src/composables/main-state/my-labeler.ts |
mozzius/graysky (iOS/Android, popular) |
public | NO explicit hardcoding | NO | not visibly — no labelersPref or appLabelers references found in apps/expo/src/lib/agent.tsx; uses new AtpAgent({service: "https://public.api.bsky.app"}) with no labeler configuration |
inherits whatever the appview applies | apps/expo/src/lib/agent.tsx (sparse; no moderation directory under src/lib/) |
pdelfan/ouranos (Next.js web) |
public | no obvious config | NO | not visibly — no moderation directory or labeler config surfaced in src/ |
inherits SDK defaults | src/ (no moderation/labeler subdirectory found) |
mary-ext/langit → Skeetdeck (deck-style web) |
public | NO — app/api/moderation/ contains its own moderation primitives + a GLOBAL_LABELS map; labelers are passed in by callers |
NO | not surfaced in service.ts (callers responsible) | YES — own moderation primitives (preference/blur/severity enums, decideLabelModeration, getModerationUI) |
app/api/moderation/index.ts, app/api/moderation/service.ts |
ioriayane/Hagoromo (Qt/C++ desktop) |
public | delegated to ConfigurableLabels (not directly hardcoded in labelerprovider.cpp) |
NO | retrieves via m_labels.labelerDids() — caller-configured |
YES — app/qtquick/moderation/labelerlistmodel.cpp plus labeler list UI |
lib/tools/labelerprovider.cpp, lib/tools/configurablelabels.h, app/qtquick/moderation/ |
moderation.bsky.app labeler as a default. Every sampled client
either hardcodes ONLY moderation.bsky.app or hardcodes nothing
(inheriting @atproto/api's defaults or the appview's behavior).did:plc:ar7c4by46qjdydhdevvrndac. The remaining 4/7 hardcode
nothing (Graysky, Ouranos, Skeetdeck/langit, Hagoromo).labelersPref from app.bsky.actor.getPreferences.
This matches Bluesky's stackable-moderation framing: built-in default
is auto-applied; everything else requires explicit user opt-in.mod-authority.test
in IS_TEST_USER branches; production path is unaffected.MAX_LABELERS = 20; Klearsky's
LABELER_UPPER_LIMIT = 20. User-chosen labeler set is bounded.moderation.bsky.app enjoys default-client conversion in any
sampled production client. The "reference vs unknown classifier"
distinction was at most an operator-side cataloging confusion; it
was never close to becoming a real conversion-path collapse.consumer_scope=opt_in_consumer_observed machinery
is fire code for a building not yet built. No production
consumer observed to use this path; the only "real" instance is
Driftwatch's synthetic policy we wrote ourselves.global_platform
finding for that consumer specifically, but the consumer scope
would still be the named-client, not "Bluesky as a whole").bluesky-social/atproto main branch,
BSKY_LABELER_DID constant value at the time of fetch was
did:plc:ar7c4by46qjdydhdevvrndac.gh api search/code + raw-file WebFetch.