auth-org-onboarding
Why It Exists
The gap this skill closes isn't hypothetical: a brand-new signup with zero org
membership hits a 403/no-access wall on every org-gated page, and it has been
independently observed and fixed in more than one fleet repo with genuinely
different underlying architectures. The introducing commit
(6fb18e8, feat(skills): add auth-org-onboarding skill (--org-resolution live-db|jwt-embedded)) is explicit that this is a repeated fleet problem, not
a one-off bug report.
That commit also names exactly where each reference implementation came from:
live-db was "ported near-verbatim from jobweave's production
onboarding.ts," resolving org membership via a fresh DB lookup on every
request with no session-refresh step. jwt-embedded was "adapted from
work-loom," minting org id/role into the JWT via a Supabase Auth Access Token
Hook instead. These aren't two implementations of the same idea — they're two
different fleet repos independently arriving at different architectures
because of how each one's existing auth stack resolves membership at request
time.
The jwt-embedded half is where the skill's hard-won specificity comes from.
Per the introducing commit, that reference "went through a real
reject/fix/re-approve review cycle": a first architect pass caught a
fabricated claim in an early draft — that the skill had "fixed" work-loom's
org-name uniquify logic, when work-loom's version was never broken — plus
three real bugs introduced while porting access-token-hook.sql from
work-loom's source: dropped RLS policies, a wrong join column, and a
null-unsafe claim merge. All three were re-derived from work-loom's actual
source and fixed before a second review pass approved the reference.
A follow-up commit (783542e, found via git log --follow on this skill)
shows the skill needed one more correction after landing: an unquoted colon
inside a plain YAML scalar in SKILL.md's frontmatter description broke
the pre-existing symlink-based skills add -g CLI, fixed by quoting the
scalar (per
docs/sessions/2026-08-14-skill-distribution-rollout/SUMMARY.md).
What It Does
auth-org-onboarding scaffolds a POST /onboarding endpoint plus its
matching web-integration snippet, selected by a required --org-resolution live-db|jwt-embedded flag with no default. Both variants check for an
existing membership before writing anything (idempotent-by-membership-check-
first) and share a faithfully-ported slug/org-name uniquification helper
(suffix loop, then a short-random backstop).
Where they diverge is what happens after the org is created. live-db
resolves membership via a live DB lookup on every request, so a brand-new org
is visible on the very next page load with no extra step — the dashboard's
server layout just self-heals silently. jwt-embedded bakes org id/role into
the JWT itself via a Supabase Auth Access Token Hook, so the client's
existing token is stale until an explicit post-onboarding refresh runs. Using
the wrong web-integration pattern for a repo's actual architecture leaves the
new org invisible to the client indefinitely, with no error anywhere — which
is why the flag has no default.
How To Use It
Triggers on: "onboarding gap", "auto-create org on signup", "no org
membership after signup", "you don't have access after signup",
"org-resolution live-db vs jwt-embedded", "access token hook org_id",
"session refresh after onboarding", "advisory lock org creation race", or
closing the org-onboarding gap as a follow-up to the
auth-email-provisioning/auth-transactional-emails skills.
skills add git@github.com:catesandrew/next-starters.git --skill skills/auth-org-onboarding -g
npm install @next-starters/skill-auth-org-onboarding
/plugin marketplace add catesandrew/next-starters
/plugin install auth-org-onboarding@next-starters
Gotchas & Invariants
--org-resolutionhas no default on purpose — it's a real, observed drift risk. A silently-defaulted value would write the wrong web-integration snippet into a repo already committed to the other architecture, leaving a server-created org invisible to the client forever. Same fail-closed pattern asauth-email-provisioning's--ses-region.- Both variants must check for an existing membership before any write —
this is what makes calling the endpoint unconditionally on every dashboard
(live-db) or
/welcome(jwt-embedded) load safe and cheap. - The
pg_advisory_xact_lockrace protection in the jwt-embedded reference is the ONE deliberate deviation from its work-loom source, which relies only on DB constraints and returns a raw 500 (not a cleanexistingresponse) on a same-user race — this is explicitly commented in the code, not a silent "ours is better" claim. - The advisory lock only serializes calls from the same user. A genuine cross-user slug/name collision under real concurrent load remains a known, open limitation in both variants — don't claim either "never 500s" on a collision.
- The slug/org-name uniquification loop (50 numeric-suffix attempts, then a
short-random backstop) is already correct in both source repos and is
ported faithfully, not fixed — it's unit-tested directly via
bin/__tests__/org-uniquify.test.mjs. access-token-hook.sqlmust keep RLS SELECT policies forsupabase_auth_admin(not just GRANTs) and null-safecoalesce(...) || jsonb_build_object(...)claim merging — a barejsonb_setsilently no-ops whenapp_metadatais absent, and dropping the RLS policies makes the hook silently read zero rows, falling through to the empty-org fallback for every login.jwt-embeddedneeds an explicit post-onboarding refresh, and the two real mechanisms are NOT interchangeable:supabase.auth.refreshSession()for a direct Supabase Auth client, versus a session-management framework's own re-fetch (e.g. NextAuth'suseSession().update()) performed inside that framework's own session callback for a framework-fronted app.- Porting the live-db self-heal pattern (no refresh, silent server-layout call) onto a jwt-embedded repo is the single most likely cross-variant mistake — that dashboard's authorization check reads a stale JWT claim, not a live DB lookup.
SKILL.md's YAML frontmatterdescriptionmust stay a quoted scalar — an earlier unquoted version broke the symlink-basedskills add -gCLI on an embedded colon (fixed in783542e).
Related Skills
- auth-email-provisioning — provisions the SES/Cloudflare/Turnstile infra for the signup flow this onboarding gap sits immediately after, with no org-provisioning concerns of its own.
- auth-transactional-emails — authors the branded confirmation/recovery templates sent during that same signup flow, also with no org-provisioning concerns of its own.
Sourced from: skills/auth-org-onboarding/metadata.json, skills/auth-org-onboarding/SKILL.md, docs/sessions/2026-08-14-skill-distribution-rollout/SUMMARY.md, git log --follow -- skills/auth-org-onboarding (commits 6fb18e8, 783542e)