## Summary
Four Mintlify doc pages threw runtime console errors and rendered
broken/empty content. Two distinct root causes:
1. **`HexclaveAgentReminders` snippet** — broke Setup, "Using Hexclave
with AI", and Stack CLI pages (and home). The component referenced a
sibling module-level export (`<pre>{hexclaveReminders}</pre>`), but
Mintlify evaluates an imported snippet component in an isolated scope
that does not include sibling exports, throwing `hexclaveReminders is
not defined`. Fixed by inlining the value into the component (matching
the self-contained pattern of every other working snippet). The snippet
is generated, so the generator `scripts/generate-setup-prompt-docs.ts`
was updated too.
2. **`connected-account.mdx`** — SDK ConnectedAccount page rendered
empty because it used `CollapsibleTypesSection`/`MethodLayout`/etc. but
was missing the `/snippets/sdk-type-components.jsx` import that all
other SDK type pages have (`Expected component CollapsibleTypesSection
to be defined`). Added the import.
## Validation
- All 4 pages render fully with clean browser consoles (verified locally
via the `mint` dev server).
- `mint validate` passes.
- Re-running the generator produces no diff (snippet stays in sync with
the generator).
Link to Devin session:
https://app.devin.ai/sessions/6ef551840f104808937c325d3f105e7a
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes console errors and empty renders in Mintlify Hexclave docs by
making the `HexclaveAgentReminders` snippet self-contained and adding
missing SDK type component imports in `connected-account.mdx`. Also
removes an unused export to avoid duplication.
- **Bug Fixes**
- Inlined content in `HexclaveAgentReminders` and updated
`scripts/generate-setup-prompt-docs.ts` to emit it, resolving
ReferenceError on Setup, Using Hexclave with AI, Stack CLI, and home
pages.
- Added missing `/snippets/sdk-type-components.jsx` import and the
`ContentSection`/`MethodReturns` named imports in
`sdk/types/connected-account.mdx` so type components render.
- **Refactors**
- Removed the unused `hexclaveReminders` export from the snippet.
<sup>Written for commit b5f2f7a28e.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/hexclave/hexclave/pull/1541?utm_source=github"
target="_blank" rel="noopener noreferrer"
data-no-image-dialog="true"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cubic.dev/buttons/review-in-cubic-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://cubic.dev/buttons/review-in-cubic-light.svg"><img
alt="Review in cubic"
src="https://cubic.dev/buttons/review-in-cubic-dark.svg"></picture></a>
<!-- End of auto-generated description by cubic. -->
---------
Co-authored-by: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Co-authored-by: aman <aman@stack-auth.com>
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
## Summary
Replaces the request-host-header-derived OAuth `redirect_uri` with a
config-driven `customCallbackUrl` field on each environment-level OAuth
provider.
Resolution of the `redirect_uri` we send to providers (and that
customers register in their provider app config):
- **Shared providers** → always the stack-auth-branded callback, so
Stack's shared OAuth apps keep working. `customCallbackUrl` is
schema-forbidden when `isShared` is true.
- **Custom + `customCallbackUrl` set** → the configured URL verbatim.
- **Custom without it (legacy)** → the stack-auth-branded callback, so
providers registered before this field are unaffected.
- **New custom providers set up in the dashboard** → the env-aware
hexclave-branded callback (prod → `api.hexclave.com`, dev/staging →
siblings, self-host/localhost → `NEXT_PUBLIC_STACK_API_URL` unchanged).
## Details
- **Schema** (`schema.ts`, `schema-fields.ts`): optional
`customCallbackUrl` after `clientSecret`, with a `.when('isShared')`
rule rejecting any value for shared providers; added to the provider
default factory.
- **Shared host helper** (`utils/cloud-hosts.tsx`, new):
`CLOUD_HOST_PAIRS` moved into stack-shared with `getCloudApiUrlSiblings`
/ `getStackAuthApiBaseUrl` / `getHexclaveApiBaseUrl`;
`request-api-url.ts` re-exports it so the JWT `iss` logic is untouched.
- **Runtime** (`oauth/index.tsx` + all 13 provider `create()`s):
`getProvider` resolves the full `redirect_uri` from config instead of
the request host; providers now take `redirectUri` instead of `apiUrl`.
The JWT `iss` path still uses the request host.
- **Dashboard** (`page-client.tsx`, `providers.tsx`,
`oauth-callback-url.ts` new): brand-new custom providers get the
hexclave callback; existing providers keep whatever they had (edits
never silently move a registered redirect URL); the displayed Redirect
URL mirrors backend resolution.
- **Docs** (`migration.mdx`): existing `api.stack-auth.com` callbacks
keep working; only recreated providers use the hexclave URL.
## Notes / scope decisions
- **Dashboard-only injection**: SDK/CLI/legacy-config-created custom
providers fall back to the stack-auth callback (they don't auto-get the
hexclave URL).
- **shared → standard** conversions keep the stack-auth fallback rather
than flipping to hexclave (the safe path that never breaks a registered
redirect).
## Test plan
- [x] `typecheck` + `lint` green across stack-shared, backend,
dashboard, e2e
- [x] cloud-hosts unit tests, schema tests, schema fuzzer pass
- [x] e2e: shared-provider `customCallbackUrl` rejected (400);
standard-provider `customCallbackUrl` accepted and round-trips
- [ ] e2e OAuth authorize/callback flow (needs running stack) — reasoned
unaffected since localhost isn't a cloud host, so the redirect base
stays localhost as before
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Adds a per-provider `customCallbackUrl` for OAuth `redirect_uri`,
removing the request-host dependency and making redirects predictable.
Shared providers always use the Stack-branded callback; new or converted
custom providers default to the Hexclave-branded callback. Existing
callbacks keep working; no changes needed unless you recreate or convert
a provider.
- **New Features**
- Added `customCallbackUrl` on provider configs (URL-validated;
forbidden when `isShared` is true).
- `getProvider` now resolves a config-driven `redirectUri`; providers
take `redirectUri` instead of `apiUrl` (pure resolver with in-source +
e2e tests to lock legacy behavior).
- Introduced `@stackframe/stack-shared` `utils/cloud-hosts.tsx` and
dashboard helpers to show the resolved Redirect URL and set the Hexclave
callback for new providers and when converting shared → standard.
- **Bug Fixes**
- OAuth callback now handles legitimate cross-host flows by recording
the authorize host and skipping the host-scoped CSRF cookie when
authorize and callback hosts differ, relying on server-side state and
PKCE.
<sup>Written for commit 32d95fcdcb.
Summary will update on new commits.</sup>
<a
href="https://cubic.dev/pr/hexclave/stack-auth/pull/1512?utm_source=github">Review
in cubic</a>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Preserve and display custom OAuth callback/redirect URLs in the
dashboard; provider creation/edit flows respect existing custom URLs.
* Added cloud-host mapping and redirect-uri helpers to resolve branded
API callback bases.
* **Bug Fixes**
* Improved cross-host OAuth callback handling and CSRF validation for
reliable cross-host flows.
* **Tests**
* Added E2E and unit tests covering callback URL behavior and host
mapping.
* **Documentation**
* Updated migration guidance for callback URL changes and recreation
scenarios.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1512?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
When the backend serves both `api.stack-auth.com` and `api.hexclave.com`
from the same deployment, signed JWT `iss` claims and OAuth
`redirect_uri` values need to match the host the customer's SDK actually
talks to — otherwise customers with hardcoded issuer checks or
registered OAuth callback URLs break when their SDK upgrades. This PR
makes both follow the request host.
Closes the "Interpretation B" plan from our earlier discussion.
## Changes
### Backend — request-host-derived `iss` and `redirect_uri`
- **New helper**
[`apps/backend/src/lib/request-api-url.ts`](apps/backend/src/lib/request-api-url.ts)
exports `getApiUrlForRequest(req)` and `getApiUrlForHost(host)`. A
consolidated `CLOUD_HOST_PAIRS` constant is the single source of truth
for the stack-auth ↔ hexclave host pairs (prod, dev, staging). Both the
allowlist here and the validator alias map in `tokens.tsx` derive from
it, so they can never drift again.
- **JWT issuer per request**
([`apps/backend/src/lib/tokens.tsx`](apps/backend/src/lib/tokens.tsx)) —
`getIssuer` now takes `apiUrl`.
`generateAccessTokenFromRefreshTokenIfValid` and `createAuthTokens`
accept an `apiUrl` parameter that flows into the `iss` claim.
`getAllowedIssuers` stays env-driven with the bidirectional alias map,
so tokens cross-validate across hosts.
- **OAuth `redirect_uri` per request** — all 12 providers +
`MockProvider` now take `apiUrl` and use it to build `redirect_uri =
apiUrl + "/api/v1/auth/oauth/callback/<provider>"`. `getProvider()`
accepts an `{ apiUrl }` option and forwards it.
- **OAuth2Server factory** — the module-level `oauthServer` singleton
became a per-request `createOAuthServer({ apiUrl })` factory so
`OAuthModel.generateAccessToken` mints tokens with the right `iss`. Used
in the callback route, the token route, and the cross-domain-authorize
helper.
- **Token-minting call sites updated** — all 10 `createAuthTokens`
invocations (password sign-up/sign-in, sessions create,
sessions/current/refresh, anonymous sign-up, apple-native callback,
MFA/OTP/passkey sign-in, OAuth model token exchange), plus the
CLI-complete `generateAccessTokenFromRefreshTokenIfValid` direct call,
now pass `getApiUrlForRequest(fullReq)`.
- **`createVerificationCodeHandler` refactored** to pass `apiUrl` as a
6th positional arg to the user's handler, so MFA/OTP/passkey sign-in
flows get the same per-request `iss` as the rest. The other 8 callers
(password-reset, contact-channels-verify, etc.) don't need changes —
they accept fewer args and TS function-arity compatibility makes that
fine.
### SDK — freeze `@stackframe/*` defaults at stack-auth.com
-
[`packages/template/src/lib/stack-app/apps/implementations/common.ts`](packages/template/src/lib/stack-app/apps/implementations/common.ts)
reverts `defaultBaseUrl` and `defaultAnalyticsBaseUrl` to
`https://api.stack-auth.com` / `https://r.stack-auth.com`. A customer
who upgrades their `@stackframe/*` package to the latest version without
explicitly migrating to `@hexclave/*` keeps hitting `api.stack-auth.com`
and never sees their JWT `iss` or OAuth `redirect_uri` change.
- The `@hexclave/*` mirror packages are unaffected because they were
published from source when `defaultBaseUrl =
"https://api.hexclave.com"`; v1.0.0 already targets the hexclave host on
npm. Extending `scripts/rewrite-packages-to-hexclave.ts` to substitute
these literals during future republishes is a separate follow-up.
### Docs — migration guide rewrite
- [`docs-mintlify/migration.mdx`](docs-mintlify/migration.mdx) rewritten
concisely. Spells out the two host-visible changes that require
pre-deploy action when migrating to `@hexclave/*`: updating manual JWT
verifier code (with the array-of-issuers pattern) and updating OAuth
callback URLs at each provider (with the GitHub-OAuth-Apps single-URL
caveat explicitly called out).
## What was deliberately left out
- **Rewriter pipeline extension** for `@hexclave/*` republishes —
separate follow-up.
- **Cross-SDK defaults** (Swift, stack-cli, init-stack still default to
`api.hexclave.com`) — out of scope per discussion.
- **Dashboard launch-checklist host-awareness** — out of scope per
discussion (callback URLs stay hexclave-branded in the dashboard UI).
## Verification
- `pnpm typecheck` — 29/29 packages pass.
- `pnpm lint` — 29/29 packages pass.
- Five parallel review agents (JWT, OAuth, helper, migration guide, SDK
defaults) + an external review of the commit caught four real issues —
all resolved in the same commit before push:
- Missing `api.staging.*` entries in `issuerHostAliases` (would have
broken cross-host token validation on staging).
- Stale comment in `apps/backend/src/stack.tsx`.
- Misleading "backward-compat" comment in `getHardcodedFallbackUrls`.
- MFA/OTP/passkey using env-var fallback for `iss` instead of the
request host.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Makes JWT issuers and OAuth redirect_uri values follow the incoming
request’s host so tokens and redirects always match `api.stack-auth.com`
or `api.hexclave.com`. Also freezes `@stackframe/*` SDK defaults to
Stack Auth and tightens the migration guide to focus on OAuth callbacks.
- **New Features**
- Added `request-api-url` helper with an allowlist of cloud hosts;
unknown hosts fall back to the deployment’s API URL.
- JWT `iss` now uses the per-request API URL; validation accepts both
brands via aliases derived from one `CLOUD_HOST_PAIRS` source.
- All OAuth providers build `redirect_uri` from the request host;
`getProvider()` now takes `{ apiUrl }`.
- Replaced the OAuth2Server singleton with per-request
`createOAuthServer({ apiUrl })` so token exchange mints with the right
issuer.
- Updated token-minting and OAuth routes to pass the API URL;
verification-code flows receive it; connected-accounts refresh paths
safely pin the deployment default.
- SDK: `@stackframe/*` defaults point to `https://api.stack-auth.com`;
`@hexclave/*` mirrors remain hexclave-branded.
- Shared: updated fallback API host lists to include both stack-auth and
hexclave hosts.
- **Migration**
- Update each provider’s OAuth callback URL to
`https://api.hexclave.com/api/v1/auth/oauth/callback/<provider>` when
migrating to `@hexclave/*`.
- If you verify JWTs manually, update the expected issuer to the
hexclave host (including anonymous/restricted variants).
<sup>Written for commit e42dfaf05b.
Summary will update on new commits. <a
href="https://cubic.dev/pr/hexclave/stack-auth/pull/1498?utm_source=github">Review
in cubic</a></sup>
<!-- End of auto-generated description by cubic. -->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Per-request API host awareness so tokens and OAuth flows reflect the
incoming request’s canonical API URL.
* **Updates**
* OAuth providers and servers now derive callback/redirect URLs from
request context rather than a static URL.
* Token issuance/validation now embeds and accepts host-specific issuer
URLs and paired host aliases for rebrand compatibility.
* **Documentation**
* Migration guide updated for branding, JWT issuer expectations, and
OAuth callback guidance.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1498?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
## Summary
**Stacked on [#1475](https://github.com/hexclave/stack-auth/pull/1475)**
(`cl/hexclave-pr1`, the invisible compatibility layer). Diff vs that
base = the actual PR 2 code.
This is **PR 2 of the Stack Auth → Hexclave rebrand: the visible flip**.
Old wire identifiers (cookies, request/response headers, Bearer prefix,
JWT issuers, MCP tool name) keep working indefinitely via PR 1's
dual-accept. This PR flips every user-visible surface — package names
taught in docs, SDK class names in code examples, dashboard setup
snippets, page titles, error messages, email content, CLI binary,
default base URLs, GitHub repo slug, contributor guidance — to the
Hexclave brand.
See [`RENAME-TO-HEXCLAVE.md`](./RENAME-TO-HEXCLAVE.md) → *"PR 2: Rebrand
to Hexclave (visible)"* for the full per-work-area spec.
## What's implemented (per the plan's PR 2 scope)
- **SDK base URLs** flipped: `defaultBaseUrl` and
`defaultAnalyticsBaseUrl` in
[common.ts](packages/template/src/lib/stack-app/apps/implementations/common.ts:127)
→ `https://api.hexclave.com` / `https://r.hexclave.com`. PR 1's
[`getHardcodedFallbackUrls`](packages/stack-shared/src/utils/urls.tsx:199)
table now keys on the Hexclave domain.
- **Domain inventory sweep** (16 subdomains from the plan): every
`api/app/docs/discord/demo/mcp/skill/feedback/test/preview/r/api2/api.staging/idp-jwk-audience/built-with.stack-auth.com`
reference in production code, docs-mintlify, examples, READMEs, and
contributor guidance flipped to `*.hexclave.com`. Carve-outs: PR 1's
intentional JWT issuer dual-accept table in
[tokens.tsx](apps/backend/src/lib/tokens.tsx), the legacy `./docs/`
folder, the `unified-docs-widget` allowlist (deliberately accepts both
during DNS transition), and `url-targets.ts` hosted-component default
(baked into existing customer deploys).
- **`@deprecated` JSDoc** on every `Stack*` public export
([packages/template/src/lib/stack-app/index.ts](packages/template/src/lib/stack-app/index.ts)
+ [packages/template/src/index.ts](packages/template/src/index.ts)) —
`StackClientApp`, `StackServerApp`, `StackAdminApp` + every
constructor/options/JSON type, `StackHandler`, `StackProvider`,
`StackTheme`, `useStackApp`, `defineStackConfig`, `StackConfig`.
Hexclave\* aliases are now canonical.
- **Runtime `console.warn`**
([packages/template/src/internal/deprecation-warning.ts](packages/template/src/internal/deprecation-warning.ts))
— once-per-process when the SDK is loaded from a `@stackframe/*`
artifact. Detection uses the existing
`STACK_COMPILE_TIME_CLIENT_PACKAGE_VERSION_SENTINEL` (rewritten at build
time to e.g. `js @stackframe/stack@2.8.92` or `js
@hexclave/next@1.0.0`); `@hexclave/*` mirror artifacts short-circuit the
warning.
- **Tier 3 data migration**: new idempotent SQL migration
[`20260523000000_rename_internal_project_to_hexclave`](apps/backend/prisma/migrations/20260523000000_rename_internal_project_to_hexclave/migration.sql)
— updates the internal Project `displayName` 'Stack Dashboard' →
'Hexclave Dashboard' and `description` only if both still hold the
pre-rebrand defaults. Operator-renamed projects untouched, missing row
no-ops, re-runs are no-ops. [`seed.ts`](apps/backend/prisma/seed.ts:87)
default flipped. `getSharedEmailConfig("Stack Auth")` → `("Hexclave")`.
- **Tier 4 brand strings** (mechanical sweep, ~340 files):
- Page + OpenAPI titles (Hexclave API / Dashboard / REST API / Webhooks
API / Documentation). OpenAPI `info.description` documents
`X-Hexclave-*` headers as canonical with compat note on `X-Stack-*`.
- `HexclaveAssertionError` message text
([errors.tsx:71](packages/stack-shared/src/utils/errors.tsx:71)) — "an
error in Stack." → "an error in Hexclave."
- Known-error message templates
([known-errors.tsx](packages/stack-shared/src/known-errors.tsx)) flipped
to lead with `x-hexclave-*` + the new `docs.hexclave.com` URL; legacy
`x-stack-*` mentioned as compat aliases. **25 e2e test files updated in
lockstep**.
- Email content: failed-emails-digest body, sendTestEmail recipient (now
`sent-with-hexclave.com`), test-email-recipient default.
- `CHANGELOG.md` title → "Hexclave Changelog".
- `AGENTS.md` env var convention: new vars prefix `HEXCLAVE_` /
`NEXT_PUBLIC_HEXCLAVE_` for Category A/B; legacy `STACK_*` explicitly
noted as accepted via PR 1's dual-read.
- **CLI / init wizard**:
- Every dashboard setup snippet, init-stack template, and docs-mintlify
page teaches `npx @hexclave/cli@latest init` (was
`@stackframe/stack-cli`).
[setup-page.tsx](apps/dashboard/src/app/(main)/(protected)/projects/[projectId]/(overview)/setup-page.tsx)
+
[link-existing-onboarding](apps/dashboard/src/app/(main)/(protected)/(outside-dashboard)/new-project/page-client-parts/link-existing-onboarding.tsx).
- [init-stack](packages/init-stack/src/index.ts:634)
`STACK_*_INSTALL_PACKAGE_NAME_OVERRIDE` defaults flipped to
`@hexclave/*`.
- Generated `stack/client.ts` / `stack/server.ts` import from
`@hexclave/next` and reference `HexclaveClientApp` /
`HexclaveServerApp`.
- Internal `StackAuthKeys` dashboard component renamed to
`HexclaveKeys`.
- **docs-mintlify rewrite** (legacy `./docs/` intentionally untouched
per scoping decision):
- **78 MDX files swept**.
`@stackframe/{react,stack,js,tanstack-start,...}` →
`@hexclave/{react,stack,js,...}` in install snippets and code blocks;
`Stack*` SDK class names → `Hexclave*` in all code examples; 'Stack
Auth' brand phrase → 'Hexclave'.
- `openapi/{server,admin,client,webhooks}.json` titles → 'Hexclave REST
API' / 'Hexclave Webhooks API'.
- **Generators flipped before regeneration**:
-
[`packages/stack-shared/src/helpers/init-prompt.ts`](packages/stack-shared/src/helpers/init-prompt.ts),
[`/ai/prompts.ts`](packages/stack-shared/src/ai/prompts.ts),
[`apps/backend/src/lib/ai/prompts.ts`](apps/backend/src/lib/ai/prompts.ts),
[`apps/backend/src/lib/ai/tools/create-email-{template,draft}.ts`](apps/backend/src/lib/ai/tools/create-email-template.ts),
[`apps/skills/src/app/route.ts`](apps/skills/src/app/route.ts) (taught
MCP tool → `ask_hexclave` with compat note; CLI binary teach →
`hexclave`),
[`docs-mintlify/snippets/home-prompt-island.jsx`](docs-mintlify/snippets/home-prompt-island.jsx),
[`packages/template/README.md`](packages/template/README.md) +
integrations/convex/component/README.md.
- `generate-sdks` propagated changes to `packages/{react,stack,js}`.
- **OpenAPI dual-documentation**:
[`apps/backend/src/app/api/latest/route.ts`](apps/backend/src/app/api/latest/route.ts)
now lists `X-Hexclave-*` headers as primary documented schemas with
`X-Stack-*` duplicates marked `.optional()` (both accepted at runtime by
PR 1's normalize-at-proxy shim).
- **`@stackframe/emails` virtual module**: dual-aliased to
`@hexclave/emails` at the bundler boundary
([email-rendering.tsx:89](apps/backend/src/lib/email-rendering.tsx:89)).
Stored email templates continue to import from either name; new
AI-generated templates and the system prompt teach `@hexclave/emails`.
- **Tier 2 mirror-publish wiring** (new this PR, lays the groundwork for
`@hexclave/*` first publish):
-
[`scripts/rewrite-packages-to-hexclave.ts`](scripts/rewrite-packages-to-hexclave.ts)
— rewrites 9 publishable `@stackframe/*` → `@hexclave/*` `package.json`
files (reads `HEXCLAVE_VERSION` env or `--version=` flag), pins
cross-deps to the shared `@hexclave` version, registers `hexclave` bin
alongside `stack` for `@hexclave/cli`.
-
[`.github/workflows/npm-publish.yaml`](.github/workflows/npm-publish.yaml)
appended with rewrite-then-republish step. `pnpm publish` skips
already-on-npm versions so reruns are safe.
- **Sender email domain**: `noreply@stackframe.co` →
`noreply@sent-with-hexclave.com` (the dedicated transactional-sender
domain split per the plan, to isolate bulk deliverability from
`hexclave.com` reputation); `security@` / `team@stack-auth.com` inbound
mailboxes → `@hexclave.com`.
- **Self-host docs**: docker network / container names in the bash
examples flipped from `stack-auth` to `hexclave` (`hexclave-postgres`,
`hexclave-clickhouse`, `hexclave.env`). The docker image tag
`stackauth/server:latest` stays per the plan's locked decision.
- **GitHub repo slug**: `hexclave/stack-auth` → `hexclave/hexclave` in
every `package.json` `repository` field, README link, CHANGELOG
raw-asset URL.
## Carve-outs (deliberately untouched)
-
**[`apps/backend/src/lib/tokens.tsx`](apps/backend/src/lib/tokens.tsx)**
JWT issuer dual-accept table — PR 1 intentional infrastructure, kept
indefinitely.
- **Legacy `./docs/` folder** — per scoping decision (only
`docs-mintlify/` rewritten).
- **`unified-docs-widget` hostname allowlist** — accepts both
`.hexclave.com` (canonical) and `.stack-auth.com` (transition window)
for DNS rollout.
- **`url-targets.ts`** hosted-domain default
`.built-with-stack-auth.com` — wire identifier baked into existing
customer deploys; indefinite read-fallback.
- **Binary visual assets** (logos, favicons, OG images, README
screenshots) — out of scope for this PR. Need design work; tracked
separately.
## Verification
- **`pnpm typecheck`** on
`packages/{template,stack-shared,react,stack,js}` + `apps/dashboard`:
**all green**. The remaining backend / e-commerce-demo typecheck errors
are pre-existing (Prisma codegen output +
`./generated/api-versions.json` not present in fresh worktrees without
`pnpm run codegen-prisma` + a live DB) and unrelated to this diff.
- **`pnpm lint`** on the same 6 packages: all green.
- **Final grep** for residual `Stack Auth` / `stack-auth.com` /
`@stackframe/stack-cli@latest` references: zero outside the intentional
carve-outs above.
- **25 e2e test files updated in lockstep** with the known-error message
changes (asserted strings flipped to match the new x-hexclave-* +
compat-note messages).
## Deploy blockers (ops sequencing before this rebrand goes live)
This PR is code-complete, but the rebrand's visible surfaces (SDK
default URLs, dashboard links, npm READMEs, REST error messages, runtime
deprecation warning) all point at `*.hexclave.com` / `@hexclave/*`
resources that don't exist yet. None of these are fixable from a PR —
they're ops/registrar/npm work that has to be sequenced before merging
this to a release tag.
Suggested ordering, hardest blockers first:
### Tier 1 — required before customer-facing deploy (everything below
this line *will visibly break customers on day 1* if skipped)
1. **DNS + TLS for `api.hexclave.com` + `api1./api2.hexclave.com`** →
must point at the same backend that serves `api.stack-auth.com` (or a
backend that mirrors PR 1's dual-accept). The SDK's new `defaultBaseUrl`
is `https://api.hexclave.com`; every customer that relied on the old
default and upgrades to a post-PR2 SDK build sends API requests here.
Until this resolves, every default-config customer's API call NXDOMAINs.
2. **DNS for `app.hexclave.com`** → the dashboard. Referenced in the
SDK's default-error messages ("Please create a project on the Hexclave
dashboard at https://app.hexclave.com"), the init-stack flow's
`wizard-congrats` redirect, and the OAuth dashboard handoff.
3. **DNS for `docs.hexclave.com`** + Mintlify deploy → the SDK runtime
deprecation warning (`https://docs.hexclave.com/migration`), every
README, every "Learn more" link in the dashboard, and every REST API
error body (`/api/overview#authentication`) points here. The MDX is in
this PR; the docs build target needs DNS.
4. **DNS for `mcp.hexclave.com`** → the MCP server endpoint that every
taught agent integration (`claude mcp add ...`, `cursor`, `codex`,
`vscode`) registers. Until this resolves, every `npx
@hexclave/cli@latest init` MCP-registration step fails.
5. **Reserve the `@hexclave` npm scope + set repo variable
`HEXCLAVE_VERSION`** → the mirror-publish step in
`.github/workflows/npm-publish.yaml` is gated on this variable. Without
it, the entire taught onboarding command `npx @hexclave/cli@latest init`
404s from the npm registry, *and* every README that says "install
`@hexclave/next`" leads to install failure. Pick the initial version
intentionally (`1.0.0` or aligned to `@stackframe/stack`); don't accept
a silent default.
### Tier 2 — required before announcing the rebrand publicly (lookalike
or low-traffic surfaces, but visibly broken)
6. **DNS for `r.hexclave.com`** → the analytics beacon
`defaultAnalyticsBaseUrl`. Silent failure if missing (analytics drops),
but should land alongside Tier 1.
7. **Register `sent-with-hexclave.com` + full email auth (SPF / DKIM /
DMARC)** → the new default sender domain for shared-sender transactional
emails. Without it the dashboard "send test email" path emits bounces,
and shared-sender flows (`getSharedEmailConfig("Hexclave")`) deliver to
spam at best.
8. **MX + SPF / DMARC for `hexclave.com`** → `team@hexclave.com` and
`security@hexclave.com` mailboxes. The security disclosure mailbox is
referenced in [`.github/SECURITY.md`](.github/SECURITY.md);
`team@hexclave.com` is the actual recipient of internal feedback emails
sent at runtime by
[`apps/backend/src/lib/internal-feedback-emails.tsx`](apps/backend/src/lib/internal-feedback-emails.tsx).
Today, every runtime feedback email bounces.
9. **DNS for `skill.hexclave.com`** → the canonical AI-agent skill fetch
URL (the agent bootstrap pivot). Without it, the entire "agent downloads
`SKILL.md` from a known URL" flow taught in
[`packages/stack-shared/src/helpers/init-prompt.ts`](packages/stack-shared/src/helpers/init-prompt.ts)
fails.
10. **Create `github.com/hexclave/hexclave` as a public repo** (even as
a redirect to `hexclave/stack-auth`) **OR** rewrite every `package.json`
`"repository"` field + dashboard footer "view on GitHub" link to point
at `hexclave/stack-auth` (which already exists). Currently every npm
package page's "Repository" link is dead, and the dashboard's GitHub
button + dev-tool repo link are dead.
### Tier 3 — broken but low-visibility / low-traffic
11. **DNS for `discord.hexclave.com`** → Discord invite redirect, used
in every README's chip and the dashboard footer.
12. **DNS for `demo.hexclave.com`** → "✨ Demo" badge in every npm
package README. Broken-image badge on the package page.
13. **DNS + TLS for `built-with-hexclave.com`** → optional
hosted-handler domain (the default reverted to
`.built-with-stack-auth.com` in this PR's carve-outs, so this only
matters for projects that manually flip).
## Other follow-ups (not deploy-blocking)
- **E2E snapshot regen across the full suite** for the dual-emitted
`x-hexclave-*` response headers (PR 1 follow-up; `vitest -u` in CI
absorbs).
- **Binary visual assets** — logos, favicons, OG images, README
screenshots; need design pass.
- **Backend OpenAPI fumadocs regen** in CI flow — the JSON files in
`docs-mintlify/openapi/` are committed but regen runs in CI. Verify the
workflow that does this still works against the post-PR2 source.
- **Backend typecheck infra debt** — needs `codegen-prisma` +
`codegen-route-info` to clear; pre-existing, unaffected by this PR.
## Test plan
- [ ] CI runs full e2e suite (with `vitest -u` to absorb residual
snapshot deltas, then committed back).
- [ ] Spot-check: new `@hexclave/cli init` (once published) generates
`hexclave.config.ts` and works against a fresh project.
- [ ] Spot-check: existing customer with `@stackframe/stack` import sees
the once-per-process `console.warn` recommending `@hexclave/next` on SDK
init.
- [ ] Manual: dashboard setup page renders the `npx @hexclave/cli@latest
init` snippet and the `x-hexclave-publishable-client-key` API header in
the curl example.
- [ ] Manual: a fresh `pnpm run prisma migrate` against a clean DB sets
the internal project displayName to 'Hexclave Dashboard'.
---------
Co-authored-by: Konstantin Wohlwend <n2d4xc@gmail.com>
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
## Summary
**Stacked on #1468** (`docs/hexclave-rename-plan` — the plan doc). Diff
vs that base = the actual PR 1 code.
This is **PR 1 of the Hexclave rebrand: the invisible compatibility
layer**. Everything is additive. Old SDKs, old wire identifiers, and old
env var names keep working unchanged. The backend dual-accepts and
dual-emits; new SDK code emits `x-hexclave-*` headers and the
`hexclave_` Bearer prefix; cookies dual-write; env vars dual-read across
every category. **No user-visible rebranding lands here** — that's PR 2.
See [`RENAME-TO-HEXCLAVE.md`](./RENAME-TO-HEXCLAVE.md) → *"PR 1
implementation guide"* for the full per-work-area spec, file pointers,
and chosen approach.
## What's implemented (all 14 PR-1 work-areas)
- **SDK export aliases** — `Hexclave*` aliases for the user-facing
`Stack*` exports added in `packages/template`; codegen propagates them
to `@stackframe/{js,stack,react,tanstack-start}`. React-only aliases
correctly excluded from `@stackframe/js`. (`e60550a2`)
- **JWT issuer dual-accept** — `decodeAccessToken` accepts both
`api.stack-auth.com` and `api.hexclave.com` issuers. Signing unchanged.
(`fc781def`)
- **Request-header dual-accept** — backend + dashboard proxies normalize
`x-hexclave-*` → `x-stack-*` at the existing empty proxy hook (so
`smart-request.tsx` and every route schema keep working unchanged); CORS
allowlists extended via a derive-once helper. (`2a056eac`)
- **MCP `ask_hexclave`** — registered alongside `ask_stack_auth` via a
shared helper; `ask_stack_auth` behavior byte-identical. (`30ffd604`)
- **Dev-tool** — DOM ids + header emit switched.
`window.HexclaveDevTool` exposed alongside `window.StackDevTool`.
(`32131ea7`)
- **The big consolidated commit** (`7fed864a`):
- **Env vars** — central `getEnvVariable` prefix-transform (HEXCLAVE
first, STACK fallback); dashboard + template client env files dual-read;
`turbo.json` globalEnv; `NEXT_PUBLIC_STACK_PORT_PREFIX` renamed outright
across ~82 files including docker.
- **Cookies** — dual-write/dual-read auth (`stack-access`/`-refresh-*`
and custom-domain variants), OAuth-state
(`stack-oauth-{inner,outer}-*`), and low-risk cookies (`stack-is-https`,
`stack-last-seen-changelog-version`). Bypass sites patched (backend
OAuth callback, dashboard remote-dev auth route, impersonation snippets,
snapshot serializer).
- **Bearer prefix** — SDK token parser accepts both `stackauth_` and
`hexclave_`; emits `hexclave_`. Discovery correction: this is purely
SDK-internal — the backend never parses it.
- **Response headers** — backend dual-emits
`x-hexclave-{request-id,actual-status,known-error}`; SDKs dual-read (new
first, stack fallback).
- **SDK request-header emit switch** —
`client/server/admin-interface.ts` + dashboard `api-headers.ts` +
`internal-project-headers.ts` + `feedback-form.tsx` switched to
`x-hexclave-*`. Plus `stack_response_mode` query param.
- **Storage keys** — dev-tool / cli-auth / oauth-button / docs keys
renamed (straight); `stack:session-replay:v1` dual-read so in-progress
recordings survive SDK upgrades; `stack_mfa_attempt_code` dual-read.
- **Query params** — cross-domain params dual-emit/dual-accept via
shared helpers; backend `oauth/authorize` accepts
`hexclave_response_mode` and `stack_response_mode`; `stack-init-id`
renamed.
- **`Symbol.for`** — app-internals symbol gets a parallel
`Symbol.for("Hexclave--app-internals")` getter on each attach site (no
read-site churn — old symbol still attached). 3 file-private symbols
renamed outright.
- **Config discovery** — prefer `hexclave.config.ts`, fall back to
`stack.config.ts` at every discovery site (CLI / dashboard / backend /
local-emulator); `init` writes the new filename; CLI credentials path
migrates.
- **Internal renames** — `StackAssertionError`,
`StackClient/Server/AdminInterface` renamed outright (no alias, per the
"internal-only → rename" rule). ~264 files touched.
- **Review-pass fixes** (`21217fbe`) — three real bugs found by parallel
review agents and fixed:
- `snapshot-serializer.ts` was interpolating the whole
`keyedCookieNamePrefixes` array (`${arr}`) — adding a second prefix
would have corrupted **every** OAuth-cookie snapshot, not just new ones.
- **Docker port-prefix producer/consumer mismatch** —
`entrypoint.sh`/`run-emulator.sh`/cloud-init `user-data` were still
producing `NEXT_PUBLIC_STACK_PORT_PREFIX` while the dashboard sentinel +
consumers had been renamed; silent self-host regression (custom port
prefix would be ignored).
- **Missing `hexclave-oauth-inner-*` dual-write** in the OAuth authorize
route — callback's fallback masked it but the dual-write was specified
by the plan.
- Plus: `mcp.test.ts` tool-list assertions updated to include
`ask_hexclave`; two dashboard header-emit sites switched to
`x-hexclave-*` for consistency.
- **E2E snapshot serializer follow-up** (`4b16cc5d`) —
`x-hexclave-request-id` added to the hidden-headers list (mirroring
`x-stack-request-id` treatment), and 2 sample inline snapshots
regenerated in `projects.test.ts` to include the new dual-emitted
headers.
## Verification
- **`pnpm typecheck`** — clean (the fresh-worktree `@/.source` / Prisma
codegen gap in `stack-docs` is pre-existing and unrelated).
- **`pnpm lint`** — 29/29 packages green.
- **`pnpm exec turbo run build --filter=./packages/*`** — 13/13 packages
build (including `@stackframe/stack-cli` once the dashboard standalone
is present).
- **Live E2E** against a running backend on `cl/hexclave-pr1`:
- `pnpm test run
apps/e2e/tests/backend/endpoints/api/v1/internal/mcp.test.ts` — **6/6
pass** (verifies the new `ask_hexclave` tool — the hand-written inline
snapshot matched actual MCP server output).
- `pnpm test run
apps/e2e/tests/backend/endpoints/api/v1/internal/projects.test.ts` —
**11/11 pass** (verifies wire dual-accept + dual-emit end-to-end; the
snapshot serializer fix was found and applied during this check).
A four-agent parallel **review pass** also audited the full diff for
logic/runtime bugs across the work-areas (wire headers + JWT, cookies +
bearer + symbols, env vars, query params + config + MCP + aliases). All
in-slice review verdicts were ✓ except the three bugs listed above,
which are now fixed.
## Known follow-ups (out of scope for this PR)
- **E2E snapshots across the rest of the suite** — backend now
dual-emits `x-hexclave-{known-error,actual-status}` alongside
`x-stack-*`, which legitimately appears in inline snapshots throughout
`apps/e2e`. Two were regenerated here as a sample; the rest should regen
with `vitest -u` in CI.
- **Docker shell env vars beyond `PORT_PREFIX`** — `entrypoint.sh` still
reads `STACK_*` env vars directly (the JS-side `getEnvVariable`
transform doesn't help the shell). JS consumers dual-read so it works in
practice; full shell-level dual-read is a deeper self-host follow-up.
- **`@stackframe/stack-cli` build ordering** — pre-existing; needs
`build:rde-standalone` first. Not affected by this PR.
## Test plan
- [ ] CI runs full e2e suite (with `vitest -u` to absorb dual-emit
snapshot deltas, then committed back)
- [ ] Spot-check: an old SDK build (emitting only `x-stack-*`) still
authenticates against the new backend
- [ ] Spot-check: a new SDK (emitting `x-hexclave-*` / `Bearer
hexclave_*`) still authenticates against an old backend during deploy
ordering
- [ ] Manual: `npx @stackframe/stack-cli@latest init` (new onboarding
entrypoint) generates `hexclave.config.ts`
- [ ] Manual: existing `stack.config.ts`-only project still resolves (no
migration required)
---------
Co-authored-by: bilal <bilal@stack-auth.com>
DB migration compat / Back-compat — Current branch migrations with ${{ needs.check-migrations-changed.outputs.base_branch }} branch code (push) Has been cancelled
DB migration compat / Forward-compat — Current branch code with ${{ needs.check-migrations-changed.outputs.base_branch }} branch migrations (push) Has been cancelled
### Summary of Changes
You can now edit items on a product view.
The "Make free" button is less obtuse, and it clearly tells you what
it's going to do.
Additionally, we found out while working on this PR that you cannot
create a `paymentIntent` on stripe that is < 0.5$. So, you can't create
an OTP for a "free" product. We add safeguards to protect against that.
Also, 0 dollar subscriptions don't create a subscription invoice.
Additionally, the old code relied on being able to fetch the stripe
client secret, which would be null for a 0 dollar subscription so we
create a carve out.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Better free-product checkout handling: $0 subscriptions return an
empty success response without a payment client secret; non-free
subscriptions include client secret when needed.
* UI: “Make free” flow, “Free · {amount}” with price ID, per-price
checkout error indicators/tooltips, and an alert for products with
invalid prices.
* Client- and server-side Stripe one-time minimum checks.
* **Bug Fixes**
* Included-item dialog now resets form state when opened to avoid stale
values.
* **Documentation**
* OpenAPI: clarified client_secret may be omitted when no customer
confirmation is required.
* **Tests**
* Added end-to-end tests covering $0 purchase-session flows.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1455?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
Two related improvements to Stack Auth's AI setup story, both driven by
`packages/stack-shared/src/ai/prompts.ts`:
### 1. Clearer env-var guidance in the cloud-project flow (existing
commit)
The previous wording suggested `STACK_PROJECT_ID` should be prefixed via
a generic _"if available, prefix with your framework's convention"_
comment, and the backend section additionally listed
`NEXT_PUBLIC_STACK_PUBLISHABLE_CLIENT_KEY` — which the SDK does not
actually read in the cloud-project setup. Agents would dutifully
fabricate that third variable.
This is now spelled out:
- The exact prefix per framework (Next.js →
`NEXT_PUBLIC_STACK_PROJECT_ID`, Vite → `VITE_STACK_PROJECT_ID`, etc.) is
given inline.
- A note clarifies that on the client, **only** the project ID is read —
there is no separate publishable / client key.
- A note clarifies that the backend setup reads exactly two variables
(`STACK_PROJECT_ID` + `STACK_SECRET_SERVER_KEY`); a third slot in
`.env.local` is wrong.
### 2. First-class TanStack Start support (new commit)
Until now `mainType: "tanstack-start"` was silently routed through
`@stackframe/react` and inherited the React-only setup steps. Agents had
to guess at the TanStack-specific bits (where to mount `StackProvider`,
what to do with `routeTree.gen.ts`, how `useUser()` behaves under SSR,
where the handler route lives).
`prompts.ts` now:
- Recognizes TanStack Start as its own `mainType` and routes the install
to `@stackframe/tanstack-start`.
- Lists TanStack Start alongside Next.js / React in the
supported-frameworks list and the package table.
- Adds three TanStack-specific steps that don't apply to vanilla React:
1. Mount `StackProvider` / `StackTheme` inside the root route's
`component` (the inner React tree), keeping `shellComponent` as the
document shell.
2. Wrap `<Outlet />` in `Suspense` inside `RootComponent`.
3. Register the Stack handler splat at `src/routes/handler/\$.tsx` with
`ssr: false`.
- Surfaces the two notes that aren't obvious from the React docs:
`routeTree.gen.ts` is generated and shouldn't be hand-edited, and
`useUser()` resolves the SSR user from TanStack Start's request cookies
for free as long as `tokenStore: \"cookie\"` is set.
The auto-generated outputs
(`docs-mintlify/guides/getting-started/setup.mdx`,
`docs-mintlify/snippets/home-prompt-island.jsx`) are regenerated from
the prompt.
### 3. tanstack-start-demo SSR-vs-client examples
Two paired routes (`/ssr` and `/client`) render the same `AuthDemoCard`
so the SSR-vs-\`ssr: false\` tradeoff is observable side-by-side. The
new \`AuthDemoCard\` shows the resolved Stack Auth user (or sign-in/up
buttons) plus the snippet that produced it. The
\`ClientMountedUserButton\` workaround in the header is dropped now that
SSR cookie reading just works, and the empty \`Suspense
fallback={null}\` in \`__root.tsx\` is replaced with a
\`RouteLoadingState\` skeleton.
## Test plan
- [ ] \`pnpm typecheck\` and \`pnpm lint\` both pass on the touched
packages (\`stack-shared\`, \`tanstack-start-demo\`).
- [ ] \`docs-mintlify/guides/getting-started/setup.mdx\` and
\`docs-mintlify/snippets/home-prompt-island.jsx\` are byte-identical to
a fresh \`scripts/generate-setup-prompt-docs.ts\` run.
- [ ] In \`tanstack-start-demo\`, \`/ssr\` renders the user card during
the server response (no flash from signed-out → signed-in), and
\`/client\` renders the empty card on first paint, then resolves to the
user after hydration.
- [ ] \`/handler/sign-in\`, \`/handler/sign-up\`, OAuth callbacks, and
password reset all render correctly through the new splat route.
- [ ] Following the new TanStack Start prompt steps from scratch in an
empty \`npm create @tanstack/start@latest\` project produces a working
sign-in flow without any extra changes.
Made with [Cursor](https://cursor.com)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Added TanStack Start support, provider/theme wiring, SSR and
client-only demo pages, plus an Auth demo card component.
* **Documentation**
* Updated setup guides and snippets across frameworks; clarified env-var
guidance (client reads only project ID; secret is server-only) and
removed misleading publishable-key example.
* Clarified OAuth callback and hosted-domain behavior.
* **Improvements**
* Added loading skeleton UI, refined demo navigation, and tightened
setup wording.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1438?utm_source=github_walkthrough&utm_medium=github&utm_campaign=change_stack)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
---------
Co-authored-by: Cursor <cursoragent@cursor.com>