Skip to content

local emulator security and features fixes#1247

Merged
BilalG1 merged 42 commits intodevfrom
client-sdk-local-emulator-support
Apr 14, 2026
Merged

local emulator security and features fixes#1247
BilalG1 merged 42 commits intodevfrom
client-sdk-local-emulator-support

Conversation

@BilalG1
Copy link
Copy Markdown
Collaborator

@BilalG1 BilalG1 commented Mar 13, 2026

Summary by CodeRabbit

Release Notes

  • New Features

    • Added Stripe, OAuth, and Freestyle mock services to the local emulator
    • Introduced emulator run CLI command to execute applications with emulator credentials automatically injected
    • Enhanced credential management for local development
  • Improvements

    • Improved ARM64 QEMU emulation with cross-architecture support
    • Better error detection and logging during emulator provisioning
    • Added example middleware configuration with authentication support

@vercel
Copy link
Copy Markdown

vercel bot commented Mar 13, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
3 Building Building Preview, Comment Apr 14, 2026 9:39pm
3-1776188452445-5Jus Canceled Canceled Apr 14, 2026 9:39pm
stack-auth-hosted-components Ready Ready Preview, Comment Apr 14, 2026 9:39pm
stack-backend Ready Ready Preview, Comment Apr 14, 2026 9:39pm
stack-dashboard Ready Ready Preview, Comment Apr 14, 2026 9:39pm
stack-demo Ready Ready Preview, Comment Apr 14, 2026 9:39pm
stack-docs Ready Ready Preview, Comment Apr 14, 2026 9:39pm
stack-preview-backend Ready Ready Preview, Comment Apr 14, 2026 9:39pm
stack-preview-dashboard Ready Ready Preview, Comment Apr 14, 2026 9:39pm

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Mar 13, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR expands the local emulator infrastructure with publishable client key support, enhances credential generation and validation, refactors the emulator provisioning pipeline with improved logging and health checks, adds mock service support (OAuth, Stripe, Freestyle), updates the CLI to support config-based credential injection, and introduces new QEMU boot testing capabilities.

Changes

Cohort / File(s) Summary
Backend API & Credentials
apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx
Updated getOrCreateLocalEmulatorProjectId() to return { projectId, created } instead of just projectId. Extended getOrCreateCredentials() to require and return publishableClientKey alongside existing keys. Updated POST route schema to require/return publishable_client_key. Conditionally calls overrideBranchConfigOverride on new project creation to enable localhost domains.
Seed & Environment
apps/backend/prisma/seed.ts, docker/local-emulator/generate-env-development.mjs, docker/server/entrypoint.sh
Refactored seed logic to perform internal project ApiKeySet upsert earlier in flow. Made seed key environment variables optional in local-emulator mode with null fallback. Shifted credential generation responsibility from seed script to per-VM boot in entrypoint.sh, with conditional validation and database upsert for newly initialized environments.
Docker Infrastructure & Services
docker/local-emulator/Dockerfile, docker/local-emulator/supervisord.conf
Added build stages for freestyle-mock and mock-oauth-server services. Introduced upx-compress stage for binary optimization. Added stripe-mock binary and new mock service program definitions (stripe-mock, freestyle-mock, mock-oauth, cron-jobs) to supervisord configuration. Updated Inbucket UI path configuration.
QEMU Build & Provisioning
docker/local-emulator/qemu/build-image.sh, docker/local-emulator/qemu/cloud-init/emulator/user-data, docker/local-emulator/qemu/test-serial.sh
Extensive refactor of QEMU provisioning pipeline with new marker-detection helpers, incremental log streaming, and dynamic CPU selection (cortex-a76 for cross-arch TCG). Enhanced cloud-init user-data with persistent key generation, skip-migrations flags, mock service endpoint configuration, and improved container startup/logging control. Added new test-serial.sh script for fast boot verification.
Emulator Runtime
docker/local-emulator/qemu/run-emulator.sh, docker/local-emulator/run-cron-jobs.sh
Extended runtime environment variables to include host port mappings and VM directory. Updated QEMU port forwarding to bind to localhost only. Added new cron-jobs polling script for triggering backend async endpoints with configurable timeouts.
GitHub Actions & CI
.github/workflows/qemu-emulator-build.yaml
Introduced matrix-based runner selection per architecture (amd64/arm64 on ubicloud-standard-8). Added KVM device access setup with udev rules. Updated QEMU package installation to include qemu-kvm. Made emulator start/stop steps conditional on amd64 architecture.
CLI & Example App
packages/stack-cli/src/commands/emulator.ts, examples/middleware/package.json, examples/middleware/src/middleware.tsx, examples/middleware/stack.config.ts
Added --config-file option to emulator commands with credential fetching from backend via internal-pck polling. Implemented new emulator run command to start/reuse emulator and inject credentials into child processes. Updated example middleware dev script to use emulator run. Added URL-aware redirect handling and StackConfig type support.
Admin App
packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts
Added blank line after constructor option resolution (formatting only).

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

  • PR #1233: Directly overlaps with same local-emulator project route changes (function return shapes, schema updates, POST handler behavior).
  • PR #1330: Parallel modifications to local-emulator build/runtime stack (QEMU scripts, Dockerfile, cloud-init) affecting identical infrastructure files.
  • PR #1266: Concurrent changes to the same local-emulator project route file with overlapping credential and schema concerns.

Poem

🐰 Hops through emulator pathways,
Keys now published, configs flow,
Mock services dance in tandem,
From CLI seeds to QEMU's glow,
A rabbit's infrastructure aglow! 🌟

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (2 warnings)

Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is empty except for a CONTRIBUTING.md reminder, providing no details about changes, objectives, or implementation approach. Add a detailed description explaining the changes, motivation, key files modified, and how to test the implementation.
Docstring Coverage ⚠️ Warning Docstring coverage is 2.86% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (1 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly describes the main changes, which focus on local emulator functionality and security improvements involving credentials, key management, and configuration for the emulator environment.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch client-sdk-local-emulator-support

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 13, 2026

Greptile Summary

This PR extends the local emulator to support the full SDK hierarchy (client, server, and admin) by adding publishableClientKey to the emulator credentials flow, introducing _updateEmulatorCredentials overrides at each interface level, and wiring up an async init promise that fetches real project credentials from the local emulator on SDK construction.

Key changes and findings:

  • Backend (route.tsx): The getOrCreateCredentials query now validates publishableClientKey via an assertion, but the findFirst query still lacks a publishableClientKey: { not: null } filter. Pre-existing key sets without this field will trigger an assertion error instead of being skipped in favour of creating a new valid key set.
  • server-app-impl.ts / admin-app-impl.ts: _emulatorInitPromise is set but no prepareRequest is passed to StackServerInterface / StackAdminInterface. Since the parent's prepareRequest branch is only entered when no interface override is provided, server and admin requests are not guarded against racing ahead of the credential fetch. There is also no .catch() on the init promise, which creates an unhandled promise rejection if the emulator is unreachable.
  • client-app-impl.ts: Correctly awaits _emulatorInitPromise inside prepareRequest, so client-level requests are safe.
  • entrypoint.sh: Correctly assigns predictable well-known keys in emulator mode so the SDK bootstrap call can authenticate against the internal project before real credentials are loaded.

Confidence Score: 2/5

  • Not safe to merge — two logic bugs can cause authentication failures for server/admin SDK users and data inconsistency when pre-existing emulator key sets are encountered.
  • The missing publishableClientKey filter in the Prisma query and the absent prepareRequest wiring for server/admin interfaces are both correctness bugs that will surface in real usage: the first during upgrades that hit existing key sets, the second for any server/admin SDK call made promptly after app construction in emulator mode.
  • apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx (missing DB filter), packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts and admin-app-impl.ts (missing prepareRequest wiring and unhandled rejection).

Important Files Changed

Filename Overview
apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx Adds publishableClientKey to the response and assertion, but the existingKeySet query is missing a publishableClientKey: { not: null } filter — pre-existing key sets without this field would trigger an assertion error instead of being bypassed and a new valid key set being created.
packages/stack-shared/src/interface/client-interface.ts Adds _projectIdOverride and _publishableClientKeyOverride mutable fields and the _updateEmulatorCredentials method so the interface can switch to project-specific credentials after the async emulator init resolves; the override is correctly prioritized in request headers and the projectId getter.
packages/template/src/lib/stack-app/apps/implementations/common.ts Introduces fetchEmulatorProjectCredentials, emulator URL/key constants, and updates getDefaultProjectId/getBaseUrl/getDefaultSecretServerKey/getDefaultSuperSecretAdminKey to handle the emulator case gracefully without throwing.
packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts Correctly gates emulator credential fetching behind prepareRequest for client-level requests; _emulatorInitPromise is properly awaited before each request when using StackClientInterface.
packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts Sets _emulatorInitPromise but does not pass prepareRequest to StackServerInterface, meaning server-level requests may proceed with placeholder credentials before the real emulator credentials are loaded; also no .catch() means emulator init failures become unhandled promise rejections.
packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts Same prepareRequest/_emulatorInitPromise wiring issue as server-app-impl.ts — admin requests are not guarded; also no .catch() on the init promise.

Sequence Diagram

sequenceDiagram
    participant App as SDK App (Client/Server/Admin)
    participant CI as StackClientInterface
    participant SI as StackServerInterface
    participant BE as Local Emulator Backend
    participant DB as Prisma DB

    App->>CI: new StackClientInterface({ prepareRequest })
    App->>App: _emulatorInitPromise = fetchEmulatorProjectCredentials()
    App->>BE: POST /api/v1/internal/local-emulator/project
    BE->>DB: findFirst(apiKeySet where secretServerKey!=null AND superSecretAdminKey!=null)
    alt Key set found (may lack publishableClientKey — bug)
        DB-->>BE: existing keySet
        BE->>BE: assert publishableClientKey != null (throws if null)
    else No key set found
        DB-->>BE: null
        BE->>DB: create apiKeySet (all 3 keys)
        DB-->>BE: new keySet
    end
    BE-->>App: { project_id, publishable_client_key, secret_server_key, super_secret_admin_key }
    App->>CI: _updateEmulatorCredentials(credentials)
    App->>SI: _updateEmulatorCredentials(credentials)

    Note over App,SI: Client requests: prepareRequest awaits _emulatorInitPromise ✓
    Note over App,SI: Server/Admin requests: NO prepareRequest set — may race ✗

    App->>CI: sendClientRequest (awaits prepareRequest → init promise)
    App->>SI: sendServerRequest (NO prepareRequest → may use placeholder keys)
Loading

Comments Outside Diff (1)

  1. apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx, line 113-130 (link)

    Missing publishableClientKey filter in existingKeySet query

    The assertion at line 144 now requires publishableClientKey to be non-null, but the findFirst query that fetches existingKeySet does not filter for publishableClientKey: { not: null }. If there are any previously-created key sets (from before this PR, when the assertion didn't require publishableClientKey) that have a null publishableClientKey but valid secretServerKey and superSecretAdminKey, the query will return that key set. The subsequent assertion will then throw a StackAssertionError instead of falling through to create a new valid key set.

Last reviewed commit: 01ca850

Comment thread packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts Outdated
Comment thread packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts Outdated
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 4

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx (1)

113-130: ⚠️ Potential issue | 🟡 Minor

Potential issue: Prisma query doesn't filter for publishableClientKey existence.

The query at lines 113-130 filters for secretServerKey: { not: null } and superSecretAdminKey: { not: null } but not publishableClientKey: { not: null }. If there are existing key sets in the database without a publishableClientKey, this could return them, and the assertion at line 144 would throw.

Consider adding publishableClientKey: { not: null } to the query filter for consistency:

Proposed fix
   const existingKeySet = await globalPrismaClient.apiKeySet.findFirst({
     where: {
       projectId,
       manuallyRevokedAt: null,
       expiresAt: {
         gt: new Date(),
       },
+      publishableClientKey: {
+        not: null,
+      },
       secretServerKey: {
         not: null,
       },
       superSecretAdminKey: {
         not: null,
       },
     },
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx`
around lines 113 - 130, The Prisma query in
globalPrismaClient.apiKeySet.findFirst (assigned to existingKeySet) filters for
secretServerKey and superSecretAdminKey but not publishableClientKey, so it may
return rows missing publishableClientKey and later cause the assertion at line
144 to fail; update the findFirst where clause to include publishableClientKey:
{ not: null } so only key sets that contain all three keys (secretServerKey,
superSecretAdminKey, publishableClientKey) are returned by existingKeySet.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@packages/stack-shared/src/interface/client-interface.ts`:
- Around line 53-70: The emulator override only updates
_projectIdOverride/_publishableClientKeyOverride via _updateEmulatorCredentials
but fetchNewAccessToken, getOAuthUrl, and callOAuthCallback still read
this.options.publishableClientKey (and possibly this.options.projectId), causing
inconsistent behavior; update those methods to read the effective accessors
(this.publishableClientKey and this.projectId) instead of directly using
this.options.* so the OAuth/token refresh flow honors the emulator overrides
from _updateEmulatorCredentials.

In `@packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts`:
- Around line 154-164: AdminAppImpl (and similarly ServerAppImpl) sets
this._emulatorInitPromise but never wires a prepareRequest callback on the
created interface, allowing requests to race before emulator credentials load;
add a prepareRequest async callback when constructing/assigning this._interface
that awaits this._emulatorInitPromise if present (i.e., implement
prepareRequest: async () => { if (this._emulatorInitPromise) await
this._emulatorInitPromise; }) so any request via the interface waits for
_emulatorInitPromise to resolve before proceeding.

In `@packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts`:
- Around line 503-538: The emulator credentials are applied asynchronously after
the StackClientInterface is created, so synchronous methods that derive cookie
names (notably _getSession and _getOrCreateTokenStore) can use the pre-override
projectId and miss emulator sessions; fix by ensuring emulator credentials are
resolved before any code can synchronously read projectId or cookie names:
either await fetchEmulatorProjectCredentials(emulatorConfigFilePath) and apply
the returned project_id/publishable_client_key to projectId/publishableClientKey
before constructing StackClientInterface, or modify _getSession and
_getOrCreateTokenStore to await this._emulatorInitPromise (if present) before
deriving cookie names (and ensure any hook/path that calls them will suspend
while _emulatorInitPromise resolves); update references to prepareRequest only
for network protection and keep a single source of truth by applying the
emulator override prior to exposing the app interface.

In `@packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts`:
- Around line 420-438: The server interface is created immediately so initial
requests can run with pre-override keys; to fix, mirror the client-side behavior
by ensuring emulator credential fetch completes before the server request path
uses the interface: when isEmulator and no extraOptions.interface, set
this._emulatorInitPromise = fetchEmulatorProjectCredentials(...), then either
await that promise before constructing/assigning the StackServerInterface or
ensure this._interface’s request-building methods internally await
this._emulatorInitPromise (i.e., update server-app-impl.ts so the code around
StackServerInterface, this._interface and _emulatorInitPromise ensures the
credentials are applied before any inherited request code runs).

---

Outside diff comments:
In `@apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx`:
- Around line 113-130: The Prisma query in
globalPrismaClient.apiKeySet.findFirst (assigned to existingKeySet) filters for
secretServerKey and superSecretAdminKey but not publishableClientKey, so it may
return rows missing publishableClientKey and later cause the assertion at line
144 to fail; update the findFirst where clause to include publishableClientKey:
{ not: null } so only key sets that contain all three keys (secretServerKey,
superSecretAdminKey, publishableClientKey) are returned by existingKeySet.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: f1b977d5-c56f-409f-a728-6a208f15751d

📥 Commits

Reviewing files that changed from the base of the PR and between 612cb71 and 01ca850.

📒 Files selected for processing (11)
  • apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx
  • apps/backend/src/lib/local-emulator.ts
  • docker/server/entrypoint.sh
  • packages/stack-shared/src/interface/admin-interface.ts
  • packages/stack-shared/src/interface/client-interface.ts
  • packages/stack-shared/src/interface/server-interface.ts
  • packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts
  • packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
  • packages/template/src/lib/stack-app/apps/implementations/common.ts
  • packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts
  • packages/template/src/lib/stack-app/apps/interfaces/client-app.ts

Comment thread packages/stack-shared/src/interface/client-interface.ts Outdated
Comment thread packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts Outdated
Comment thread packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts Outdated
Comment thread packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts Outdated
Comment thread packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts Outdated
Comment thread packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts Outdated
Add prepareRequest callbacks to StackServerInterface and
StackAdminInterface that await _emulatorInitPromise, matching
the existing pattern in the client app. Prevents race conditions
where requests use placeholder credentials before the emulator
credentials are fetched.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts (1)

435-435: Consider replacing non-null assertion with explicit error.

While the non-null assertion is logically safe here (the isEmulator guard on line 433 ensures emulatorConfigFilePath is truthy), the coding guidelines prefer explicit error messages that state the assumption.

💡 Suggested improvement
-      this._emulatorInitPromise = fetchEmulatorProjectCredentials(emulatorConfigFilePath!).then((data) => {
+      this._emulatorInitPromise = fetchEmulatorProjectCredentials(emulatorConfigFilePath ?? throwErr("emulatorConfigFilePath must be defined when isEmulator is true")).then((data) => {

As per coding guidelines: "Prefer ?? throwErr(...) over non-null assertions with good error messages explicitly stating the assumption."

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts`
at line 435, Replace the non-null assertion on emulatorConfigFilePath when
initializing this._emulatorInitPromise with an explicit null-check that throws a
clear error; specifically, change the call to
fetchEmulatorProjectCredentials(emulatorConfigFilePath!) in the
_emulatorInitPromise assignment to use emulatorConfigFilePath ??
throwErr("expected emulatorConfigFilePath to be set when isEmulator is true")
(or your project's throw helper), keeping the surrounding isEmulator guard and
the fetchEmulatorProjectCredentials call intact so the code fails with a
descriptive message instead of relying on a non-null assertion.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In `@packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts`:
- Line 435: Replace the non-null assertion on emulatorConfigFilePath when
initializing this._emulatorInitPromise with an explicit null-check that throws a
clear error; specifically, change the call to
fetchEmulatorProjectCredentials(emulatorConfigFilePath!) in the
_emulatorInitPromise assignment to use emulatorConfigFilePath ??
throwErr("expected emulatorConfigFilePath to be set when isEmulator is true")
(or your project's throw helper), keeping the surrounding isEmulator guard and
the fetchEmulatorProjectCredentials call intact so the code fails with a
descriptive message instead of relying on a non-null assertion.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b1420a7b-0c62-4608-9dff-9f228ac2008c

📥 Commits

Reviewing files that changed from the base of the PR and between 01ca850 and 25f8b1c.

📒 Files selected for processing (2)
  • packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts
  • packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts
🚧 Files skipped from review as they are similar to previous changes (1)
  • packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts

@BilalG1 BilalG1 requested a review from N2D4 April 8, 2026 23:58
@BilalG1 BilalG1 assigned N2D4 and unassigned BilalG1 Apr 8, 2026
Merge upstream envVars abstraction (replacing raw process.env reads)
with local emulator support additions. Add new
NEXT_PUBLIC_STACK_LOCAL_EMULATOR_CONFIG_FILE_PATH to envVars.
Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx (1)

114-146: ⚠️ Potential issue | 🟠 Major

Avoid selecting legacy key sets that cannot satisfy the new contract

Line 114 can still load active key sets without publishableClientKey, and Line 145 then hard-fails. That breaks older emulator projects instead of rotating to a new valid key set.

Suggested fix
   const existingKeySet = await globalPrismaClient.apiKeySet.findFirst({
     where: {
       projectId,
       manuallyRevokedAt: null,
       expiresAt: {
         gt: new Date(),
       },
+      publishableClientKey: {
+        not: null,
+      },
       secretServerKey: {
         not: null,
       },
       superSecretAdminKey: {
         not: null,
       },
     },

Based on learnings: Adopt the convention // TODO next-release in this codebase for temporary backward-compat shims that only need to survive one release.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx`
around lines 114 - 146, The find/create logic can return legacy key sets missing
publishableClientKey/secretServerKey/superSecretAdminKey and later throws in the
StackAssertionError; update the selection to prefer only key sets that have all
three keys (modify the globalPrismaClient.apiKeySet.findFirst where clause to
require publishableClientKey/secretServerKey/superSecretAdminKey not null) and
if the found existingKeySet still lacks any required key, ignore it and force
creation via globalPrismaClient.apiKeySet.create (or explicitly create a new key
set when existingKeySet is falsy or incomplete); add a short inline comment
marked "// TODO next-release" by this shim so it can be removed in the next
release.
🧹 Nitpick comments (1)
apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx (1)

145-145: Use explicit null checks in the required-key assertion

Line 145 uses truthiness checks. Please switch to explicit == null checks to match repo standards and avoid conflating empty-string and nullish cases.

Suggested fix
-  if (!keySet.publishableClientKey || !keySet.secretServerKey || !keySet.superSecretAdminKey) {
+  if (
+    keySet.publishableClientKey == null ||
+    keySet.secretServerKey == null ||
+    keySet.superSecretAdminKey == null
+  ) {

As per coding guidelines: Prefer explicit null/undefinedness checks over boolean checks (e.g., foo == null instead of !foo).

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx` at
line 145, The truthiness check on keySet fields conflates empty strings with
null/undefined; update the assertion in route.tsx to use explicit nullish checks
for the three properties: replace the condition that references
publishableClientKey, secretServerKey, and superSecretAdminKey with checks that
each is == null (which catches null or undefined only) so the branch only
triggers for missing keys, not falsy-but-present values.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx`:
- Around line 114-146: The find/create logic can return legacy key sets missing
publishableClientKey/secretServerKey/superSecretAdminKey and later throws in the
StackAssertionError; update the selection to prefer only key sets that have all
three keys (modify the globalPrismaClient.apiKeySet.findFirst where clause to
require publishableClientKey/secretServerKey/superSecretAdminKey not null) and
if the found existingKeySet still lacks any required key, ignore it and force
creation via globalPrismaClient.apiKeySet.create (or explicitly create a new key
set when existingKeySet is falsy or incomplete); add a short inline comment
marked "// TODO next-release" by this shim so it can be removed in the next
release.

---

Nitpick comments:
In `@apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx`:
- Line 145: The truthiness check on keySet fields conflates empty strings with
null/undefined; update the assertion in route.tsx to use explicit nullish checks
for the three properties: replace the condition that references
publishableClientKey, secretServerKey, and superSecretAdminKey with checks that
each is == null (which catches null or undefined only) so the branch only
triggers for missing keys, not falsy-but-present values.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 4823839f-2f09-4674-9563-f63f39d1530b

📥 Commits

Reviewing files that changed from the base of the PR and between 25f8b1c and 51e71cf.

📒 Files selected for processing (2)
  • apps/backend/src/app/api/latest/internal/local-emulator/project/route.tsx
  • apps/backend/src/lib/local-emulator.ts
✅ Files skipped from review due to trivial changes (1)
  • apps/backend/src/lib/local-emulator.ts

BilalG1 added 9 commits April 9, 2026 14:21
Provisioning used to silently wait out the full 6000s timeout on any
guest-side failure because the cleanup trap only logged the error. Now
it writes STACK_CLOUD_INIT_FAILED and shuts the VM down, and the host
waiter breaks on that marker and reports it distinctly.

Also bump smoke test timeout 120s->300s, dump docker ps / container
logs / free -m / verbose curl when it fails, log the qemu accel path,
and enable /dev/kvm on the CI runner so the VM isn't stuck in TCG.
The arm64 matrix entry cross-compiles on the amd64 CI runner, so the
guest runs under QEMU TCG. Under -cpu max, V8 emits armv8.5+ JIT code
that TCG mistranslates and node crashes with SIGTRAP (exit 133)
during migrations. Three changes together get it working:

- Drop to -cpu cortex-a72 for TCG arm64 guests. Limits V8 to
  armv8.0-a which TCG handles cleanly. Native paths (HVF/KVM) keep
  -cpu max for full performance.
- Run migrations with NODE_OPTIONS=--jitless as belt-and-suspenders.
  Migrations are I/O-bound so the perf hit is negligible.
- Skip the in-guest smoke test on arm64. A full Next.js backend under
  cross-arch TCG either SIGTRAPs or times out; the amd64 build still
  runs the smoke test, which covers every non-arch-specific code
  path. Arch is propagated into the guest via a new build-arch.env
  marker in the stack-bundle ISO.
The previous commit set NODE_OPTIONS=--jitless on the migration
docker exec. That was wrong for two reasons:
- --jitless disables eval and new Function, which some code in the
  migration path uses, so it broke amd64 builds that had been passing.
- --jitless is a V8 feature gate, not a TCG workaround. If it breaks
  one arch it breaks both — it could never have helped arm64 either.

Revert the --jitless flag and rely on -cpu cortex-a72 (added in the
parent commit) as the root-cause fix for the arm64 TCG SIGTRAP.

Keep the stdout/stderr capture for the migration exec so the next
failure dumps the actual node error through log-provision instead of
being swallowed by the serial-only stream.
Cross-arch TCG on ubicloud-standard-8 either SIGTRAPs during migrations
(old QEMU) or hangs in wait-for-deps with no progress. GitHub's
ubuntu-24.04-arm runner is an Azure arm64 VM — same-arch TCG, no KVM
(no nested virt exposed) — but empirically completes migrations, the
dep setup, and image packaging end-to-end (verified on the diagnostics
branch run). Only failure there was the backend smoke test hitting its
300s timeout, which the parent commit on this branch already skips on
arm64.

Keep amd64 on ubicloud-standard-8 for its KVM acceleration.
wait-for-deps used to loop forever on each service, so any single
dep that failed to start (e.g. a service crash-looping under TCG)
hung the build until the outer 6000s provision timeout.

Rewrite as a wait_for helper with:
- Hard 1500s budget across the full dep wait (overridable via
  STACK_DEPS_TIMEOUT). On timeout, dump docker ps -a, last 300 lines
  of the deps container, and per-service reachability, then exit 1
  so provision-build's cleanup trap fires and the VM shuts down fast.
- "<service> ready (Ns)" log lines on each service so successful
  runs show which service was the bottleneck.
- 30s heartbeat per service so long-running waits don't look frozen.

amd64 is unaffected — services come up in ~1s each under KVM, which
is well inside any threshold here.
Same-arch TCG (e.g. arm64 guest on the arm64 ubuntu-24.04-arm runner
that has no nested virt) was falling through to -cpu cortex-a72 too.
Empirically that hangs wait-for-deps indefinitely — services never
reach a ready state — probably because QEMU's TCG emulation of named
CPU models is less well-tested than -cpu max, especially for the LSE
atomic fallback paths the dep services exercise.

The cortex-a72 workaround is only needed for cross-arch TCG, where V8
emits JIT instructions the amd64 host's TCG mistranslates. Restrict
it to that case; same-arch TCG now gets -cpu max, matching the known
working config from the diagnostics branch run on ubuntu-24.04-arm.
Comment thread packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts Outdated
Comment thread apps/backend/src/lib/local-emulator.ts Outdated
Comment thread packages/template/src/lib/stack-app/apps/implementations/common.ts Outdated
…tor-support

# Conflicts:
#	packages/stack-shared/src/interface/client-interface.ts
#	packages/template/src/lib/stack-app/apps/implementations/admin-app-impl.ts
#	packages/template/src/lib/stack-app/apps/implementations/client-app-impl.ts
#	packages/template/src/lib/stack-app/apps/implementations/server-app-impl.ts
BilalG1 added 2 commits April 14, 2026 09:32
Keeps stdout clean for callers parsing CLI output; the notice is
informational rather than a primary result.
build.env sets NEXT_PUBLIC_STACK_IS_LOCAL_EMULATOR=true, which makes
docker/server/entrypoint.sh require the three internal SEED keys. At
real-VM boot those come from render-stack-env via local-emulator.env,
but the build-time smoke test doesn't run that path, so the backend
was crash-looping and the amd64 QEMU build failed with a /health
timeout. Mint throwaway hex keys for the smoke-test container only.
@BilalG1 BilalG1 merged commit 88d3317 into dev Apr 14, 2026
32 of 35 checks passed
@BilalG1 BilalG1 deleted the client-sdk-local-emulator-support branch April 14, 2026 22:36
@promptless
Copy link
Copy Markdown
Contributor

promptless bot commented Apr 14, 2026

Promptless prepared a documentation update related to this change.

Triggered by PR #1247

Added documentation for the new emulator run CLI command, mock services (Stripe, OAuth, Freestyle), and automatic credential injection features to the self-host guide.

Review: Document Local Emulator Mode

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

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants