Skip to content

bug(skills): the auth-permissions.md useAuth example reads user.name behind an isAuthenticated guard that guest mode and preview mode hardcode true #9350

Closed

Description

@os-tesla

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

  1. added theissue type on Sep 13, 2026
  2. claude commented on Sep 13, 2026

    @claude
    Contributor

    Graded by the skills seat under the maintainer's 2026-09-13 ruling — pm:queue · priority:p2 · Bug (session session_01DAcomhvR9kKizeYgg89Vo8, GitHub os-project-manager, 2026-09-13T07:32Z). The card already carried domain:skills with 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, the useAuth fence (opens at :38 on origin/main a5921a0f8 per the card) ⇒ published skills/** ⇒ governed (draft + human merge; build tier from the published-catalog mandate). Class (a): the example, copied as written, throws TypeError in guest mode and preview mode — the two modes a reader without an auth backend is in — because isAuthenticated is hardcoded true there while user stays null (packages/auth/src/AuthProvider.tsx); the shipped UserMenu.tsx guards 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.tsx does (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 the os:check marker 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 (--measure diagnostic count for this file 1 → 0, positive control: objectui#9311's diagnostic if still open, else another [semantic] row). Serial: objectui skills/objectui/guides/** — PR #9352 (data-integration.md, objectui#9311) landed 06:29Z; no open objectui PR touches auth-permissions.md at 06:58Z (20 file lists read).


    Generated by Claude Code

  3. claude commented on Sep 13, 2026

    @claude
    Contributor

    Claim: PM loop round 1
    Session: session_01DAcomhvR9kKizeYgg89Vo8 (GitHub os-project-manager, skills seat), claimed at 2026-09-13T07:43Z
    Branch: claude/issue-9350-useauth-guard-both
    Worktree: objectui-issue-9350
    Domain: domain:skills (graded p2 Bug by this seat under the 2026-09-13 self-grading ruling, 5651956190; published skills/**, governed in objectui too)
    File surface (region-declared): skills/objectui/guides/auth-permissions.md, the ### useAuth hook fence (opens :36–:39 on objectui origin/main) and the prose beside it — guard both members (if (!isAuthenticated || !user) return LOGINBUTTON;, the shape packages/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 hardcode isAuthenticated true while user stays null, packages/auth/src/AuthProvider.tsx :197–:198); ⛔ no os:check marker 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 — published skills/** mandate, hand-derived for objectui because dispatch-gates.mjs answers 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 seat
    Clause-②: 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 touches guides/plugin-development.md; PR #9352 on guides/data-integration.md landed 06:29Z); last touch of this file b1777ae (2026-09-12); verify lock free; H17 index ∩ this face = ∅; disjoint from the objectstack wave-2 siblings #17905 and #17935.


    Generated by Claude Code

  4. claude commented on Sep 13, 2026

    @claude
    Contributor

    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 --measure exit 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) -> --measure exit 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 --stat empty, git status --porcelain empty, 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 --measure before the build exited 2 PRECONDITION NOT MET: not a reading. Real gate pnpm check:skill-examples exit 0: Semantic phase: 14 of 14 ts fence(s) judged, 0 failed. Lit controls: useAuth count 5 -> 5; isAuthenticated || !user 0 -> 1. No package source touched, so no package test/typecheck closure owed; pnpm lint not 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?.positions and user ?? undefined — no same defect, untouched.",
    "noted, not filed: --measure (unmarked population) prints Shadowed 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

  5. claude commented on Sep 13, 2026

    @claude
    Contributor

    ACCEPT — skills seat (session session_01DAcomhvR9kKizeYgg89Vo8, GitHub os-project-manager), contract-review tier, read on objectui PR #9374 head 67cccb09c at 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 (⛔ no os:check, as graded). Single-parent commit on objectui 69aa9c0; origin/main unchanged since.

    Read on the head, not the report: !isAuthenticated || !user 0 → 1; if (!isAuthenticated) return 1 → 0; useAuth 5 → 5 (lit control); guest mode / preview mode 0 → 1; UserMenu 3 → 4. Contract re-read by the seat: AuthProvider.tsx :197–:199 hardcodes isAuthenticated true outside the enabled-and-not-preview case while user stays null; UserMenu.tsx :43 guards both. Draft, base main, first line Fixes #9350, no other closing keyword, one footer, zero angle brackets in the body, ## 维护者速读(草稿) present. Gates per the report: pnpm check:skill-examples exit 0 (14 of 14 ts fence(s) judged, 0 failed); the --measure probe 1 → 0 for this file with the ablation leg proven on disk (blob dd4e4633 restored, git diff HEAD empty); 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 and check-new-cross-file-line-citations all exit 0; check-governed-queue-guard.mjs --test ⇒ GOVERNED (path face); check:doc-snippets NOT MEASURED (PRECONDITION NOT MET, and its header does not claim skills/objectui/**). CI on 67cccb0 at 08:01Z: 24 success, 3 skipped, 7 in progress; Lint, Type Check and Governed Surface Queue Guard success.

    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 AuthProvider span is :197–:199, not :197–:198 (content as described).

    Governed ⇒ human terminal: needs-user-decision on PR #9374, 速读终稿 posted there, reviews requested from os-zhuang and hotlong (objectui GOVERNED_APPROVERS). ⛔ Not flipped ready, queued or approved by this seat. The card stays pm:dispatched + assigned until MERGED.


    Generated by Claude Code

  6. claude commented on Sep 13, 2026

    @claude
    Contributor

    Landing path changed by maintainer ruling C (#17971), 2026-09-13T09:00Z — skills seat. PR #9374 carried os-zhuang's APPROVED review on head 67cccb09 since 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, is main's by signature — objectui#9346 — and not in the required set). Under the ruling the seat lands it: needs-user-decision cleared 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 stays pm:dispatched + assigned until then. ⛔ No approval was issued by this seat.


    Generated by Claude Code

  7. claude commented on Sep 13, 2026

    @claude
    Contributor

    LANDED — skills seat (session session_01DAcomhvR9kKizeYgg89Vo8), 2026-09-13T10:16Z. objectui PR #9374 merged through the merge queue at 2026-09-13T10:12Z as ffc4c443 (merged_by os-project-manager; auto-merge armed by this seat under ruling C — governed skills/** surface, authorized approval os-zhuang 5190151876 APPROVED on head 67cccb09 at 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 ref gh-readonly-queue/main/pr-9374-* is gone (0 refs), and objectui origin/main ffc4c44 carries the content — git log origin/main --oneline -60 has (#9374) 1 with control (#9369) 1. Card closed by the PR's Fixes #9350 line at 10:12:29Z (state_reason completed); pm:dispatched residue and the assignee os-project-manager stripped in this pass and read back. Contract review of record 5652098578 (head 67cccb09); 速读 5652172095 (the Doc Snippet Type Check red was base-inherited per #17934 shape 1 and has since cleared on main via objectui PR #9369).


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions