Switches the localStorage key from the global
"hexclave-rebrand-modal-dismissed" to a per-user
"hexclave-rebrand-modal-dismissed:<user.id>".
Rationale: a shared browser (e.g. a work machine where two teammates
each log into their own dashboard account) was previously hiding the
announcement from teammate B as soon as teammate A dismissed it. Now
each account tracks its own dismissal independently. The trade-off —
the same human switching between their personal and work accounts will
see the modal once per account — is acceptable: each account is a
distinct identity, and a one-time brand notice per account is fine.
Storage key is computed as `${STORAGE_KEY_PREFIX}${user.id}` only when
`user` is non-null; both the useEffect read path and the dismiss write
path null-check `storageKey` defensively even though the existing gates
(`isPreRebrandUser`, `if (!isPreRebrandUser) return null`) already
ensure neither runs without a user.
No migration shim — the old global key was only ever written in local
dev/test before this PR ships, so no production data is being orphaned.
Adds an explicit short-circuit at the top of HexclaveRebrandModal that
returns no modal when any of the three NEXT_PUBLIC_STACK_IS_* dev flags
is true — local emulator, remote development environment, or preview.
These surfaces either auto-create throwaway users (preview) or seed a
fixture admin (emulator/RDE). The rebrand notice would be friction for
developers logging into local dev and meaningless for preview visitors
who never used "Stack Auth" in the first place. Coincidentally these
users are also filtered out today by signedUpAt < REBRAND_CUTOFF (fresh
preview signups happen after the cutoff; a freshly-seeded emulator admin
also signs up post-cutoff), but the explicit env-flag guard is more
defensive — it stays correct if the cutoff is ever bumped forward for a
test.
Same three flags the protected layout already gates on
(apps/dashboard/src/app/(main)/(protected)/layout-client.tsx:15-17).
Announces the rebrand to authenticated dashboard users whose
signedUpAt predates 2026-05-27 UTC (the rebrand cutoff). Brand-new
users signing up after the cutoff already land on Hexclave-branded
surfaces and don't see the modal.
- Modal lives in the protected dashboard layout via
apps/dashboard/src/app/(main)/(protected)/layout-client.tsx so it
surfaces on any logged-in route once per browser.
- Gated on useUser({ or: "return-null" }) + signedUpAt < cutoff, so
it never triggers a sign-in redirect for guests and never shows for
post-rebrand signups.
- Dismissal (button, X, overlay, Escape) routes through onOpenChange
and writes "hexclave-rebrand-modal-dismissed" to localStorage so it
never re-appears for that browser.
- Illustration uses the existing Stack Auth logo (logo.svg /
logo-bright.svg for dark mode) and the Hexclave benzene mark saved
to public/hexclave-icon.svg.
- Copy links app.hexclave.com and docs.hexclave.com/migration.
Screenshot: .github/assets/hexclave-rebrand-modal.png
The legacy docs/ folder still referenced noreply@stackframe.co for the
shared email provider — flip to match the new sender domain set up
on Resend as the dedicated transactional-sender domain. Aligns with
the dashboard + docs-mintlify references that were already flipped.
The bare-name sweep was mutating the sentinel string
('js @stackframe/js@2.8.105') into 'js @hexclave/js@2.8.105' before
the sentinel-specific regex (built from oldName) had a chance to
match, silently leaving the version stuck at the old @stackframe
version on published @hexclave/* artifacts. Reorder so the sentinel
rewrite runs first; the name sweep that follows can't touch the
rewritten sentinel because it no longer contains any @stackframe/*
substrings.
Both failures are pre-existing on `dev` (confirmed by checking the most
recent dev run
[26434368271](https://github.com/hexclave/stack-auth/actions/runs/26434368271)
— same two annotations, same line numbers). Neither is caused by an open
PR.
## Failure 1 — \`apps/backend/src/lib/redirect-urls.test.tsx:75\`
\`\`\`
AssertionError: expected false to be true
\`\`\`
The \`withHostedHandlerEnv\` helper set/cleared only the
\`STACK_*\`-prefixed env vars. CI's
[e2e-custom-base-port-api-tests.yaml:21](.github/workflows/e2e-custom-base-port-api-tests.yaml#L21)
sets only the \`HEXCLAVE_*\`-prefixed sibling
(\`NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX=67\`), and the dual-read shim in
[packages/stack-shared/src/utils/env.tsx#L53-L55](packages/stack-shared/src/utils/env.tsx#L53-L55)
prefers \`HEXCLAVE_*\` over \`STACK_*\`:
\`\`\`ts
const hexclaveName = getHexclaveEnvVarName(name);
let value = (hexclaveName ? process.env[hexclaveName] : undefined) ??
process.env[name];
\`\`\`
So \`getEnvVariable(\"NEXT_PUBLIC_STACK_PORT_PREFIX\", \"81\")\`
returned \`\"67\"\` instead of the test's \`\"92\"\`, the template
resolved to port \`6709\` instead of \`9209\`, and the assertion at line
75 failed.
**Fix:** mirror every \`STACK_*\` key managed by the helper to its
\`HEXCLAVE_*\` sibling. The dual-read then resolves to the
test-controlled value regardless of which key it checks first.
## Failure 2 —
\`apps/backend/prisma/migrations/20260526060000_nullable_oauth_access_token_expires_at/tests/nullable-expires-at.ts:58\`
\`\`\`
PostgresError: null value in column \"updatedAt\" of relation
\"OAuthAccessToken\" violates not-null constraint
\`\`\`
The migration test's raw INSERT omits \`\"updatedAt\"\`. The Prisma
model declares \`updatedAt DateTime @updatedAt\` with no
\`@default(now())\`, so the DB column is \`NOT NULL\` with no default —
Prisma populates it at the ORM layer on insert, but this test bypasses
Prisma via \`postgres.js\`.
**Fix:** add the \`\"updatedAt\"\` column to the INSERT, set to
\`NOW()\`, with a comment noting why raw SQL must set it explicitly.
## Verification
- **Failure 1, before fix:** ran \`NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX=67
pnpm test run apps/backend/src/lib/redirect-urls.test.tsx\` locally →
reproduces the exact line-75 assertion failure from CI.
- **Failure 1, after fix:** same command → 33/33 pass.
- **Failure 2:** local reproduction requires the migration-test postgres
harness; the fix is one column matching how every other raw SQL insert
in this repo handles \`@updatedAt\` fields. CI on this branch will
confirm.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes two failing tests on dev CI by aligning env var handling in
redirect URL tests and by setting the missing updatedAt in a migration
test. Restores green CI with no runtime changes.
- **Bug Fixes**
- Redirect URL tests: `withHostedHandlerEnv` now mirrors `STACK_*`
values to their `HEXCLAVE_*` siblings and restores both, so
`getEnvVariable` reads the test-controlled values even when CI sets only
`HEXCLAVE_*` (e.g. `NEXT_PUBLIC_HEXCLAVE_PORT_PREFIX`).
- Migration test: the raw insert into `OAuthAccessToken` now sets
`"updatedAt" = NOW()` since `Prisma`’s `@updatedAt` isn’t applied when
using `postgres.js` and the column is NOT NULL.
<sup>Written for commit 75c8e4343e.
Summary will update on new commits. <a
href="https://cubic.dev/pr/hexclave/stack-auth/pull/1488?utm_source=github">Review
in cubic</a></sup>
<!-- End of auto-generated description by cubic. -->
Companion to ff44d4ec3 — that commit shortened the disclaimer from
'...error in Hexclave (formerly Stack Auth).' to '...error in Hexclave.'
and updated the url-targets test snapshots, but missed the matching
inline snapshot in apps/backend/src/lib/redirect-urls.test.tsx. The test
suite passes after the update (33/33).
Filter `pnpm publish -r` to only the rewritten @hexclave/* packages in
the mirror step, removing the reliance on pnpm's skip-existing-versions
behavior for the unchanged @stackframe/* packages still in the workspace
at that point.
Addresses greptile P1 finding on PR #1481.
## What
The `description` field in `skills/stack-auth/SKILL.md` failed to parse
as YAML, surfacing on GitHub as:
> Error in user YAML: (<unknown>): mapping values are not allowed in
this context at line 2 column 338
## Why
The `description` was an **unquoted** YAML scalar containing a
colon-space sequence:
> …live, canonical instructions for every Stack Auth surface, including
the CLI**: **how to model users…
In YAML, `: ` inside an unquoted scalar is parsed as a key/value
mapping, which is illegal mid-value — hence *"mapping values are not
allowed in this context."*
## Fix
Wrapped the `description` value in **single** quotes. Single quotes (not
double) because the string already contains embedded double quotes
(`"stack auth"`, `"skill"`) but no apostrophes, so no escaping is
needed.
Verified the frontmatter now parses cleanly — all 9 keys (`name`,
`description`, `version`, `author`, `tags`, `testingTypes`,
`frameworks`, `languages`, `domains`) load via `yaml.safe_load`.
## Notes
- One-line change, content of the description is unchanged.
- The unrelated `@stackframe/dashboard` typecheck failure on this branch
comes from a stale generated `.next/dev/types/routes.d.ts` build
artifact and is not touched by this PR.
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Fixes YAML frontmatter parsing in skills/stack-auth/SKILL.md by quoting
the description, removing the “mapping values are not allowed in this
context” error. Wrapped the description in single quotes to prevent the
`CLI: how...` colon-space from being parsed as a mapping.
<sup>Written for commit b8866bd93a.
Summary will update on new commits. <a
href="https://cubic.dev/pr/hexclave/stack-auth/pull/1487?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
* **Documentation**
* Updated formatting in skill documentation to improve consistency.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1487?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 -->
The HexclaveAssertionError disclaimer was simplified from
"...error in Hexclave (formerly Stack Auth)." to "...error in Hexclave."
but the inline snapshots in url-targets and redirect-urls tests still
expected the longer text. Updates the template source-of-truth; SDK
mirrors regenerate via the preinstall generate-sdks hook.
Rebased onto dev after PR 1475 (cl/hexclave-pr1) was squash-merged.
Squashes the original 46-commit branch (including PR1-duplicate commits
that arrived via cherry-picks/merges) into a single commit containing
only PR2's net delta over dev.
Original PR 1481 head: 94872de407873a1cabd4085deb21b69afe8d7699
(kept locally at backup/cl-romantic-mendel-5a2c25-pre-rebase)
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>
## Summary
- Forward Babel/JSX compile errors, runtime throws, and unhandled
rejections from the AI dashboard sandbox iframe to the parent composer
via `postMessage`, so users see actionable errors instead of a blank
preview
- Compile AI-generated dashboard source explicitly with
`Babel.transform` + try/catch (stored in `text/plain` to avoid Babel's
auto-handler swallowing parse errors) and add `crossorigin="anonymous"`
on the Babel script for readable cross-origin error messages
- Switch authenticated smart-tier model from
`moonshotai/kimi-k2.6:nitro` to `x-ai/grok-build-0.1`
## Test plan
- [ ] Generate a dashboard with valid AI code and confirm the preview
still renders
- [ ] Generate a dashboard with invalid JSX and confirm the composer
shows the compile error (not a blank iframe)
- [ ] Trigger a runtime error in generated dashboard code and confirm it
reaches the parent error boundary
- [ ] Verify authenticated smart-tier requests route to
`x-ai/grok-build-0.1`
Made with [Cursor](https://cursor.com)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Bug Fixes**
* Embedded dashboards now show a clear “Dashboard failed to compile”
message on compilation errors instead of a blank iframe.
* Dashboard runtime errors and unhandled promise rejections are captured
earlier and forwarded to the parent for improved visibility.
* **Updates**
* The authenticated AI model used for the "smart" quality has been
changed, affecting model selection for authenticated requests.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1476?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 -->