Found while repairing objectui#9311 (the unguarded dataSource read in data-integration.md). Same class — published guidance reading a member the contract declares as possibly absent — but a different guide and a different member, so it is filed rather than folded in: objectui#9311's PR is a governed-surface PR parked for human merge, and its diff is deliberately one file.
⛔ Not claimed, not edited. Published skills/** is a governed surface.
The defect
skills/objectui/guides/auth-permissions.md, the fence under "### useAuth hook" (fence opens at :38, read on origin/main = a5921a0f8). Written out in prose because JSX tag shapes do not survive being stored in an issue body — TAGNAME stands for each element:
const { user, isAuthenticated, isLoading, error, signOut } = useAuth();
if (isLoading) return SPINNER;
if (!isAuthenticated) return LOGINBUTTON;
// ... then, inside the returned markup:
Hello {user.name}
user.name is read with isAuthenticated as its only guard.
Why that guard does not hold — and this is the part worth reading
AuthContextValue declares user: AuthUser | null (packages/auth/src/AuthContext.ts). isAuthenticated is not derived from user in two of the three modes (packages/auth/src/AuthProvider.tsx):
const isAuthenticated = (enabled && !isPreviewMode)
? user !== null && session !== null
: true;
⇒ in guest mode (enabled: false) and in preview mode, isAuthenticated is hardcoded true while user stays null. So the early return does not imply a user, and user.name throws a TypeError in exactly those two modes — the ones a reader prototyping without an auth backend is most likely to be in.
⭐ The repository's own shipped component already knows this. packages/auth/src/UserMenu.tsx guards both:
if (!isAuthenticated || !user) {
return null;
}
The published guidance guards one. That asymmetry is the finding: this is not a compiler-pedantry item, it is the shipped code and the published example disagreeing about a contract the shipped code reads correctly.
Failing probe, named
node scripts/check-skill-examples.mjs --measure (judges every candidate fence, marked or not) on origin/main:
[semantic] skills/objectui/guides/auth-permissions.md:49:20 TS18047: 'user' is possibly 'null'.
Corpus-wide that is the only possibly-null/undefined diagnostic in skills/ + .claude/skills/ besides objectui#9311's — measured, 121 candidate ts fences, 97 judged.
⚠️ Why the fence is green today, and why marking it is not the whole repair
The fence carries no os:check opt-in marker, so check-skill-examples.mjs does not judge it: the defect is published and uncompiled, which is the class that gate's own header calls "looks like enforcement, isn't".
⛔ But adding the marker is not the fix on its own. The same fence also fails on three identifiers it never declares (the spinner, login-button and button placeholders), so it is a FRAGMENT by construction, and marking it would turn the gate red for reasons that are not this defect. The repair is the guard plus prose saying why a signed-in-looking context can still have no user; whether the fence is then made self-contained enough to mark is a second, separable question.
Suggested repair
Guard both members, the way UserMenu.tsx does, and say why in the prose around the example (AGENTS.md requires the prose to move with a changed example):
if (!isAuthenticated || !user) return LOGINBUTTON;
Refs: objectui#9311 (same class, the dataSource member) · objectui#7912 (the narrowing ruling that made objectui#9311's instance visible)
Filed by an os-dev seat while working objectui#9311, using Claude Code, session session_01UzHd6hDYatoDn17BuwKxnZ.
Found while repairing objectui#9311 (the unguarded
dataSourceread indata-integration.md). Same class — published guidance reading a member the contract declares as possibly absent — but a different guide and a different member, so it is filed rather than folded in: objectui#9311's PR is a governed-surface PR parked for human merge, and its diff is deliberately one file.⛔ Not claimed, not edited. Published
skills/**is a governed surface.The defect
skills/objectui/guides/auth-permissions.md, the fence under "### useAuth hook" (fence opens at :38, read onorigin/main=a5921a0f8). Written out in prose because JSX tag shapes do not survive being stored in an issue body — TAGNAME stands for each element:user.nameis read withisAuthenticatedas its only guard.Why that guard does not hold — and this is the part worth reading
AuthContextValuedeclaresuser: AuthUser | null(packages/auth/src/AuthContext.ts).isAuthenticatedis not derived fromuserin two of the three modes (packages/auth/src/AuthProvider.tsx):⇒ in guest mode (
enabled: false) and in preview mode,isAuthenticatedis hardcodedtruewhileuserstaysnull. So the early return does not imply a user, anduser.namethrows a TypeError in exactly those two modes — the ones a reader prototyping without an auth backend is most likely to be in.⭐ The repository's own shipped component already knows this.
packages/auth/src/UserMenu.tsxguards both:The published guidance guards one. That asymmetry is the finding: this is not a compiler-pedantry item, it is the shipped code and the published example disagreeing about a contract the shipped code reads correctly.
Failing probe, named
node scripts/check-skill-examples.mjs --measure(judges every candidate fence, marked or not) onorigin/main:Corpus-wide that is the only possibly-null/undefined diagnostic in
skills/+.claude/skills/besides objectui#9311's — measured, 121 candidate ts fences, 97 judged.The fence carries no
os:checkopt-in marker, socheck-skill-examples.mjsdoes not judge it: the defect is published and uncompiled, which is the class that gate's own header calls "looks like enforcement, isn't".⛔ But adding the marker is not the fix on its own. The same fence also fails on three identifiers it never declares (the spinner, login-button and button placeholders), so it is a FRAGMENT by construction, and marking it would turn the gate red for reasons that are not this defect. The repair is the guard plus prose saying why a signed-in-looking context can still have no user; whether the fence is then made self-contained enough to mark is a second, separable question.
Suggested repair
Guard both members, the way
UserMenu.tsxdoes, and say why in the prose around the example (AGENTS.md requires the prose to move with a changed example):Refs: objectui#9311 (same class, the
dataSourcemember) · objectui#7912 (the narrowing ruling that made objectui#9311's instance visible)Filed by an
os-devseat while working objectui#9311, using Claude Code, sessionsession_01UzHd6hDYatoDn17BuwKxnZ.