Repository navigation
bug(skills): the auth-permissions.md useAuth example reads user.name behind an isAuthenticated guard that guest mode and preview mode hardcode true #9350
Description
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.
Activity
claude commentedon Sep 13, 2026
Graded by the skills seat under the maintainer's 2026-09-13 ruling —
pm:queue·priority:p2·Bug(sessionsession_01DAcomhvR9kKizeYgg89Vo8, GitHubos-project-manager, 2026-09-13T07:32Z). The card already carrieddomain:skillswith no pm state (the sweep's disjunct ③); a lane seat may now grade what lands in its files — maintainer, verbatim, untranslated (live chat, recorded on objectstack#17942): 「②同意,执行席自路由,分诊也无需事后审计」.Landing:
skills/objectui/guides/auth-permissions.md, theuseAuthfence (opens at :38 onorigin/maina5921a0f8per the card) ⇒ publishedskills/**⇒ governed (draft + human merge; build tier from the published-catalog mandate). Class (a): the example, copied as written, throwsTypeErrorin guest mode and preview mode — the two modes a reader without an auth backend is in — becauseisAuthenticatedis hardcodedtruethere whileuserstaysnull(packages/auth/src/AuthProvider.tsx); the shippedUserMenu.tsxguards both. A failing probe is named (node scripts/check-skill-examples.mjs --measure→TS18047: 'user' is possibly 'null'at :49:20). p2: an agent-executed instruction that fails at runtime in two of three modes; not p1 — no shipped component is wrong, only the published example.Acceptance (the card's suggested repair, adopted): guard both members the way
UserMenu.tsxdoes (if (!isAuthenticated || !user) return LOGINBUTTON;) and move the prose with it (why a signed-in-looking context can still have no user); ⛔ do not add theos:checkmarker to this fence in this PR — the fence is a fragment by construction (three undeclared placeholders) and marking it reds the gate for reasons that are not this defect; whether to make it self-contained is a separate question, noted not filed. Re-measure the probe before and after (--measurediagnostic count for this file 1 → 0, positive control: objectui#9311's diagnostic if still open, else another[semantic]row). Serial: objectuiskills/objectui/guides/**— PR #9352 (data-integration.md, objectui#9311) landed 06:29Z; no open objectui PR touchesauth-permissions.mdat 06:58Z (20 file lists read).Generated by Claude Code
claude commentedon Sep 13, 2026
Claim: PM loop round 1
Session:
session_01DAcomhvR9kKizeYgg89Vo8(GitHubos-project-manager, skills seat), claimed at 2026-09-13T07:43ZBranch:
claude/issue-9350-useauth-guard-bothWorktree:
objectui-issue-9350Domain:
domain:skills(graded p2 Bug by this seat under the 2026-09-13 self-grading ruling, 5651956190; publishedskills/**, governed in objectui too)File surface (region-declared):
skills/objectui/guides/auth-permissions.md, the### useAuth hookfence (opens :36–:39 on objectuiorigin/main) and the prose beside it — guard both members (if (!isAuthenticated || !user) return LOGINBUTTON;, the shapepackages/auth/src/UserMenu.tsx:43 ships) and one sentence on why a signed-in-looking context can still carry no user (guest mode and preview mode hardcodeisAuthenticatedtrue whileuserstaysnull,packages/auth/src/AuthProvider.tsx:197–:198); ⛔ noos:checkmarker on this fence (a fragment by construction); ⛔ nothing else in the file. Net line budget: at most +2 lines (the guard is in place, the prose one or two lines); ⛔ no re-wrap as currency; the objectui skill token ratchet (check:skill-eval-tokens) is read from the gate.Container & model:
S,mode:subagent,model: fable(claude-fable-5-1— publishedskills/**mandate, hand-derived for objectui becausedispatch-gates.mjsanswers only the objectstack tree); seat review at the contract-review tier; governed ⇒ draft PR at the human terminal with the four-piece, ⛔ never flipped ready by the seatClause-②: no
Thread-read: 5651956190 (this seat's grading, 2026-09-13T07:32Z — the newest comment at this claim)
Serial constraints cleared: no open objectui PR touches
skills/objectui/guides/auth-permissions.md(20 file lists read at 2026-09-13T06:58Z; PR #8941 touchesguides/plugin-development.md; PR #9352 onguides/data-integration.mdlanded 06:29Z); last touch of this fileb1777ae(2026-09-12); verify lock free; H17 index ∩ this face = ∅; disjoint from the objectstack wave-2 siblings #17905 and #17935.Generated by Claude Code
claude commentedon Sep 13, 2026
os-dev-report
{
"issue": 9350,
"status": "done",
"branch": "claude/issue-9350-useauth-guard-both",
"pr": "#9374",
"premise_still_valid": true,
"summary": "Guarded both members in the useAuth example of skills/objectui/guides/auth-permissions.md (the early return now reads
if (!isAuthenticated || !user) return LOGINBUTTON;, the returned element unchanged) and added one sentence above the fence saying that guest mode and preview mode hardcode isAuthenticated true while user stays null, pointing at the shipped UserMenu. No os:check marker added (ruling 2: fragment by construction). Empty-frontmatter changeset committed. PR is a DRAFT on a governed surface (skills/) and was never flipped ready. Assignee was already os-project-manager on arrival; not written. P1/P2/P3/P4/P5 all held on base 69aa9c0 (P2: the isAuthenticated line spans AuthProvider.tsx :197-:199, not :197-:198; immaterial).","tests": "Build (under shared verify lock, slot issue-9350-objectui):
pnpm exec turbo run build $(node scripts/check-skill-examples.mjs --build-filter) --concurrency=2->Tasks: 29 successful, 29 total,os-verify-lock: VERDICT command-exit 0 · held the lock 141s · waited 0s. Probe at 67cccb0:node scripts/check-skill-examples.mjs --measureexit 0, TS18047 rows for this file 0 (whole corpus 0),[semantic]rows 269,Semantic phase: 97 of 121 ts fence(s) judged, 79 failed. Ablation leg (after commit):git checkout 69aa9c0 -- skills/objectui/guides/auth-permissions.md(on-disk: 365 lines, old guard 1, new guard 0, prose 0) ->--measureexit 0 printing[semantic] skills/objectui/guides/auth-permissions.md:49:20 TS18047: 'user' is possibly 'null'.(1 row; 270 semantic rows; same judged line) ->git checkout HEAD -- FILE-> restore proven: git hash-object == HEAD blob dd4e46330628db42c963525bb4ccaabcee0affdd,git diff HEAD --statempty,git status --porcelainempty, on-disk 367 lines / guard 1 / prose 1. Direction observed: red-to-green as predicted (row 1 -> 0, diagnostic rows 270 -> 269, failed-fence count unchanged because the fence still fails on its 3 undeclared placeholders). First--measurebefore the build exited 2 PRECONDITION NOT MET: not a reading. Real gatepnpm check:skill-examplesexit 0:Semantic phase: 14 of 14 ts fence(s) judged, 0 failed.Lit controls:useAuthcount 5 -> 5;isAuthenticated || !user0 -> 1. No package source touched, so no package test/typecheck closure owed;pnpm lintnot run (repo-wide CI run; no lintable source in diff).","mcp_calls": "0 — REST (curl) only: issue body + comments read, 8 PR file lists, open-PR list, PR create, PR read-back, report comment + read-back",
"open_questions": [],
"out_of_scope_findings": [
"noted, not filed: whether to make the useAuth fence self-contained (declare Spinner/LoginButton/Button) and then mark it os:check is a separate question per grading 5651956190; the file's other unmarked fences are the same fragment class (--measure lists them). 承接者:无.",
"noted, not filed: the second useAuth use in this file (the marked PermissionProvider setup fence) reads
user?.positionsanduser ?? undefined— no same defect, untouched.","noted, not filed:
--measure(unmarked population) printsShadowed types: 4 local re-declaration(s) of a PUBLISHED name ... 4 NOT declared; pre-existing and identical on base and HEAD, not a gate failure (the real gate prints 0)."],
"gates": [
{
"cmd": "pnpm check:skill-examples",
"exit": 0,
"verdict": "Semantic phase: 14 of 14 ts fence(s) judged, 0 failed. / Every marked skill example holds up against the built types."
},
{
"cmd": "node scripts/check-skill-examples.mjs --measure (HEAD 67cccb0)",
"exit": 0,
"verdict": "TS18047 rows 0; [semantic] rows 269; Semantic phase: 97 of 121 ts fence(s) judged, 79 failed."
},
{
"cmd": "node scripts/check-skill-examples.mjs --measure (ablation: base content of the guide on the same built tree)",
"exit": 0,
"verdict": "[semantic] skills/objectui/guides/auth-permissions.md:49:20 TS18047: 'user' is possibly 'null'. (1 row); [semantic] rows 270; Semantic phase: 97 of 121 ts fence(s) judged, 79 failed."
},
{
"cmd": "pnpm check:skills-paths",
"exit": 0,
"verdict": "check-skills-paths: OK (88/89 stated path(s) resolve across 20 guide file(s); 1 baselined)."
},
{
"cmd": "pnpm check:skill-eval-tokens (before and after, identical)",
"exit": 0,
"verdict": "Scanned 1 skill bundle(s) under skills: 11 eval file(s), 33 eval(s), 125 must_contain token(s), scored against 16 guide file(s). Red under the chosen oracle: 0 (0 beyond the baseline). Every must_contain token is taught by its own skill bundle."
},
{
"cmd": "pnpm check:doc-fences",
"exit": 0,
"verdict": "check:doc-fences — every TypeScript block in 227 document(s) is fenced ts/tsx/typescript, except 80 declared file(s) carrying 89 block(s) of objectui#5867's remaining population."
},
{
"cmd": "pnpm check:control-bytes",
"exit": 0,
"verdict": "check-control-bytes: OK (scanned 7540 tracked text file(s); skipped 85 binary)."
},
{
"cmd": "pnpm check:shell-escape-residue",
"exit": 0,
"verdict": "check-shell-escape-residue: OK (5/5 root(s) resolved ... skills: 16 file(s), 210 fence(s) ...)."
},
{
"cmd": "pnpm check:doc-types",
"exit": 0,
"verdict": "Every documented component type is registered."
},
{
"cmd": "pnpm check:upstream-port-parity",
"exit": 0,
"verdict": "check-upstream-port-parity: 11 ported file(s) match objectstack-ai/objectstack modulo their declared divergences."
},
{
"cmd": "node scripts/check-changeset-presence.mjs",
"exit": 0,
"verdict": "No source or published contract of a released package changed in this range, so no changeset is owed. (empty-frontmatter changeset declared anyway per the docs-only convention; 1 changeset(s) added)"
},
{
"cmd": "node scripts/check-changeset-claims.mjs / check-changeset-no-major.mjs / check-changeset-fixed.mjs / check-changeset-overwrite.mjs",
"exit": 0,
"verdict": "No pending changeset names a file this change touches. / No changeset declares a major bump. / All workspace packages are in the changeset fixed group. / No pre-existing changeset was modified or deleted."
},
{
"cmd": "node scripts/check-new-cross-file-line-citations.mjs",
"exit": 0,
"verdict": "VERDICT new-cross-file-line-citations: 0 new citation(s), enforcement report-only -> exit 0"
},
{
"cmd": "node scripts/check-governed-queue-guard.mjs --test skills/objectui/guides/auth-permissions.md",
"exit": 3,
"verdict": "GOVERNED — 1 of 1 path(s) are on a governed surface: skills/ x1 — the published skills catalog (path face, not a failure)"
},
{
"cmd": "pnpm check:doc-snippets",
"exit": 2,
"verdict": "NOT MEASURED — PRECONDITION NOT MET (a different 34-package closure is unbuilt); its own header states skills/objectui/** is NOT claimed by that gate, so it is not a derived gate for this diff"
}
],
"line_budget": {
"file": "skills/objectui/guides/auth-permissions.md",
"file_lines_before": 365,
"file_lines_after": 367,
"net": 2,
"ceiling": 2,
"insertions": 3,
"deletions": 1,
"rewrap": "none",
"catalog_skill_md_sum_before": 137,
"catalog_skill_md_sum_after": 137,
"catalog_files": 1,
"token_gate": "check:skill-eval-tokens before/after identical: exit 0, 125 must_contain tokens, 16 guide files, red 0"
},
"deviations": [
"None from Zone 1 rulings. The --measure 'before' reading was taken as an ablation leg after the commit (base guide content on the built tree) rather than as a pre-edit run, because the pre-edit run exited 2 (unbuilt); both directions are measured on the same built tree.",
"PR body created via REST POST /pulls (not MCP): stored body equals sent modulo the trailing newline (8429 -> 8428 bytes), 1 footer (session-URL form), 0 angle brackets. No body edit was made after creation.",
"P2 line span: the isAuthenticated expression in packages/auth/src/AuthProvider.tsx spans :197-:199 (three lines), the dispatch said :197-:198; content as described."
],
"files_changed": [
"skills/objectui/guides/auth-permissions.md (+3/-1: guard line, one prose sentence plus its blank line)",
".changeset/9350-auth-permissions-useauth-guard-both.md (new, empty frontmatter)"
],
"serial_check": "No open objectui PR touches skills/**: 8 PRs updated after the PM's 06:58Z reading (#9367, #9371, #9369, #9349, #9364, #9368, #9360, #9366) list no skills/ file (REST file lists).",
"pr_state": "draft: true, base: main, head 67cccb0, first line
Fixes #9350, no labels added (Clause-②: no; objectui has no skip-changeset mechanism), never flipped ready / enqueued / auto-merged; 维护者速读(草稿) five parts present with 席位意见 blank; session URL in prose and footer.","cleanup": "worktree /home/user/objectui-issue-9350: node_modules removed, git worktree remove (no --force); see cleanup line in the transcript"
}
Generated by Claude Code
claude commentedon Sep 13, 2026
ACCEPT — skills seat (session
session_01DAcomhvR9kKizeYgg89Vo8, GitHubos-project-manager), contract-review tier, read on objectui PR #9374 head67cccb09cat 2026-09-13T08:02Z (report 5652087511 read after the head).Scope against the claim (5652020721):
skills/objectui/guides/auth-permissions.md+3/−1 (the guard line and one prose sentence with its blank line) plus an empty-frontmatter changeset; 365 → 367 lines — net +2, exactly the budget cap; no re-wrap; the fence stays an unmarked fragment (⛔ noos:check, as graded). Single-parent commit on objectui69aa9c0;origin/mainunchanged since.Read on the head, not the report:
!isAuthenticated || !user0 → 1;if (!isAuthenticated) return1 → 0;useAuth5 → 5 (lit control);guest mode/preview mode0 → 1;UserMenu3 → 4. Contract re-read by the seat:AuthProvider.tsx:197–:199 hardcodesisAuthenticatedtrue outside the enabled-and-not-preview case whileuserstays null;UserMenu.tsx:43 guards both. Draft, basemain, first lineFixes #9350, no other closing keyword, one footer, zero angle brackets in the body,## 维护者速读(草稿)present. Gates per the report:pnpm check:skill-examplesexit 0 (14 of 14 ts fence(s) judged, 0 failed); the--measureprobe 1 → 0 for this file with the ablation leg proven on disk (blobdd4e4633restored,git diff HEADempty);check:skills-paths,check:skill-eval-tokens(identical before / after),check:doc-fences,check:control-bytes,check:shell-escape-residue,check:doc-types,check:upstream-port-parity, the four changeset checks andcheck-new-cross-file-line-citationsall exit 0;check-governed-queue-guard.mjs --test⇒ GOVERNED (path face);check:doc-snippetsNOT MEASURED (PRECONDITION NOT MET, and its header does not claimskills/objectui/**). CI on67cccb0at 08:01Z: 24 success, 3 skipped, 7 in progress;Lint,Type CheckandGoverned Surface Queue Guardsuccess.Contract review of record: PASS, comment 5652098578 on the PR (head
67cccb0).Deviations accepted: the before-reading taken as an ablation leg on the built tree rather than a pre-edit run (the pre-edit run exited 2, unbuilt) — both directions on one tree is the stronger reading; the
AuthProviderspan is :197–:199, not :197–:198 (content as described).Governed ⇒ human terminal:
needs-user-decisionon PR #9374, 速读终稿 posted there, reviews requested from os-zhuang and hotlong (objectuiGOVERNED_APPROVERS). ⛔ Not flipped ready, queued or approved by this seat. The card stayspm:dispatched+ assigned until MERGED.Generated by Claude Code
claude commentedon Sep 13, 2026
Landing path changed by maintainer ruling C (#17971), 2026-09-13T09:00Z — skills seat. PR #9374 carried os-zhuang's APPROVED review on head
67cccb09since 2026-09-13T08:02Z (not dismissed; the Governed Surface Queue Guard reads success), the contract-review record 5652098578 names that head, every check on it has completed (31 success / 3 skipped; the one red,Doc Snippet Type Check, ismain's by signature — objectui#9346 — and not in the required set). Under the ruling the seat lands it:needs-user-decisioncleared from the PR (read back) and auto-merge armed by this seat at 2026-09-13T08:51Z — the PR sits in the objectui merge queue (gh-readonly-queue/main/pr-9374-*, behind pr-9369). Landing record follows on MERGED; the card stayspm:dispatched+ assigned until then. ⛔ No approval was issued by this seat.Generated by Claude Code
claude commentedon Sep 13, 2026
LANDED — skills seat (session
session_01DAcomhvR9kKizeYgg89Vo8), 2026-09-13T10:16Z. objectui PR #9374 merged through the merge queue at 2026-09-13T10:12Z asffc4c443(merged_by os-project-manager; auto-merge armed by this seat under ruling C — governedskills/**surface, authorized approval os-zhuang 5190151876 APPROVED on head67cccb09at 08:02Z, head unchanged to landing; first enqueue 08:51Z, CI_TIMEOUT dequeue 09:53Z, re-armed and re-enqueued 09:53Z). Two readings at 2026-09-13T10:14Z: the queue refgh-readonly-queue/main/pr-9374-*is gone (0 refs), and objectuiorigin/mainffc4c44carries the content —git log origin/main --oneline -60has(#9374)1 with control(#9369)1. Card closed by the PR'sFixes #9350line at 10:12:29Z (state_reason completed);pm:dispatchedresidue and the assigneeos-project-managerstripped in this pass and read back. Contract review of record 5652098578 (head67cccb09); 速读 5652172095 (theDoc Snippet Type Checkred was base-inherited per #17934 shape 1 and has since cleared on main via objectui PR #9369).Generated by Claude Code