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>
<!--
Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/hexclave/stack-auth/blob/dev/CONTRIBUTING.md
-->
<!-- This is an auto-generated description by cubic. -->
---
## Summary by cubic
Improves onboarding and analytics visuals. Adds clear Stripe setup
actions (Connect or Do Later) with safe redirects and precise
loading/disabled states, and fixes a stuck dashboard reload after
linking an existing project.
- **New Features**
- Payments onboarding: separate Connect/Do Later actions with per-button
loading and mutual disabling.
- US-only: “Do Later” creates a deferred Stripe account before finishing
onboarding.
- Secure redirect: enforce HTTPS on Connect and navigate via
window.location.href.
- Refresh Stripe account cache after `setupPayments()` to avoid stale
data on return.
- Analytics: smoother pie hover transitions, fading center label,
optional `showDateRange`; dashboard donut hides date range and adjusts
radii; revenue hover chart uses split bars for rounded tops and an avg
line.
- Tests cover deferred/unsupported payments setup and button loading
isolation.
- **Bug Fixes**
- After linking an existing config, use a full page navigation to the
project to prevent the dashboard from getting stuck on initial load.
<sup>Written for commit c80034ad1f.
Summary will update on new commits. <a
href="https://cubic.dev/pr/hexclave/stack-auth/pull/1489?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**
* Option to defer payments setup during project onboarding;
connect/defer actions show targeted loading states.
* **Improvements**
* Linking existing projects now performs full navigation when
applicable.
* Charts: refined donut/pie sizing, smoother center fade animation,
optional date-range display, and improved color/stack rendering.
* Payments setup now refreshes account info after setup.
* **Tests**
* Added tests covering payments deferral, connect flows, and UI/loading
behavior.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1489?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 #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
- Add an admin-only delete endpoint and SDK method to remove managed
email domains, with Resend/DNSimple cleanup and a guard against deleting
domains currently in use for sending.
- Add dashboard UI to remove unused managed domains (with confirmation)
and improve the DNS setup step with Cloudflare detection, zone file
download, and import instructions.
- Add E2E coverage for delete auth, success, in-use rejection,
post-switch deletion, and 404 cases.
## Test plan
- [ ] Run `pnpm test run managed-email-onboarding`
- [ ] In dashboard email settings, add a managed domain and verify
Cloudflare hint appears when NS records point to Cloudflare
- [ ] Remove an unused managed domain and confirm it disappears from the
list
- [ ] Verify active (in-use) managed domains cannot be deleted until
email provider is switched away
Made with [Cursor](https://cursor.com)
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Delete managed email domains from the dashboard with a confirmation
flow and success notification
* Cloudflare-aware domain setup: detection banner, quick links to
Cloudflare DNS, downloadable zone file, and import instructions
* Admin API and admin-app method to perform managed-domain deletion
* **Bug Fixes**
* Deletion blocked with a clear error when a domain is actively used for
sending
* **Tests**
* Added end-to-end coverage for managed-domain delete scenarios
(success, in-use conflict, auth rejection, and 404)
* **Style**
* Data grid layout adjusted to prevent unintended full-height stretching
across various tables
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1442?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>
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
End-to-end flow for managing Stack Auth config via GitHub: link a repo
during onboarding, edit settings in the dashboard, and have the change
committed to your repo + synced back via a GitHub Actions workflow.

## What this adds
- **CLI** — `stack config push --source github --source-repo
--source-path --source-workflow-path`. Records the source on the config
row so the dashboard knows where the file lives. Reads `GITHUB_SHA` /
`GITHUB_REF_NAME` for commit + branch.
- **Onboarding "Link existing project"** — searchable repo/branch
comboboxes, auto-detects candidate `stack.config.{ts,js}` paths, writes
`STACK_AUTH_PROJECT_ID` + `STACK_AUTH_SECRET_SERVER_KEY` secrets, and
commits a generated workflow YAML that re-runs `stack config push` on
every change to the config file.
- **Dashboard "Push to GitHub" dialog** — replaces the prior TODO
buttons. Pre-flights `repo`+`workflow` scopes on the user's GitHub
connection; if missing, the button flips to "Reconnect with GitHub". On
push, commits the dashboard's edit straight to the linked repo/branch
via the Contents API (with `cache: "no-store"` to dodge GitHub's 60s GET
cache so consecutive pushes don't 409). Suspense boundary scoped to the
dialog body so opening it doesn't blank the dashboard.
- **Project settings** — surface the linked workflow file as a clickable
GitHub link when the source carries `workflow_path`.
## Test plan
- `pnpm lint` (29/29) ✓
- `pnpm typecheck` (29/29) ✓
- `pnpm --filter @stackframe/stack-cli test` (111/111) ✓
- Dashboard vitest on the three relevant files
(`link-existing-onboarding-workflow`, `github-api`,
`github-config-push`) — 37/37 ✓
- Live end-to-end: `BilalG1/lex-lookup` linked to a local dev project;
passkey toggled, push committed `0bb958bd`
([commit](0bb958bda3)).
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Persist workflow file paths for GitHub-backed config sync
* Dashboard “Push” flow to commit config updates with trimmed/default
commit messages
* CLI options to declare GitHub source (repo/path/workflow) and persist
selectable package runner for manual pushes
* Show workflow-file link in project configuration when present
* **Improvements**
* Robust config-path normalization, existence checks, debounced
repo/branch search, and better GitHub rate-limit handling
* New GitHub API utilities for safe file read/commit and import-package
detection
* **Tests**
* Expanded tests covering GitHub API, config rendering/merge, and push
behaviors
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1450?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
- Replaces per-entry refund schema with a flat `{ amount_usd,
revoke_product, end_subscription? }` shape; refund state is now derived
from bulldozer ledger rows (`refund:<sourceTxnId>:<uuid>`) instead of
the legacy `refundedAt` column, enabling multiple partial refunds up to
the remaining cap.
- Adds `invoice_id` for refunding any subscription invoice (start or
renewal), Stripe idempotency keys derived from `(tenancyId, sourceTxnId,
amount, prior_refunded)` so retries dedupe but intentional partials
don't collide, and a legacy backstop that rejects pre-rework
`refundedAt` purchases.
- Dashboard refund dialog rebuilt around the three toggles (revoke→end
coupling cascades into the UI); refund rows surface in the listing as
`type: "refund"` with `adjusted_by` linkage handling both new and legacy
formats.
## Implements
[STA2-52 — Build in refund logic for
payments](https://linear.app/stack-auth/issue/STA2-52/build-in-refund-logic-for-payments)
## Documented limitations (planned follow-up work)
These are called out in code comments and intentionally deferred to a
follow-up PR:
- **Cap-check race under concurrent refunds.** Bulldozer's embedded
`BEGIN/COMMIT` prevents an outer Prisma tx from scoping the writes, so
two concurrent refunds can both pass the cap check. Needs a
bulldozer-aware mutex or pending-refund-intent pattern. In practice
refunds are admin-only and rare, so the race window is small.
- **Stripe + DB non-atomicity on the DB-success → response-loss path.**
The Stripe idempotency key is keyed on `(tenancyId, sourceTxnId, amount,
priorRefunded)`, so a retry after Stripe-success → DB-fail self-heals
(Stripe dedupes; the next attempt writes the bulldozer row). The hole is
the reverse direction: if the bulldozer row commits but the response is
lost, a retry sees a higher `priorRefunded` and generates a fresh key —
Stripe would issue a second real refund. No out-of-band reconciliation
today.
- **Dashboard can't reach the `invoice_id` path.** Refund actions are
only enabled on `purchase` rows and the submit call never passes
`invoice_id`, so admins refunding a renewal must use the API directly.
Follow-up: enable the action on `subscription-renewal` rows and thread
`invoice_id` through.
## Architectural note
`active-subscription-end` and `item-quantity-expire` entries are **not**
emitted on the refund row itself. They're produced by the derived
sub-end transaction (`transactions.ts:158-228`) once Prisma
`subscription.endedAt` is updated, keeping the `expiresWhen` /
`when-repeated` semantics in one place. This is the main structural
divergence from the ticket's literal entry recipe.
## Review follow-ups addressed in this PR
**First-pass review:**
- **KnownError back-compat preserved**: `SubscriptionAlreadyRefunded` /
`OneTimePurchaseAlreadyRefunded` are once again thrown by the
legacy-`refundedAt` backstop, and `TestModePurchaseNonRefundable` is
thrown when an admin sends `amount_usd > 0` against a test-mode
purchase. Callers catching by error code keep working through the
rework.
- **Idempotency-key comment corrected**: now accurately describes the
`(tenancyId, sourceTxnId, amount, priorRefunded)` key and its
self-healing behaviour on the Stripe-success → DB-fail retry path (see
Documented limitations above for the remaining hole).
- **Renewal-invoice e2e coverage added**: new test sets up a live-mode
subscription via Stripe webhooks (`subscription_create` +
`subscription_cycle` invoices), refunds the renewal invoice via
`invoice_id`, and asserts the resulting `refund_transaction_id` starts
with `refund:sub-renewal:` and is linked back via `adjusted_by` on the
*renewal* row (not the start row). Plus negative cases:
cross-subscription `invoice_id` → 404, `invoice_id` on a one-time
purchase → SchemaError.
**Second-pass review:**
- **Idempotent sub-cancel error-code string fix**: the Stripe code for
re-cancelling an already-canceled sub is
`subscription_already_canceled`, not `subscription_canceled` — the
previous catch would have re-thrown.
- **End-only sub refund replay rejected**: when `amount=0, revoke=false,
end=true` and the sub is already `cancelAtPeriodEnd` or `endedAt`, throw
SchemaError. Otherwise `readPriorRefundSummary` doesn't see end-only
events and the call would be a forever-no-op accumulating empty refund
rows.
- **`revoke_product=true` with renewal `invoice_id` rejected**: the
product grant lives on the sub-start txn, not on renewal txns — a
renewal-scoped revocation would write a back-reference to a non-existent
entry. Forces admin to revoke against the start invoice (or the default
no-`invoice_id` call).
- **Refund row `id` matches the linkage**: the listing route now returns
the full refund txnId as `id` for `type: "refund"` rows so it matches
`adjusted_by.transaction_id` — the dashboard can join source rows to
their refund rows.
- **+2 e2e tests** for the above (end-only replay rejection,
revoke+renewal rejection).
**Third-pass review:**
- **Dashboard refund dialog seeds state on open**: previously the reset
block lived in `ActionDialog`'s `onOpenChange`, which doesn't fire on
the open transition for a controlled dialog. As a result the dialog
opened with the initial `useState` defaults (`amountUsd = '0'`), and an
admin submitting unchanged on a paid purchase would revoke/end at $0
instead of refunding the charged amount. The seed now runs in the menu
`onClick` before `setIsDialogOpen(true)`.
- **`SUBSCRIPTION_START_PRODUCT_GRANT_ENTRY_INDEX` corrected from 1 →
0**: the constant is persisted as `adjustedEntryIndex` on
product-revocation entries and copied through verbatim by
`mapLedgerEntry`. That mapper drops the hidden
`active-subscription-start` entry, so the public-API layout puts the
product grant at index 0. The prior value of `1` pointed at the
money-transfer entry (or out of range on test-mode subs) through the
public listing.
- **`amountTotal` cap gated behind a USD pre-flight**:
`SubscriptionInvoice` doesn't persist invoice currency, and the previous
code took `invoice.amountTotal` as USD cents directly. Now
`getTotalUsdStripeUnits` (which throws on non-USD pricing) is always
called first; `amountTotal` is only preferred as the actual cap after
that pre-flight succeeds.
## Test plan
- [x] `pnpm typecheck` — 28/28 pass
- [x] `pnpm lint` — 28/28 pass
- [x] `pnpm test run
apps/e2e/tests/backend/endpoints/api/v1/internal/transactions-refund.test.ts`
— **19/19 pass** (was 14/14 on the original PR; +3 for `invoice_id`
path: renewal refund happy path, unrelated `invoice_id` rejection,
`invoice_id` on OTP rejection; +2 for second-pass: end-only replay
rejection, revoke+renewal rejection)
- [x] curl smoke against
`/api/latest/internal/payments/transactions/refund` — unknown purchase →
404, no-op → 400, negative → 400, sub-revoke-without-end → 400
- [x] **Dashboard UI end-to-end re-run pending** — the original
agent-browser pass ran before the third-pass dialog-seed fix, so any
"money + revoke" submissions may have actually sent `amount_usd = "0"`.
Re-test before un-drafting: open the refund dialog from the menu,
confirm the amount field pre-fills with the charged amount, exercise
validation (negative / exceeds-cap / no-op), and submit both an
end-subscription-only sub refund and a money+revoke OTP refund; verify
bulldozer rows and Prisma `cancelAtPeriodEnd` updates.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Ledger-driven refund flow with stable refund IDs, invoice-aware
refunds, OTP/product-revocation support, tri-state end_action (now /
at-period-end / none), and API responses that include
refund_transaction_id.
* **Bug Fixes / Improvements**
* Deterministic Stripe idempotency, stronger replay protection,
refundable-amount caps, test-mode constraints, and transactions listing
updated to surface refunds.
* **Tests**
* Expanded unit and E2E coverage for new request shape, invoice paths,
money-unit conversion, and edge cases.
<!-- review_stack_entry_start -->
[](https://app.coderabbit.ai/change-stack/hexclave/stack-auth/pull/1429)
<!-- review_stack_entry_end -->
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
## Summary
- Adds the generated `@stackframe/tanstack-start` workspace package
registration.
- Adds TanStack Start platform macros/dependencies to the SDK template
and generator.
- Adds TanStack Start cookie/token-store support plus the handler SSR
guard needed by Start.
## Scope
This intentionally excludes Dashboard V2 routes, hooks, components, app
shell logic, and dashboard API type additions. Those stay in the
existing dashboard PR/branch.
## Validation
- `pnpm install --lockfile-only --ignore-scripts`
- `pnpm install --ignore-scripts`
- `pnpm -C packages/template lint
src/components-page/stack-handler-client.tsx src/lib/cookie.ts
src/lib/stack-app/apps/implementations/client-app-impl.ts`
Package typecheck was attempted with `pnpm -C packages/template
typecheck`, but the clean worktree lacks generated package declaration
outputs for workspace dependencies such as `@stackframe/stack-shared`
and `@stackframe/stack-ui`. Per repo instructions, package
builds/codegen are not run by agents.
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* TanStack Start integration: published SDK package, example demo app,
dashboard onboarding flow, framework-aware CTAs/docs, and a
TanStack-specific provider for client-only auth routes.
* Improved client/server auth: safer runtime guards and consistent
cookie/token-store behavior across SSR and client.
* **Documentation**
* New Integrations guide and expanded getting-started/setup docs with
TanStack Start examples and env/key guidance.
* **Chores**
* Template, build, tooling, and demo config updates to support the new
platform.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!--
Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md
-->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **Refactor**
* Enhanced CLI authentication confirmation tracking to improve session
persistence and state management during sign-in flows.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
<!--
Make sure you've read the CONTRIBUTING.md guidelines:
https://github.com/stack-auth/stack-auth/blob/dev/CONTRIBUTING.md
-->
<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit
* **New Features**
* Tabbed user profile with Activity (30-day analytics, KPIs, daily
chart, top lists, recent events), Payments (transactions, subscriptions,
product/item balances) and an activity heatmap sidebar.
* New internal user-activity API and admin-facing activity hook; admin
API client can fetch per-user activity.
* **UI/UX Improvements**
* Unified menus, cards and tables; inline editable user details with
accept/revert; metadata editor validates JSON; country-code input has
draft editing; tabs support optional icons.
* **API**
* Transactions endpoint and admin transaction queries now support
optional customer-scoped filtering.
* **Tests**
* End-to-end coverage for the user-activity endpoint.
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
<img width="1326" height="752" alt="image"
src="https://github.com/user-attachments/assets/97c04dca-db59-4357-98b1-8eae5a7a3673"
/>
<img width="1142" height="251" alt="image"
src="https://github.com/user-attachments/assets/e1aa44fc-0d7e-436d-90a5-c7cb15155e24"
/>
<img width="1170" height="1125" alt="image"
src="https://github.com/user-attachments/assets/bf6659fd-a9b5-4ae6-a13d-dab9956ad650"
/>